@rackbops/ac-agent 2.0.0-alpha.42 → 2.0.0-alpha.43

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/ac-agent.mjs CHANGED
@@ -47,7 +47,7 @@ function _mergeNamespaces(n, m) {
47
47
  }
48
48
 
49
49
  var name = "@rackbops/ac-agent";
50
- var version = "2.0.0-alpha.42";
50
+ var version = "2.0.0-alpha.43";
51
51
  var type = "module";
52
52
  var description = "The per-machine host agent (installed with npx @rackbops/ac-agent): an HTTPS health server plus install/status/uninstall for a Windows logon task or a systemd user unit.";
53
53
  var bin = {
@@ -73,7 +73,7 @@ var dependencies = {
73
73
  "@hono/node-server": "^2.1.3",
74
74
  hono: "^4.13.11",
75
75
  selfsigned: "^5.0.0",
76
- "smol-toml": "^1.0.0",
76
+ "smol-toml": "^1.9.0",
77
77
  yaml: "^2.9.1"
78
78
  };
79
79
  var devDependencies = {
@@ -447,134 +447,6 @@ function createClaudeDesktopWatch(deps, hooks = {}, opts = {}) {
447
447
  return { current: () => held, refresh, start };
448
448
  }
449
449
 
450
- /*!
451
- * Copyright (c) Squirrel Chat et al., All rights reserved.
452
- * SPDX-License-Identifier: BSD-3-Clause
453
- *
454
- * Redistribution and use in source and binary forms, with or without
455
- * modification, are permitted provided that the following conditions are met:
456
- *
457
- * 1. Redistributions of source code must retain the above copyright notice, this
458
- * list of conditions and the following disclaimer.
459
- * 2. Redistributions in binary form must reproduce the above copyright notice,
460
- * this list of conditions and the following disclaimer in the
461
- * documentation and/or other materials provided with the distribution.
462
- * 3. Neither the name of the copyright holder nor the names of its contributors
463
- * may be used to endorse or promote products derived from this software without
464
- * specific prior written permission.
465
- *
466
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
467
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
468
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
469
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
470
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
471
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
472
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
473
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
474
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
475
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
476
- */
477
- let DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[T ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|[-+]\d{2}:\d{2})?$/i;
478
- class TomlDate extends Date {
479
- #hasDate = false;
480
- #hasTime = false;
481
- #offset = null;
482
- constructor(date) {
483
- let hasDate = true;
484
- let hasTime = true;
485
- let offset = 'Z';
486
- if (typeof date === 'string') {
487
- let match = date.match(DATE_TIME_RE);
488
- if (match) {
489
- if (!match[1]) {
490
- hasDate = false;
491
- date = `0000-01-01T${date}`;
492
- }
493
- hasTime = !!match[2];
494
- // Make sure to use T instead of a space. Breaks in case of extreme values otherwise.
495
- hasTime && date[10] === ' ' && (date = date.replace(' ', 'T'));
496
- // Do not allow rollover hours.
497
- if (match[2] && +match[2] > 23) {
498
- date = '';
499
- }
500
- else {
501
- offset = match[3] || null;
502
- date = date.toUpperCase();
503
- if (!offset && hasTime)
504
- date += 'Z';
505
- }
506
- }
507
- else {
508
- date = '';
509
- }
510
- }
511
- super(date);
512
- if (!isNaN(this.getTime())) {
513
- this.#hasDate = hasDate;
514
- this.#hasTime = hasTime;
515
- this.#offset = offset;
516
- }
517
- }
518
- isDateTime() {
519
- return this.#hasDate && this.#hasTime;
520
- }
521
- isLocal() {
522
- return !this.#hasDate || !this.#hasTime || !this.#offset;
523
- }
524
- isDate() {
525
- return this.#hasDate && !this.#hasTime;
526
- }
527
- isTime() {
528
- return this.#hasTime && !this.#hasDate;
529
- }
530
- isValid() {
531
- return this.#hasDate || this.#hasTime;
532
- }
533
- toISOString() {
534
- let iso = super.toISOString();
535
- // Local Date
536
- if (this.isDate())
537
- return iso.slice(0, 10);
538
- // Local Time
539
- if (this.isTime())
540
- return iso.slice(11, 23);
541
- // Local DateTime
542
- if (this.#offset === null)
543
- return iso.slice(0, -1);
544
- // Offset DateTime
545
- if (this.#offset === 'Z')
546
- return iso;
547
- // This part is quite annoying: JS strips the original timezone from the ISO string representation
548
- // Instead of using a "modified" date and "Z", we restore the representation "as authored"
549
- let offset = (+(this.#offset.slice(1, 3)) * 60) + +(this.#offset.slice(4, 6));
550
- offset = this.#offset[0] === '-' ? offset : -offset;
551
- let offsetDate = new Date(this.getTime() - (offset * 60e3));
552
- return offsetDate.toISOString().slice(0, -1) + this.#offset;
553
- }
554
- static wrapAsOffsetDateTime(jsDate, offset = 'Z') {
555
- let date = new TomlDate(jsDate);
556
- date.#offset = offset;
557
- return date;
558
- }
559
- static wrapAsLocalDateTime(jsDate) {
560
- let date = new TomlDate(jsDate);
561
- date.#offset = null;
562
- return date;
563
- }
564
- static wrapAsLocalDate(jsDate) {
565
- let date = new TomlDate(jsDate);
566
- date.#hasTime = false;
567
- date.#offset = null;
568
- return date;
569
- }
570
- static wrapAsLocalTime(jsDate) {
571
- let date = new TomlDate(jsDate);
572
- date.#hasDate = false;
573
- date.#offset = null;
574
- return date;
575
- }
576
- }
577
-
578
450
  /*!
579
451
  * Copyright (c) Squirrel Chat et al., All rights reserved.
580
452
  * SPDX-License-Identifier: BSD-3-Clause
@@ -603,11 +475,11 @@ class TomlDate extends Date {
603
475
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
604
476
  */
605
477
  function getLineColFromPtr(string, ptr) {
606
- let lines = string.slice(0, ptr).split(/\r\n|\n|\r/g);
478
+ let lines = string.slice(0, ptr).split(/\r?\n/);
607
479
  return [lines.length, lines.pop().length + 1];
608
480
  }
609
481
  function makeCodeBlock(string, line, column) {
610
- let lines = string.split(/\r\n|\n|\r/g);
482
+ let lines = string.split(/\r?\n/);
611
483
  let codeblock = '';
612
484
  let numberLen = (Math.log10(line + 1) | 0) + 1;
613
485
  for (let i = line - 1; i <= line + 1; i++) {
@@ -637,95 +509,10 @@ class TomlError extends Error {
637
509
  this.column = column;
638
510
  this.codeblock = codeblock;
639
511
  }
640
- }
641
-
642
- /*!
643
- * Copyright (c) Squirrel Chat et al., All rights reserved.
644
- * SPDX-License-Identifier: BSD-3-Clause
645
- *
646
- * Redistribution and use in source and binary forms, with or without
647
- * modification, are permitted provided that the following conditions are met:
648
- *
649
- * 1. Redistributions of source code must retain the above copyright notice, this
650
- * list of conditions and the following disclaimer.
651
- * 2. Redistributions in binary form must reproduce the above copyright notice,
652
- * this list of conditions and the following disclaimer in the
653
- * documentation and/or other materials provided with the distribution.
654
- * 3. Neither the name of the copyright holder nor the names of its contributors
655
- * may be used to endorse or promote products derived from this software without
656
- * specific prior written permission.
657
- *
658
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
659
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
660
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
661
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
662
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
663
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
664
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
665
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
666
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
667
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
668
- */
669
- /** @internal */
670
- function indexOfNewline(str, start = 0) {
671
- let idx = str.indexOf('\n', start);
672
- if (str.charCodeAt(idx - 1) === 0xd /* \r */)
673
- idx--;
674
- return idx;
675
- }
676
- /** @internal */
677
- function skipComment$1(ctx) {
678
- for (; ctx.p < ctx.s.length; ctx.p++) {
679
- let c = ctx.s.charCodeAt(ctx.p);
680
- if (c === 0xa /* \n */)
681
- break;
682
- if (c === 0xd /* \r */ && ctx.s.charCodeAt(ctx.p + 1) === 0xa /* \n */) {
683
- ctx.p++;
684
- break;
685
- }
686
- if ((c < 0x20 && c !== 0x9 /* \t */) || c === 0x7f) {
687
- throw new TomlError('control characters are not allowed in comments', {
688
- toml: ctx.s,
689
- ptr: ctx.p,
690
- });
691
- }
692
- }
693
- }
694
- /** @internal */
695
- function skipVoid(ctx, banNewLines, banComments) {
696
- let c;
697
- while (1) {
698
- while ((c = ctx.s.charCodeAt(ctx.p)) === 0x20 ||
699
- c === 0x9 /* \t */ ||
700
- (!banNewLines &&
701
- (c === 0xa /* \n */ || (c === 0xd /* \r */ && ctx.s.charCodeAt(ctx.p + 1) === 0xa /* \n */))))
702
- ctx.p++;
703
- if (banComments || c !== 0x23 /* # */)
704
- break;
705
- skipComment$1(ctx);
706
- }
707
- }
708
- /** @internal */
709
- function skipUntil(ctx, sep, end) {
710
- let ptr = ctx.p;
711
- if (!end) {
712
- ptr = indexOfNewline(ctx.s, ptr);
713
- ctx.p = ptr < 0 ? ctx.s.length : ptr;
714
- return;
715
- }
716
- for (; ctx.p < ctx.s.length; ctx.p++) {
717
- let c = ctx.s.charCodeAt(ctx.p);
718
- if (c === 0x23 /* # */) {
719
- skipComment$1(ctx);
720
- }
721
- else if (c === end || c === sep) {
722
- return;
723
- }
512
+ /** @internal */
513
+ static x(message, ctx, ptr) {
514
+ throw new TomlError(message, { toml: ctx.s, ptr: ptr ?? ctx.p });
724
515
  }
725
- throw new TomlError('cannot find end of structure', {
726
- toml: ctx.s,
727
- ptr,
728
- });
729
516
  }
730
517
 
731
518
  /*!
@@ -755,13 +542,9 @@ function skipUntil(ctx, sep, end) {
755
542
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
756
543
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
757
544
  */
758
- // let CTRL_REGEX = /[\x00-\x08\x0f-\x1f\x7f]/
759
- let INT_REGEX = /^((0x[0-9a-fA-F](_?[0-9a-fA-F])*)|(([+-]|0[ob])?\d(_?\d)*))$/;
760
- let FLOAT_REGEX = /^[+-]?\d(_?\d)*(\.\d(_?\d)*)?([eE][+-]?\d(_?\d)*)?$/;
761
- let LEADING_ZERO = /^[+-]?0[0-9_]/;
762
545
  /** @internal */
763
546
  function parseString(ctx) {
764
- let start = ctx.p;
547
+ let startPtr = ctx.p;
765
548
  let c = ctx.s.charCodeAt(ctx.p++);
766
549
  let first = c;
767
550
  let isLiteral = c === 0x27; /* ' */
@@ -773,37 +556,8 @@ function parseString(ctx) {
773
556
  else if (c === 0xd /* \r */ && ctx.s.charCodeAt(ctx.p + 1) === 0xa /* \n */)
774
557
  ctx.p += 2;
775
558
  }
776
- /*
777
- The fast path does not seem to bring significant performance gains, so it's commented out.
778
- Kept for reference and/or future fafoing.
779
-
780
- Without: spec 5.08 µs/iter 3.88 ipc (99.44% cache) 23.90 branch misses 28.61k cycles 111.01k instructions
781
- 5MB 115.73 ms/iter 2.51 ipc (98.36% cache) 3.12M branch misses 619.30M cycles 1.56G instructions
782
-
783
- With: spec 5.09 µs/iter 3.90 ipc (99.46% cache) 24.42 branch misses 28.57k cycles 111.49k instructions
784
- 5MB 113.89 ms/iter 2.47 ipc (98.38% cache) 3.12M branch misses 611.94M cycles 1.51G instructions
785
-
786
- if (c === "'") {
787
- // Literal strings fast path - no transform needs to occur; just grab the str and that's it
788
- let endPtr = str.indexOf(isMultiline ? "'''" : "'", ptr)
789
- if (endPtr < 0) {
790
- throw new TomlError("unfinished string literal", { toml: str, ptr })
791
- }
792
-
793
- if (isMultiline) {
794
- // If the string ends with 4-5 quotes, then the first 1-2 are part of the string
795
- if (str[endPtr + 3] === "'") endPtr++
796
- if (str[endPtr + 3] === "'") endPtr++
797
- }
798
-
799
- let string = str.slice(ptr, endPtr)
800
- if (CTRL_REGEX.test(string)) {
801
- let match = string.match(CTRL_REGEX)!
802
- throw new TomlError('control characters are not allowed in strings', { toml: str, ptr: ptr + (match.index ?? 0) })
803
- }
804
- return [string, endPtr + (isMultiline ? 3 : 1)]
805
- }
806
- */
559
+ // For the record: it is not worth it to use a fast-path for literal strings using `indexOf`.
560
+ // Doing a char-by-char iteration ends up dispatching less instructions, for the same wall-clock runtime.
807
561
  let parsed = '';
808
562
  let sliceStart = ctx.p;
809
563
  // states:
@@ -820,10 +574,7 @@ function parseString(ctx) {
820
574
  }
821
575
  // Control characters are banned in TOML, so we throw an error if we encounter them
822
576
  else if ((c < 0x20 && c !== 0x9 /* \t */) || c === 0x7f) {
823
- throw new TomlError('control characters are not allowed in strings', {
824
- toml: ctx.s,
825
- ptr: ctx.p,
826
- });
577
+ TomlError.x('control characters are not allowed in strings', ctx);
827
578
  }
828
579
  // The string might terminate while we're parsing through a newline escape.
829
580
  // It must have encountered a newline; otherwise, it'll simply fail in another branch.
@@ -836,11 +587,15 @@ function parseString(ctx) {
836
587
  ctx.p++;
837
588
  }
838
589
  // If we're in a newline escape still, then there's nothing to add.
839
- if (!state)
840
- parsed += ctx.s.slice(sliceStart, ctx.p);
590
+ if (!state) {
591
+ // Avoid a useless concat operation if the string can be used as-is.
592
+ let s = ctx.s.slice(sliceStart, ctx.p);
593
+ parsed = parsed ? parsed + s : s;
594
+ }
841
595
  ctx.p += isMultiline ? 3 : 1;
842
596
  return parsed;
843
597
  }
598
+ // Baseline state; keep moving forward unless an escape sequence starts
844
599
  else if (!state) {
845
600
  if (!isLiteral && c === 0x5c /* \ */) {
846
601
  parsed += ctx.s.slice(sliceStart, (sliceStart = ctx.p));
@@ -849,29 +604,32 @@ function parseString(ctx) {
849
604
  }
850
605
  else if (state === 1) {
851
606
  if (c === 0x78 /* x */ || c === 0x75 /* u */ || c === 0x55 /* U */) { // Unicode escape
607
+ let errPtr = ctx.p++ - 1;
852
608
  let value = 0;
853
609
  let len = c === 0x78 /* x */ ? 2 : c === 0x75 /* u */ ? 4 : 8;
854
610
  for (let j = 0; j < len; j++, ctx.p++) {
855
- let hex = ctx.s.charCodeAt(ctx.p + 1);
611
+ let hex = ctx.s.charCodeAt(ctx.p);
856
612
  let digit =
857
613
  /* 0-9 */ hex >= 0x30 && hex <= 0x39 ? hex - 0x30 :
858
614
  /* A-F */ hex >= 0x41 && hex <= 0x46 ? hex - 0x41 + 10 :
859
615
  /* a-f */ hex >= 0x61 && hex <= 0x66 ? hex - 0x61 + 10 : -1;
860
616
  if (digit < 0)
861
- throw new TomlError('invalid non-hex character in unicode escape', { toml: ctx.s, ptr: ctx.p + 1 });
617
+ TomlError.x('invalid non-hex character in unicode escape', ctx);
862
618
  value = (value << 4) | digit;
863
619
  }
864
620
  // Because JS does bitwise on signed 32bit integers, all 0xfzzzzzzz values are actually seen as negative
865
621
  if (value < 0 || value > 0x10ffff || (value >= 0xd800 && value <= 0xdfff)) {
866
- throw new TomlError('invalid unicode escape', { toml: ctx.s, ptr: ctx.p });
622
+ TomlError.x('invalid unicode escape', ctx, errPtr);
867
623
  }
868
624
  parsed += String.fromCodePoint(value);
869
- sliceStart = ctx.p + 1;
625
+ sliceStart = ctx.p--;
870
626
  state = 0;
871
627
  }
872
- else if (c === 0x20 || c === 0x9 /* \t */) { // If it was a newline, it'd have been handled earlier
628
+ // Newline escape sequence. We only need to care about spaces and tabs; newlines are dealt with earlier
629
+ else if (isMultiline && (c === 0x20 || c === 0x9 /* \t */)) {
873
630
  state = 2;
874
631
  }
632
+ // Basic escape sequence
875
633
  else {
876
634
  if (c === 0x62 /* b */)
877
635
  parsed += '\b';
@@ -890,78 +648,179 @@ function parseString(ctx) {
890
648
  else if (c === 0x5c /* \ */)
891
649
  parsed += '\\';
892
650
  else
893
- throw new TomlError('unrecognized escape sequence', { toml: ctx.s, ptr: ctx.p });
651
+ TomlError.x('unrecognised escape sequence', ctx);
894
652
  sliceStart = ctx.p + 1;
895
653
  state = 0;
896
654
  }
897
655
  }
656
+ // Newline escape continuation: keep moving forward until the first non-whitespace char
898
657
  else if (c !== 0x20 && c !== 0x9 /* \t */) {
899
- if (state === 2) {
900
- throw new TomlError('invalid escape: only line-ending whitespace may be escaped', {
901
- toml: ctx.s,
902
- ptr: sliceStart,
903
- });
904
- }
658
+ if (state === 2)
659
+ TomlError.x('invalid escape: only line-ending whitespace may be escaped', ctx, sliceStart);
905
660
  // State cannot be zero, or we'd have branched earlier already.
906
661
  // If it's a backslash, immediately transition to the escape state so it can be processed.
907
662
  state = !isLiteral && c === 0x5c /* \ */ ? 1 : 0;
908
663
  sliceStart = ctx.p;
909
664
  }
910
665
  }
911
- throw new TomlError('unfinished string', { toml: ctx.s, ptr: start });
912
- }
913
- function sliceAndTrimEndOf(ctx, start, end) {
914
- let value = ctx.s.slice(start, end);
915
- let commentIdx = value.indexOf('#');
916
- if (commentIdx > 0) {
917
- // The call to skipComment allows to "validate" the comment
918
- // (absence of control characters)
919
- skipComment$1({ s: value, p: commentIdx});
920
- value = value.slice(0, commentIdx);
921
- }
922
- return value.trimEnd();
666
+ TomlError.x('unfinished string', ctx, startPtr);
923
667
  }
924
- /** @internal */
925
- function parseValue(ctx, integersAsBigInt, end) {
926
- let ptr = ctx.p;
927
- let err = { toml: ctx.s, ptr };
928
- skipUntil(ctx, 0x2c /* , */, end);
929
- let value = sliceAndTrimEndOf(ctx, ptr, ctx.p);
930
- if (!value)
931
- throw new TomlError('incomplete declaration: value expected', err);
932
- if (value === '-inf')
933
- return -Infinity;
934
- if (value === 'inf' || value === '+inf')
935
- return Infinity;
936
- if (value === 'nan' || value === '+nan' || value === '-nan')
937
- return NaN;
938
- // Avoid FP representation of -0
939
- if (value === '-0')
940
- return integersAsBigInt ? 0n : 0;
941
- // Numbers
942
- let isInt = INT_REGEX.test(value);
943
- if (isInt || FLOAT_REGEX.test(value)) {
944
- if (LEADING_ZERO.test(value)) {
945
- throw new TomlError('leading zeroes are not allowed', err);
946
- }
947
- value = value.replace(/_/g, '');
948
- let numeric = +value;
949
- if (isNaN(numeric)) {
950
- throw new TomlError('invalid number', err);
951
- }
952
- if (isInt) {
953
- if ((isInt = !Number.isSafeInteger(numeric)) && !integersAsBigInt) {
954
- throw new TomlError('integer value cannot be represented losslessly', err);
668
+
669
+ /*!
670
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
671
+ * SPDX-License-Identifier: BSD-3-Clause
672
+ *
673
+ * Redistribution and use in source and binary forms, with or without
674
+ * modification, are permitted provided that the following conditions are met:
675
+ *
676
+ * 1. Redistributions of source code must retain the above copyright notice, this
677
+ * list of conditions and the following disclaimer.
678
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
679
+ * this list of conditions and the following disclaimer in the
680
+ * documentation and/or other materials provided with the distribution.
681
+ * 3. Neither the name of the copyright holder nor the names of its contributors
682
+ * may be used to endorse or promote products derived from this software without
683
+ * specific prior written permission.
684
+ *
685
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
686
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
687
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
688
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
689
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
690
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
691
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
692
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
693
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
694
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
695
+ */
696
+ let DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[Tt ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|z|[-+]\d{2}:\d{2})?$/i;
697
+ class TomlDate extends Date {
698
+ #hasDate = false;
699
+ #hasTime = false;
700
+ #offset = null;
701
+ constructor(date, fasttype, unsafeDelim) {
702
+ let hasDate = true;
703
+ let hasTime = true;
704
+ let offset = 'Z';
705
+ let c;
706
+ if (typeof date === 'string') {
707
+ if (fasttype)
708
+ prep: {
709
+ // Date-time
710
+ if (fasttype < 3) {
711
+ if (+date.slice(11, 13) > 23) {
712
+ date = '';
713
+ break prep;
714
+ }
715
+ // Local
716
+ if (fasttype === 2) {
717
+ offset = null;
718
+ date += 'Z';
719
+ }
720
+ // Offset; keep track of offset if not Z
721
+ else if ((c = date.charCodeAt(date.length - 1)) !== 0x5a /* Z */ && c !== 0x7a /* z */) {
722
+ offset = date.slice(date.length - 6);
723
+ }
724
+ if (unsafeDelim)
725
+ date = date.slice(0, 10) + 'T' + date.slice(11);
726
+ }
727
+ // Time
728
+ else if (fasttype === 4) {
729
+ date = +date.slice(0, 2) > 23 ? '' : `0000-01-01T${date}Z`;
730
+ }
731
+ hasDate = fasttype !== 4;
732
+ hasTime = fasttype !== 3;
733
+ }
734
+ else {
735
+ let match = date.match(DATE_TIME_RE);
736
+ if (match) {
737
+ if (!match[1]) {
738
+ hasDate = false;
739
+ date = `0000-01-01T${date}`;
740
+ }
741
+ hasTime = !!match[2];
742
+ // Make sure to use T instead of a space. Breaks in case of extreme values otherwise.
743
+ hasTime && date[10] === ' ' && (date = date.replace(' ', 'T'));
744
+ // Do not allow rollover hours.
745
+ if (match[2] && +match[2] > 23) {
746
+ date = '';
747
+ }
748
+ else {
749
+ offset = match[3] || null;
750
+ if (!offset && hasTime)
751
+ date += 'Z';
752
+ }
753
+ }
754
+ else {
755
+ date = '';
756
+ }
955
757
  }
956
- if (isInt || integersAsBigInt === true)
957
- numeric = BigInt(value);
958
758
  }
959
- return numeric;
759
+ super(date);
760
+ if (!isNaN(this.getTime())) {
761
+ this.#hasDate = hasDate;
762
+ this.#hasTime = hasTime;
763
+ this.#offset = offset;
764
+ }
765
+ }
766
+ isDateTime() {
767
+ return this.#hasDate && this.#hasTime;
768
+ }
769
+ isLocal() {
770
+ return !this.#hasDate || !this.#hasTime || !this.#offset;
771
+ }
772
+ isDate() {
773
+ return this.#hasDate && !this.#hasTime;
774
+ }
775
+ isTime() {
776
+ return this.#hasTime && !this.#hasDate;
777
+ }
778
+ isValid() {
779
+ return this.#hasDate || this.#hasTime;
780
+ }
781
+ toISOString() {
782
+ let iso = super.toISOString();
783
+ // Local Date
784
+ if (this.isDate())
785
+ return iso.slice(0, 10);
786
+ // Local Time
787
+ if (this.isTime())
788
+ return iso.slice(11, 23);
789
+ // Local DateTime
790
+ if (this.#offset === null)
791
+ return iso.slice(0, -1);
792
+ // Offset DateTime
793
+ if (this.#offset === 'Z' || this.#offset === 'z')
794
+ return iso;
795
+ // This part is quite annoying: JS strips the original timezone from the ISO string representation
796
+ // Instead of using a "modified" date and "Z", we restore the representation "as authored"
797
+ let offset = (+(this.#offset.slice(1, 3)) * 60) + +(this.#offset.slice(4, 6));
798
+ offset = this.#offset[0] === '-' ? offset : -offset;
799
+ let offsetDate = new Date(this.getTime() - (offset * 60e3));
800
+ return offsetDate.toISOString().slice(0, -1) + this.#offset;
801
+ }
802
+ static wrapAsOffsetDateTime(jsDate, offset = 'Z') {
803
+ let date = new TomlDate(jsDate);
804
+ date.#offset = offset;
805
+ return date;
806
+ }
807
+ static wrapAsLocalDateTime(jsDate) {
808
+ let date = new TomlDate(jsDate);
809
+ date.#offset = null;
810
+ return date;
811
+ }
812
+ static wrapAsLocalDate(jsDate) {
813
+ let date = new TomlDate(jsDate);
814
+ date.#hasTime = false;
815
+ date.#offset = null;
816
+ return date;
817
+ }
818
+ static wrapAsLocalTime(jsDate) {
819
+ let date = new TomlDate(jsDate);
820
+ date.#hasDate = false;
821
+ date.#offset = null;
822
+ return date;
960
823
  }
961
- const date = new TomlDate(value);
962
- if (!date.isValid())
963
- throw new TomlError('invalid value', err);
964
- return date;
965
824
  }
966
825
 
967
826
  /*!
@@ -991,21 +850,29 @@ function parseValue(ctx, integersAsBigInt, end) {
991
850
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
992
851
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
993
852
  */
853
+ function isDigit(char, base = 10) {
854
+ return base === 16
855
+ ? (char > 0x2f && char < 0x3a) || (char > 0x40 && char < 0x47) || (char > 0x60 && char < 0x67)
856
+ : (char > 0x2f && char < 0x30 + base);
857
+ }
858
+ function isEndOfValue(char, delim) {
859
+ // Whitespace -- we're permissive on `\r` for performance; it'll be dealt with later anyway
860
+ return char === 0x20 || char === 0x9 /* \t */ || char === 0xa /* \n */ || char === 0xd /* \r */ ||
861
+ // Structure end or next value delimiter
862
+ (delim && (char === delim || char === 0x2c /* , */)) ||
863
+ // Comment
864
+ char === 0x23; /* # */
865
+ }
994
866
  /** @internal */
995
- function extractValue(ctx, end, integersAsBigInt) {
996
- let ptr = ctx.p;
997
- let c = ctx.s.charCodeAt(ptr);
867
+ function extractValue(ctx, end) {
868
+ let errPtr = ctx.p;
869
+ let c = ctx.s.charCodeAt(ctx.p);
998
870
  // Structs
999
871
  if (c === 0x5b /* [ */ || c === 0x7b /* { */) {
1000
- if (!ctx.d--) {
1001
- throw new TomlError('document contains excessively nested structures. aborting.', {
1002
- toml: ctx.s,
1003
- ptr,
1004
- });
1005
- }
872
+ ctx.d-- || TomlError.x('document contains excessively nested structures. aborting.', ctx);
1006
873
  let value = c === 0x5b /* [ */
1007
- ? parseArray(ctx, integersAsBigInt)
1008
- : parseInlineTable(ctx, integersAsBigInt);
874
+ ? parseArray(ctx)
875
+ : parseInlineTable(ctx);
1009
876
  ctx.d++;
1010
877
  return value;
1011
878
  }
@@ -1017,18 +884,229 @@ function extractValue(ctx, end, integersAsBigInt) {
1017
884
  // We can fast-path because the first character is enough to know the only possible value
1018
885
  if (c === 0x74 /* t */) { // Only possible valid value is `true`
1019
886
  if (ctx.s.charCodeAt(++ctx.p) !== 0x72 || ctx.s.charCodeAt(++ctx.p) !== 0x75 || ctx.s.charCodeAt(++ctx.p) !== 0x65)
1020
- throw new TomlError('invalid value', { toml: ctx.s, ptr });
1021
- ctx.p++;
1022
- return true;
887
+ TomlError.x('invalid value', ctx, errPtr);
888
+ return ctx.p++, true;
1023
889
  }
1024
890
  if (c === 0x66 /* f */) { // Only possible valid value is `false`
1025
891
  if (ctx.s.charCodeAt(++ctx.p) !== 0x61 || ctx.s.charCodeAt(++ctx.p) !== 0x6c || ctx.s.charCodeAt(++ctx.p) !== 0x73 || ctx.s.charCodeAt(++ctx.p) !== 0x65)
1026
- throw new TomlError('invalid value', { toml: ctx.s, ptr });
1027
- ctx.p++;
1028
- return false;
892
+ TomlError.x('invalid value', ctx, errPtr);
893
+ return ctx.p++, false;
894
+ }
895
+ if (c === 0x2b /* + */ || c === 0x2d /* - */) {
896
+ return parseNumber(ctx, ctx.p, ctx.s.charCodeAt(++ctx.p), 0x2c - c, end);
897
+ }
898
+ // Rough heuristic, but `parseDate` falls back to number parsing if it's a false positive.
899
+ // e.g.: `a = 1\n-------------- = 1` would match this heuristic, but gracefully fallback to numbers.
900
+ if (ctx.s.charCodeAt(ctx.p + 4) === 0x2d /* - */ && ctx.s.charCodeAt(ctx.p + 7) === 0x2d /* - */) {
901
+ return parseDate(ctx, c, end);
902
+ }
903
+ // Same logic as above; `parseTime` falls back to number parsing if it's a false positive.
904
+ if (ctx.s.charCodeAt(ctx.p + 2) === 0x3a /* : */) {
905
+ return parseTime(ctx, c, end);
906
+ }
907
+ return parseNumber(ctx, ctx.p, c, 0, end);
908
+ }
909
+ // State:
910
+ // 0: init
911
+ // 1: integer
912
+ // 2: fractional dot
913
+ // 3: fractional
914
+ // 4: exponent letter
915
+ // 5: exponent
916
+ // >9: underscore; see below
917
+ // -> 12: was integer
918
+ // -> 14: was fractional
919
+ // -> 16: was exponent
920
+ // states 2-5, 14, 16 are ONLY permitted iif base === 10
921
+ function parseNumber(ctx, startPtr, startChr, sign, endChr) {
922
+ let c = startChr;
923
+ let state = 0;
924
+ let hasUnderscores = false;
925
+ // (+/-)inf
926
+ if (c === 0x69 /* i */) {
927
+ if (ctx.s.charCodeAt(++ctx.p) !== 0x6e || ctx.s.charCodeAt(++ctx.p) !== 0x66)
928
+ TomlError.x('invalid value', ctx, startPtr);
929
+ return ctx.p++, (sign || 1) / 0;
930
+ }
931
+ // (+/-)nan
932
+ if (c === 0x6e /* n */) {
933
+ if (ctx.s.charCodeAt(++ctx.p) !== 0x61 || ctx.s.charCodeAt(++ctx.p) !== 0x6e)
934
+ TomlError.x('invalid value', ctx, startPtr);
935
+ return ctx.p++, NaN;
936
+ }
937
+ // Leading zero
938
+ // Only allowed cases: `0<EOV>`, `0.(...)`, `0e(...)`, `0x(...)`, `0b(...)`, `0o(...)`
939
+ // FWIW, `0e(...)` is a stupid case, but it's not banned per-se so we have to parse it
940
+ if (c === 0x30 /* 0 */) {
941
+ if (++ctx.p >= ctx.s.length || isEndOfValue(c = ctx.s.charCodeAt(ctx.p), endChr))
942
+ return ctx.bi === true ? 0n : 0; // note: conveniently deals with `-0`
943
+ if (!sign) {
944
+ if (c === 0x78 /* x */)
945
+ return parseIntegerBaseN(ctx, startPtr, 16, endChr);
946
+ else if (c === 0x62 /* b */)
947
+ return parseIntegerBaseN(ctx, startPtr, 2, endChr);
948
+ else if (c === 0x6f /* o */)
949
+ return parseIntegerBaseN(ctx, startPtr, 8, endChr);
950
+ }
951
+ if (c === 0x2e /* . */)
952
+ state = 2;
953
+ else if (c === 0x65 /* e */ || c === 0x45 /* E */)
954
+ state = 4;
955
+ else
956
+ TomlError.x('illegal leading zero', ctx, startPtr);
957
+ }
958
+ // If the 1st char is not a digit by now, then it's not a valid TOML value at all
959
+ else if (!isDigit(c))
960
+ TomlError.x('invalid value', ctx, startPtr);
961
+ while (++ctx.p < ctx.s.length && (c = ctx.s.charCodeAt(ctx.p), !isEndOfValue(c, endChr))) {
962
+ if (!state)
963
+ state = 1; // Detects single-digit numbers we can use a fast parse path for
964
+ // The way the states are numbered is not random: underscores are always permitted in odd-numbered states and
965
+ // never permitted in even-numbered ones.
966
+ if (c === 0x5f /* _ */) {
967
+ if (!(state & 1))
968
+ TomlError.x('illegal underscore', ctx);
969
+ state += 11; // 11 is a marker that makes the state even and greater than 9; see the numbering + rationale above
970
+ hasUnderscores = true;
971
+ }
972
+ // Transition to fractional part.
973
+ else if (state === 1 && c === 0x2e /* . */)
974
+ state = 2;
975
+ // Transition to exponent part.
976
+ else if ((state === 1 || state === 3) && (c === 0x65 /* e */ || c === 0x45 /* E */))
977
+ state = 4;
978
+ // + and - are permitted in state 4 only (handled before entering the function for state 0)
979
+ else if (state === 4 && (c === 0x2b /* + */ || c === 0x2d /* - */)) ;
980
+ // All special cases have been handled; only digits are allowed here
981
+ else if (!isDigit(c))
982
+ TomlError.x(`illegal character in numeric literal`, ctx);
983
+ // Clear state flags
984
+ else if (state > 9)
985
+ state -= 11;
986
+ else if (!(state & 1))
987
+ state++;
988
+ }
989
+ // Single-char number; we can fast-path these very easily.
990
+ if (!state) {
991
+ let val = (startChr - 0x30 /* 0 */) * (sign || 1);
992
+ return ctx.bi === true ? BigInt(val) : val;
993
+ }
994
+ // Even-numbered states absolutely require a digit next; not even end of value is permitted
995
+ if (!(state & 1))
996
+ TomlError.x('unfinished numeric value', ctx, startPtr);
997
+ let str = ctx.s.slice(startPtr, ctx.p);
998
+ if (hasUnderscores)
999
+ str = str.replaceAll('_', ''); // perf: replaceAll 1.25x faster than replace with a regex
1000
+ return state > 1
1001
+ ? parseFloat(str)
1002
+ : parseInteger(ctx, str, 10, startPtr);
1003
+ }
1004
+ function parseIntegerBaseN(ctx, startPtr, base, endChr) {
1005
+ let c, underscore = 1;
1006
+ while (++ctx.p < ctx.s.length && (c = ctx.s.charCodeAt(ctx.p), !isEndOfValue(c, endChr))) {
1007
+ if (c === 0x5f /* _ */) {
1008
+ if (underscore & 1)
1009
+ TomlError.x('illegal underscore', ctx);
1010
+ underscore = 3;
1011
+ }
1012
+ // We only need to check if the number is a valid digit, nothing else is permitted
1013
+ else if (!isDigit(c, base))
1014
+ TomlError.x(`illegal character in numeric literal`, ctx);
1015
+ // Clear underscore flag
1016
+ else if (underscore & 1)
1017
+ underscore--;
1018
+ }
1019
+ // Trailing underscore is not allowed
1020
+ if (underscore & 1)
1021
+ TomlError.x('unfinished numeric value', ctx);
1022
+ let str = ctx.s.slice(startPtr + 2, ctx.p);
1023
+ if (underscore)
1024
+ str = str.replaceAll('_', ''); // perf: replaceAll 1.25x faster than replace with a regex
1025
+ return parseInteger(ctx, str, base, startPtr);
1026
+ }
1027
+ function parseInteger(ctx, str, base, startPtr) {
1028
+ if (ctx.bi !== true)
1029
+ int: {
1030
+ let val = parseInt(str, base);
1031
+ if (!Number.isSafeInteger(val)) {
1032
+ if (ctx.bi)
1033
+ break int;
1034
+ TomlError.x('integer value cannot be represented losslessly', ctx, startPtr);
1035
+ }
1036
+ return val;
1037
+ }
1038
+ return base === 10 ? BigInt(str) : BigInt((base === 2 ? '0b' : base === 8 ? '0o' : '0x') + str);
1039
+ }
1040
+ function parseDate(ctx, c, endChr) {
1041
+ let startPtr = ctx.p++, unsafeSeparator;
1042
+ if (!isDigit(c) ||
1043
+ !isDigit(ctx.s.charCodeAt(ctx.p++)) ||
1044
+ !isDigit(ctx.s.charCodeAt(ctx.p++)) ||
1045
+ !isDigit(ctx.s.charCodeAt(ctx.p++))) {
1046
+ return parseNumber(ctx, ctx.p = startPtr, c, 0, endChr);
1047
+ }
1048
+ ctx.p += 5;
1049
+ if (!isDigit(ctx.s.charCodeAt(ctx.p++)))
1050
+ TomlError.x('invalid date-time: date part is malformed', ctx, startPtr);
1051
+ if (ctx.p >= ctx.s.length || (((c = ctx.s.charCodeAt(ctx.p)) !== 0x20 || (unsafeSeparator = true, !isDigit(ctx.s.charCodeAt(ctx.p + 1)))) && c !== 0x54 /* T */ && c !== 0x74 /* t */)) {
1052
+ let t = ctx.s.slice(startPtr, ctx.p);
1053
+ return readDate(ctx, t, 3, false, startPtr);
1054
+ }
1055
+ if (ctx.s.charCodeAt(ctx.p += 3) !== 0x3a /* : */)
1056
+ TomlError.x('invalid date-time: time part is malformed', ctx, startPtr);
1057
+ if (ctx.s.charCodeAt(ctx.p += 3) === 0x3a /* : */)
1058
+ ctx.p += 3;
1059
+ if (ctx.s.charCodeAt(ctx.p) === 0x2e /* . */)
1060
+ while (isDigit(ctx.s.charCodeAt(++ctx.p)))
1061
+ ;
1062
+ if (c = ctx.s.charCodeAt(ctx.p)) {
1063
+ if (c === 0x5a /* Z */ || c === 0x7a /* z */) {
1064
+ let t = ctx.s.slice(startPtr, ++ctx.p);
1065
+ return readDate(ctx, t, 1, unsafeSeparator, startPtr, '[+00:00]');
1066
+ }
1067
+ if (c === 0x2b /* + */ || c === 0x2d /* - */) {
1068
+ // Temporal's ZonedDateTime is weird asf when it comes to dealing with traditional offsets...
1069
+ // It's 1.2x faster to allocate a new string to pass to ZDT than use Instant.toZonedDateTimeISO
1070
+ let t = ctx.s.slice(startPtr, ctx.p += 6);
1071
+ return readDate(ctx, t, 1, unsafeSeparator, startPtr, !ctx.ld && ('[' + ctx.s.slice(ctx.p - 6, ctx.p) + ']'));
1072
+ }
1073
+ }
1074
+ let t = ctx.s.slice(startPtr, ctx.p);
1075
+ return readDate(ctx, t, 2, unsafeSeparator, startPtr);
1076
+ }
1077
+ function parseTime(ctx, c, endChr) {
1078
+ let start = ctx.p;
1079
+ if (!isDigit(c) || !isDigit(ctx.s.charCodeAt(++ctx.p))) {
1080
+ return parseNumber(ctx, --ctx.p, c, 0, endChr);
1081
+ }
1082
+ if (ctx.s.charCodeAt(ctx.p += 4) === 0x3a /* : */)
1083
+ ctx.p += 3;
1084
+ if (ctx.s.charCodeAt(ctx.p) === 0x2e /* . */)
1085
+ while (isDigit(ctx.s.charCodeAt(++ctx.p)))
1086
+ ;
1087
+ let t = ctx.s.slice(start, ctx.p);
1088
+ return readDate(ctx, t, 4, false, start);
1089
+ }
1090
+ function readDate(ctx, str, type, unsafeDelim, errPtr, temporalSuffix) {
1091
+ if (ctx.ld) {
1092
+ let date = new TomlDate(str, type, unsafeDelim);
1093
+ if (!date.isValid())
1094
+ TomlError.x('invalid date', ctx, errPtr);
1095
+ return date;
1096
+ }
1097
+ try {
1098
+ if (temporalSuffix)
1099
+ str += temporalSuffix;
1100
+ switch (type) {
1101
+ case 1: return Temporal.ZonedDateTime.from(str);
1102
+ case 2: return Temporal.PlainDateTime.from(str);
1103
+ case 3: return Temporal.PlainDate.from(str);
1104
+ case 4: return Temporal.PlainTime.from(str);
1105
+ }
1106
+ }
1107
+ catch (e) {
1108
+ TomlError.x(e instanceof Error ? e.message : '' + e, ctx, errPtr);
1029
1109
  }
1030
- // Legacy logic for numbers and dates. Slow and needs to be rewritten.
1031
- return parseValue(ctx, integersAsBigInt, end);
1032
1110
  }
1033
1111
 
1034
1112
  /*!
@@ -1058,83 +1136,124 @@ function extractValue(ctx, end, integersAsBigInt) {
1058
1136
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
1059
1137
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1060
1138
  */
1061
- let KEY_PART_RE = /^[a-zA-Z0-9-_]+[ \t]*$/;
1062
1139
  /** @internal */
1063
- function parseKey(ctx, end = '=') {
1064
- let start = ctx.p;
1065
- let dot = start - 1;
1066
- let parsed = [];
1067
- let endPtr = ctx.s.indexOf(end, start);
1068
- if (endPtr < 0) {
1069
- throw new TomlError('incomplete key-value: cannot find end of key', {
1070
- toml: ctx.s,
1071
- ptr: start,
1072
- });
1140
+ function skipComment$1(ctx) {
1141
+ for (; ctx.p < ctx.s.length; ctx.p++) {
1142
+ let c = ctx.s.charCodeAt(ctx.p);
1143
+ if (c === 0xa /* \n */)
1144
+ break;
1145
+ if (c === 0xd /* \r */ && ctx.s.charCodeAt(ctx.p + 1) === 0xa /* \n */) {
1146
+ ctx.p++;
1147
+ break;
1148
+ }
1149
+ if ((c < 0x20 && c !== 0x9 /* \t */) || c === 0x7f) {
1150
+ TomlError.x('control characters are not allowed in comments', ctx);
1151
+ }
1152
+ }
1153
+ }
1154
+ /** @internal */
1155
+ function skipVoid(ctx, banNewLines, banComments) {
1156
+ let c;
1157
+ while (ctx.p < ctx.s.length) {
1158
+ while (ctx.p < ctx.s.length && ((c = ctx.s.charCodeAt(ctx.p)) === 0x20 ||
1159
+ c === 0x9 /* \t */ ||
1160
+ (!banNewLines &&
1161
+ (c === 0xa /* \n */ || (c === 0xd /* \r */ && ctx.s.charCodeAt(ctx.p + 1) === 0xa /* \n */)))))
1162
+ ctx.p++;
1163
+ if (banComments || c !== 0x23 /* # */)
1164
+ break;
1165
+ skipComment$1(ctx);
1073
1166
  }
1167
+ }
1168
+
1169
+ /*!
1170
+ * Copyright (c) Squirrel Chat et al., All rights reserved.
1171
+ * SPDX-License-Identifier: BSD-3-Clause
1172
+ *
1173
+ * Redistribution and use in source and binary forms, with or without
1174
+ * modification, are permitted provided that the following conditions are met:
1175
+ *
1176
+ * 1. Redistributions of source code must retain the above copyright notice, this
1177
+ * list of conditions and the following disclaimer.
1178
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
1179
+ * this list of conditions and the following disclaimer in the
1180
+ * documentation and/or other materials provided with the distribution.
1181
+ * 3. Neither the name of the copyright holder nor the names of its contributors
1182
+ * may be used to endorse or promote products derived from this software without
1183
+ * specific prior written permission.
1184
+ *
1185
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
1186
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1187
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
1188
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
1189
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1190
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
1191
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
1192
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
1193
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
1194
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1195
+ */
1196
+ /** @internal */
1197
+ function parseKey(ctx, end = 0x3d /* = */) {
1198
+ // States:
1199
+ // 0: before first char
1200
+ // 1: parsing bare key
1201
+ // 2: after key component
1202
+ let startPtr;
1203
+ let state = 0;
1204
+ let parsed = [];
1205
+ let sliceStart;
1206
+ let c = ctx.s.charCodeAt(startPtr = ctx.p);
1074
1207
  do {
1075
- let c = ctx.s.charCodeAt(ctx.p = ++dot);
1076
- // If it's whitespace, ignore
1077
- if (c !== 0x20 && c !== 0x9 /* \t */) {
1078
- // If it's a string
1079
- if (c === 0x22 /* " */ || c === 0x27 /* ' */) {
1080
- if (c === ctx.s.charCodeAt(ctx.p + 1) && c === ctx.s.charCodeAt(ctx.p + 2)) {
1081
- throw new TomlError('multiline strings are not allowed in keys', {
1082
- toml: ctx.s,
1083
- ptr: ctx.p,
1084
- });
1085
- }
1086
- let part = parseString(ctx);
1087
- dot = ctx.s.indexOf('.', ctx.p);
1088
- let strEnd = ctx.s.slice(ctx.p, dot < 0 || dot > endPtr ? endPtr : dot);
1089
- let newLine = indexOfNewline(strEnd);
1090
- if (newLine > -1) {
1091
- throw new TomlError('newlines are not allowed in keys', {
1092
- toml: ctx.s,
1093
- ptr: newLine,
1094
- });
1095
- }
1096
- if (strEnd.trimStart()) {
1097
- throw new TomlError('found extra tokens after the string part', {
1098
- toml: ctx.s,
1099
- ptr: ctx.p,
1100
- });
1101
- }
1102
- if (endPtr < ctx.p) {
1103
- endPtr = ctx.s.indexOf(end, ctx.p);
1104
- if (endPtr < 0) {
1105
- throw new TomlError('incomplete key-value: cannot find end of key', {
1106
- toml: ctx.s,
1107
- ptr: start,
1108
- });
1109
- }
1110
- }
1111
- parsed.push(part);
1112
- }
1113
- else {
1114
- // Normal raw key part consumption and validation
1115
- dot = ctx.s.indexOf('.', ctx.p);
1116
- let part = ctx.s.slice(ctx.p, dot < 0 || dot > endPtr ? endPtr : dot);
1117
- if (!KEY_PART_RE.test(part)) {
1118
- throw new TomlError('only letter, numbers, dashes and underscores are allowed in keys', {
1119
- toml: ctx.s,
1120
- ptr: ctx.p,
1121
- });
1122
- }
1123
- parsed.push(part.trimEnd());
1208
+ // End of key
1209
+ if (c === end) {
1210
+ if (!state)
1211
+ TomlError.x('unexpected end of key', ctx);
1212
+ if (state === 1)
1213
+ parsed.push(ctx.s.slice(sliceStart, ctx.p));
1214
+ return ctx.p++, parsed;
1215
+ }
1216
+ // Dotted key separator
1217
+ else if (c === 0x2e /* . */) {
1218
+ if (!state)
1219
+ TomlError.x('illegal empty bare key', ctx);
1220
+ if (state === 1)
1221
+ parsed.push(ctx.s.slice(sliceStart, ctx.p));
1222
+ state = 0;
1223
+ }
1224
+ // Quoted key
1225
+ else if (!state && (c === 0x22 /* " */ || c === 0x27 /* ' */)) {
1226
+ if (c === ctx.s.charCodeAt(ctx.p + 1) && c === ctx.s.charCodeAt(ctx.p + 2))
1227
+ TomlError.x('illegal quoted key: multiline strings are not allowed', ctx);
1228
+ parsed.push(parseString(ctx));
1229
+ state = 2;
1230
+ ctx.p--;
1231
+ }
1232
+ // Whitespace; no-op in state 0 and 2, end of bare key in state 1
1233
+ else if (c === 0x20 || c === 0x9 /* \t */) {
1234
+ if (state === 1) {
1235
+ parsed.push(ctx.s.slice(sliceStart, ctx.p));
1236
+ state = 2;
1124
1237
  }
1125
1238
  }
1126
- // Until there's no more dot
1127
- } while (dot + 1 && dot < endPtr);
1128
- ctx.p = endPtr + 1;
1129
- skipVoid(ctx, true, true);
1130
- return parsed;
1239
+ // If state is post-key, no character is allowed; otherwise ensure it's a bare-key component
1240
+ else if (state === 2 || (c < 0x30 && c !== 0x2d /* - */) || (c > 0x39 && c < 0x41) || (c > 0x5a && c < 0x61 && c !== 0x5f /* _ */) || c > 0x7a) {
1241
+ TomlError.x('illegal character in key', ctx);
1242
+ }
1243
+ // Illegal character
1244
+ else if (!state) {
1245
+ state = 1;
1246
+ sliceStart = ctx.p;
1247
+ }
1248
+ } while (c = ctx.s.charCodeAt(++ctx.p));
1249
+ TomlError.x('incomplete key-value: cannot find end of key', ctx, startPtr);
1131
1250
  }
1132
1251
  /** @internal */
1133
- function parseInlineTable(ctx, integersAsBigInt) {
1134
- let res = {};
1252
+ function parseInlineTable(ctx) {
1253
+ let startPtr = ctx.p++;
1254
+ let res = Object.create(null);
1135
1255
  let seen = new Set();
1136
1256
  let c;
1137
- ctx.p++;
1138
1257
  while (ctx.p < ctx.s.length) {
1139
1258
  skipVoid(ctx);
1140
1259
  if ((c = ctx.s.charCodeAt(ctx.p)) === 0x7d /* } */) {
@@ -1144,67 +1263,60 @@ function parseInlineTable(ctx, integersAsBigInt) {
1144
1263
  let k;
1145
1264
  let t = res;
1146
1265
  let hasOwn = false;
1147
- let p = ctx.p;
1266
+ let errPtr = ctx.p;
1148
1267
  let key = parseKey(ctx);
1149
1268
  for (let i = 0; i < key.length; i++) {
1150
1269
  if (i)
1151
- t = hasOwn ? t[k] : (t[k] = {});
1270
+ t = hasOwn ? t[k] : (t[k] = Object.create(null));
1152
1271
  k = key[i];
1153
1272
  if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== 'object' || seen.has(t[k]))) {
1154
- throw new TomlError('trying to redefine an already defined value', {
1155
- toml: ctx.s,
1156
- ptr: p,
1157
- });
1273
+ TomlError.x('trying to redefine an already defined value', ctx, errPtr);
1274
+ }
1275
+ let unsafe = k === '__proto__';
1276
+ if (ctx.uk && (unsafe || k === 'constructor')) {
1277
+ t = ctx.uk !== 1 && TomlError.x('document contains an unsafe property', ctx, errPtr);
1278
+ break;
1158
1279
  }
1159
- if (!hasOwn && k === '__proto__') {
1280
+ if (!hasOwn && unsafe) {
1160
1281
  Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
1161
1282
  }
1162
1283
  }
1163
1284
  if (hasOwn) {
1164
- throw new TomlError('trying to redefine an already defined value', {
1165
- toml: ctx.s,
1166
- ptr: ctx.p,
1167
- });
1285
+ TomlError.x('trying to redefine an already defined value', ctx, errPtr);
1168
1286
  }
1169
- let value = extractValue(ctx, 0x7d /* } */, integersAsBigInt);
1170
- seen.add(t[k] = value);
1287
+ skipVoid(ctx, true, true);
1288
+ let value = extractValue(ctx, 0x7d /* } */);
1289
+ if (t && typeof (t[k] = value) === 'object')
1290
+ seen.add(value);
1171
1291
  skipVoid(ctx);
1172
1292
  if ((c = ctx.s.charCodeAt(ctx.p++)) === 0x7d /* } */) {
1173
1293
  return res;
1174
1294
  }
1175
- if (c !== 0x2c /* , */) {
1176
- throw new TomlError('expected comma or end of structure', { toml: ctx.s, ptr: ctx.p - 1 });
1177
- }
1295
+ if (c !== 0x2c /* , */)
1296
+ TomlError.x('expected comma or end of structure', ctx, ctx.p - 1);
1178
1297
  }
1179
- throw new TomlError('unfinished table encountered', {
1180
- toml: ctx.s,
1181
- ptr: ctx.p,
1182
- });
1298
+ TomlError.x('unfinished table', ctx, startPtr);
1183
1299
  }
1184
1300
  /** @internal */
1185
- function parseArray(ctx, integersAsBigInt) {
1301
+ function parseArray(ctx) {
1302
+ let startPtr = ctx.p++;
1186
1303
  let res = [];
1187
1304
  let c;
1188
- ctx.p++;
1189
1305
  while (ctx.p < ctx.s.length) {
1190
1306
  skipVoid(ctx);
1191
1307
  if ((c = ctx.s.charCodeAt(ctx.p)) === 0x5d /* ] */) {
1192
1308
  ctx.p++;
1193
1309
  return res;
1194
1310
  }
1195
- res.push(extractValue(ctx, 0x5d /* ] */, integersAsBigInt));
1311
+ res.push(extractValue(ctx, 0x5d /* ] */));
1196
1312
  skipVoid(ctx);
1197
1313
  if ((c = ctx.s.charCodeAt(ctx.p++)) === 0x5d /* ] */) {
1198
1314
  return res;
1199
1315
  }
1200
- if (c !== 0x2c /* , */) {
1201
- throw new TomlError('expected comma or end of structure', { toml: ctx.s, ptr: ctx.p - 1 });
1202
- }
1316
+ if (c !== 0x2c /* , */)
1317
+ TomlError.x('expected comma or end of structure', ctx, ctx.p - 1);
1203
1318
  }
1204
- throw new TomlError('unfinished array encountered', {
1205
- toml: ctx.s,
1206
- ptr: ctx.p,
1207
- });
1319
+ TomlError.x('unfinished array', ctx, startPtr);
1208
1320
  }
1209
1321
 
1210
1322
  /*!
@@ -1234,7 +1346,7 @@ function parseArray(ctx, integersAsBigInt) {
1234
1346
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
1235
1347
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1236
1348
  */
1237
- function peekTable(key, table, meta, type) {
1349
+ function peekTable(ctx, key, table, meta, type) {
1238
1350
  let t = table;
1239
1351
  let m = meta;
1240
1352
  let k;
@@ -1242,7 +1354,7 @@ function peekTable(key, table, meta, type) {
1242
1354
  let state;
1243
1355
  for (let i = 0; i < key.length; i++) {
1244
1356
  if (i) {
1245
- t = hasOwn ? t[k] : (t[k] = {});
1357
+ t = hasOwn ? t[k] : (t[k] = Object.create(null));
1246
1358
  m = (state = m[k]).c;
1247
1359
  if (type === 0 /* Type.DOTTED */ && (state.t === 1 /* Type.EXPLICIT */ || state.t === 2 /* Type.ARRAY */)) {
1248
1360
  return null;
@@ -1258,7 +1370,10 @@ function peekTable(key, table, meta, type) {
1258
1370
  return null;
1259
1371
  }
1260
1372
  if (!hasOwn) {
1261
- if (k === '__proto__') {
1373
+ let unsafe = k === '__proto__';
1374
+ if (ctx.uk && (unsafe || k === 'constructor'))
1375
+ return false;
1376
+ if (unsafe) {
1262
1377
  Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
1263
1378
  Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
1264
1379
  }
@@ -1267,7 +1382,7 @@ function peekTable(key, table, meta, type) {
1267
1382
  ? 3 /* Type.ARRAY_DOTTED */ : type,
1268
1383
  d: false,
1269
1384
  i: 0,
1270
- c: {},
1385
+ c: Object.create(null),
1271
1386
  };
1272
1387
  }
1273
1388
  }
@@ -1281,8 +1396,8 @@ function peekTable(key, table, meta, type) {
1281
1396
  state.d = true;
1282
1397
  t[k] = [];
1283
1398
  }
1284
- t[k].push(t = {});
1285
- state.c[state.i++] = (state = { t: 1 /* Type.EXPLICIT */, d: false, i: 0, c: {} });
1399
+ t[k].push(t = Object.create(null));
1400
+ state.c[state.i++] = (state = { t: 1 /* Type.EXPLICIT */, d: false, i: 0, c: Object.create(null) });
1286
1401
  }
1287
1402
  if (state.d) {
1288
1403
  // Redefining a table!
@@ -1290,63 +1405,75 @@ function peekTable(key, table, meta, type) {
1290
1405
  }
1291
1406
  state.d = true;
1292
1407
  if (type === 1 /* Type.EXPLICIT */) {
1293
- t = hasOwn ? t[k] : (t[k] = {});
1408
+ t = hasOwn ? t[k] : (t[k] = Object.create(null));
1294
1409
  }
1295
1410
  else if (type === 0 /* Type.DOTTED */ && hasOwn) {
1296
1411
  return null;
1297
1412
  }
1298
1413
  return [k, t, state.c];
1299
1414
  }
1300
- function parse$4(toml, { maxDepth = 1000, integersAsBigInt } = {}) {
1301
- let ctx = { s: toml, p: 0, d: maxDepth };
1302
- let res = {};
1303
- let meta = {};
1415
+ function validateTablePeek(ctx, peek, ptr) {
1416
+ if (peek === null || ctx.uk === 2)
1417
+ TomlError.x(peek === null
1418
+ ? 'trying to redefine an already defined table or value'
1419
+ : 'document contains an unsafe property', ctx, ptr);
1420
+ }
1421
+ function parse$4(toml, options = {}) {
1422
+ let ctx = {
1423
+ s: toml,
1424
+ p: 0,
1425
+ d: options.maxDepth ?? 1000,
1426
+ bi: options.integersAsBigInt ?? false,
1427
+ ld: options.useLegacyDate ?? true,
1428
+ uk: options.unsafeKeyBehaviour === 'throw' ? 2 : options.unsafeKeyBehaviour === 'drop' ? 1 : 0,
1429
+ };
1430
+ let res = Object.create(null);
1431
+ let meta = Object.create(null);
1304
1432
  let tmp;
1433
+ let skipping = false;
1305
1434
  let tbl = res;
1306
1435
  let m = meta;
1436
+ // BOM is allowed, skip.
1437
+ // JS is UTF-16, so we have to check for the UTF-16 BOM instead of the UTF-8 BOM sequence!
1438
+ if (toml.charCodeAt(0) === 0xfeff)
1439
+ ctx.p++;
1307
1440
  skipVoid(ctx);
1308
1441
  while (ctx.p < toml.length) {
1309
1442
  if (toml.charCodeAt(ctx.p) === 0x5b /* [ */) {
1310
1443
  let isTableArray = toml.charCodeAt(++ctx.p) === 0x5b; /* [ */
1311
1444
  tmp = ctx.p += +isTableArray;
1312
- let k = parseKey(ctx, ']');
1445
+ skipping = false;
1446
+ let k = parseKey(ctx, 0x5d /* ] */);
1313
1447
  if (isTableArray) {
1314
- if (toml.charCodeAt(ctx.p - 1) !== 0x5d /* ] */) {
1315
- throw new TomlError('expected end of table declaration', {
1316
- toml: toml,
1317
- ptr: ctx.p - 1,
1318
- });
1448
+ if (toml.charCodeAt(ctx.p) !== 0x5d /* ] */) {
1449
+ TomlError.x('expected end of table array declaration', ctx);
1319
1450
  }
1320
1451
  ctx.p++;
1321
1452
  }
1322
- let p = peekTable(k, res, meta, isTableArray ? 2 /* Type.ARRAY */ : 1 /* Type.EXPLICIT */);
1453
+ let p = peekTable(ctx, k, res, meta, isTableArray ? 2 /* Type.ARRAY */ : 1 /* Type.EXPLICIT */);
1323
1454
  if (!p) {
1324
- throw new TomlError('trying to redefine an already defined table or value', {
1325
- toml: toml,
1326
- ptr: tmp,
1327
- });
1455
+ validateTablePeek(ctx, p, tmp);
1456
+ skipping = true;
1457
+ }
1458
+ else {
1459
+ m = p[2];
1460
+ tbl = p[1];
1328
1461
  }
1329
- m = p[2];
1330
- tbl = p[1];
1331
1462
  }
1332
1463
  else {
1333
1464
  tmp = ctx.p;
1334
1465
  let k = parseKey(ctx);
1335
- let p = peekTable(k, tbl, m, 0 /* Type.DOTTED */);
1336
- if (!p) {
1337
- throw new TomlError('trying to redefine an already defined table or value', {
1338
- toml: toml,
1339
- ptr: tmp,
1340
- });
1341
- }
1342
- p[1][p[0]] = extractValue(ctx, void 0, integersAsBigInt);
1466
+ let p = peekTable(ctx, k, tbl, m, 0 /* Type.DOTTED */);
1467
+ if (!p && !skipping)
1468
+ validateTablePeek(ctx, p, tmp);
1469
+ skipVoid(ctx, true, true);
1470
+ let v = extractValue(ctx, void 0);
1471
+ if (p && !skipping)
1472
+ p[1][p[0]] = v;
1343
1473
  }
1344
1474
  skipVoid(ctx, true);
1345
- if (ctx.p < toml.length && (tmp = toml.charCodeAt(ctx.p)) !== 0xa /* \n */ && tmp !== 0xd /* \r */) {
1346
- throw new TomlError('each key-value declaration must be followed by an end-of-line', {
1347
- toml: toml,
1348
- ptr: ctx.p,
1349
- });
1475
+ if (ctx.p < toml.length && (tmp = toml.charCodeAt(ctx.p)) !== 0xa /* \n */ && (tmp !== 0xd /* \r */ || toml.charCodeAt(ctx.p + 1) !== 0xa /* \n */)) {
1476
+ TomlError.x('each key-value declaration must be followed by an end-of-line', ctx);
1350
1477
  }
1351
1478
  skipVoid(ctx);
1352
1479
  }
@@ -1381,22 +1508,29 @@ function parse$4(toml, { maxDepth = 1000, integersAsBigInt } = {}) {
1381
1508
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1382
1509
  */
1383
1510
  let BARE_KEY = /^[a-z0-9-_]+$/i;
1511
+ let HAS_WELLFORMED = !!(''.isWellFormed);
1384
1512
  function extendedTypeOf(obj) {
1385
1513
  let type = typeof obj;
1386
1514
  if (type === 'object') {
1387
1515
  if (Array.isArray(obj))
1388
1516
  return 'array';
1389
- if (typeof obj?.getUTCDate === 'function' && obj instanceof Date)
1517
+ if (typeof obj.getUTCDate === 'function' && obj instanceof Date)
1390
1518
  return 'date';
1391
- if (globalThis.Temporal &&
1392
- // check for the 'since' property as an early bailout that avoids running all 5 instanceof checks
1393
- typeof obj?.since === 'function' &&
1394
- (obj instanceof Temporal.Instant ||
1395
- obj instanceof Temporal.PlainDate ||
1396
- obj instanceof Temporal.PlainDateTime ||
1397
- obj instanceof Temporal.PlainTime ||
1398
- obj instanceof Temporal.ZonedDateTime)) {
1399
- return 'temporal';
1519
+ if (globalThis.Temporal) {
1520
+ if (obj.until) {
1521
+ if (obj instanceof Temporal.ZonedDateTime)
1522
+ return 'temporal/tz+uc';
1523
+ if (obj instanceof Temporal.PlainDateTime || obj instanceof Temporal.PlainDate)
1524
+ return 'temporal/uc';
1525
+ if (obj instanceof Temporal.PlainTime || obj instanceof Temporal.Instant)
1526
+ return 'temporal';
1527
+ if (obj instanceof Temporal.PlainYearMonth)
1528
+ return 'temporal/x';
1529
+ }
1530
+ else if ((obj.toPlainDate && obj instanceof Temporal.PlainMonthDay) ||
1531
+ (obj.negated && obj instanceof Temporal.Duration)) {
1532
+ return 'temporal/x';
1533
+ }
1400
1534
  }
1401
1535
  }
1402
1536
  return type;
@@ -1408,16 +1542,20 @@ function isArrayOfTables(obj) {
1408
1542
  }
1409
1543
  return obj.length != 0;
1410
1544
  }
1545
+ function formatWellFormedStringUnchecked(s) {
1546
+ return JSON.stringify(s).replaceAll('\x7f', '\\u007f');
1547
+ }
1411
1548
  function formatString(s) {
1412
- return JSON.stringify(s).replace(/\x7f/g, '\\u007f');
1549
+ return formatWellFormedStringUnchecked(HAS_WELLFORMED ? s.toWellFormed() : s);
1413
1550
  }
1414
- function stringifyTemporal(temporal) {
1415
- return temporal.toString({
1416
- calendarName: 'never',
1417
- timeZoneName: 'never',
1418
- });
1551
+ function formatKey(s) {
1552
+ if (BARE_KEY.test(s))
1553
+ return s;
1554
+ if (HAS_WELLFORMED && !s.isWellFormed())
1555
+ throw new RangeError('key contains illegal lone surrogates');
1556
+ return formatWellFormedStringUnchecked(s);
1419
1557
  }
1420
- function stringifyValue(val, type, depth, numberAsFloat) {
1558
+ function stringifyValue(val, type, depth, numberAsFloat, strictTemporal) {
1421
1559
  if (depth === 0) {
1422
1560
  throw new Error('Could not stringify the object: maximum object depth exceeded');
1423
1561
  }
@@ -1434,6 +1572,7 @@ function stringifyValue(val, type, depth, numberAsFloat) {
1434
1572
  return val.toFixed(1);
1435
1573
  case 'bigint':
1436
1574
  case 'boolean':
1575
+ case 'temporal':
1437
1576
  return val.toString();
1438
1577
  case 'string':
1439
1578
  return formatString(val);
@@ -1442,14 +1581,42 @@ function stringifyValue(val, type, depth, numberAsFloat) {
1442
1581
  throw new TypeError('cannot serialize invalid date');
1443
1582
  return val.toISOString();
1444
1583
  case 'object':
1445
- return stringifyInlineTable(val, depth, numberAsFloat);
1584
+ return stringifyInlineTable(val, depth, numberAsFloat, strictTemporal);
1446
1585
  case 'array':
1447
- return stringifyArray(val, depth, numberAsFloat);
1448
- case 'temporal':
1449
- return stringifyTemporal(val);
1586
+ return stringifyArray(val, depth, numberAsFloat, strictTemporal);
1587
+ // @ts-expect-error -- intentional fallthrough case
1588
+ case 'temporal/tz+uc':
1589
+ if (strictTemporal) {
1590
+ let tz = val.timeZoneId;
1591
+ let tzc = tz.charCodeAt(0);
1592
+ if (
1593
+ // Classic offset
1594
+ tzc !== 0x2b /* + */ && tzc !== 0x2d /* - */ &&
1595
+ (
1596
+ // Fast pre-check pass; see below for the actually accepted values
1597
+ (tzc !== 0x55 /* U */ && tzc !== 0x47 /* G */ && tzc !== 0x5a /* Z */ && tzc !== 0x45 /* E */) ||
1598
+ (
1599
+ // UTC and its aliases; Temporal implementations don't all canonicalise unfortunately
1600
+ tz !== 'UTC' && tz !== 'UCT' && tz !== 'Universal' && tz !== 'Zulu' &&
1601
+ // GMT is a TZ but it's for all intents and purposes equivalent to UTC. Safe to downgrade.
1602
+ !tz.startsWith('GMT') && tz !== 'Greenwich' &&
1603
+ // Etc/* are all safe to downgrade to offset (either UTC, GMT, or offset)
1604
+ !tz.startsWith('Etc/')))) {
1605
+ throw new TypeError('Temporal objects with an IANA timezone are not allowed in Temporal strict mode');
1606
+ }
1607
+ }
1608
+ case 'temporal/uc':
1609
+ if (strictTemporal && val.calendarId !== 'iso8601')
1610
+ throw new TypeError('Temporal objects with a non-default calendar are not allowed in Temporal strict mode');
1611
+ return val.toString({
1612
+ calendarName: 'never',
1613
+ timeZoneName: 'never',
1614
+ });
1615
+ case 'temporal/x':
1616
+ throw new TypeError('Unsupported ' + val[Symbol.toStringTag]);
1450
1617
  }
1451
1618
  }
1452
- function stringifyInlineTable(obj, depth, numberAsFloat) {
1619
+ function stringifyInlineTable(obj, depth, numberAsFloat, strictTemporal) {
1453
1620
  let keys = Object.keys(obj);
1454
1621
  if (keys.length === 0)
1455
1622
  return '{}';
@@ -1458,13 +1625,11 @@ function stringifyInlineTable(obj, depth, numberAsFloat) {
1458
1625
  let k = keys[i];
1459
1626
  if (i)
1460
1627
  res += ', ';
1461
- res += BARE_KEY.test(k) ? k : formatString(k);
1462
- res += ' = ';
1463
- res += stringifyValue(obj[k], extendedTypeOf(obj[k]), depth - 1, numberAsFloat);
1628
+ res += formatKey(k) + ' = ' + stringifyValue(obj[k], extendedTypeOf(obj[k]), depth - 1, numberAsFloat, strictTemporal);
1464
1629
  }
1465
1630
  return res + ' }';
1466
1631
  }
1467
- function stringifyArray(array, depth, numberAsFloat) {
1632
+ function stringifyArray(array, depth, numberAsFloat, strictTemporal) {
1468
1633
  if (array.length === 0)
1469
1634
  return '[]';
1470
1635
  let res = '[ ';
@@ -1474,22 +1639,22 @@ function stringifyArray(array, depth, numberAsFloat) {
1474
1639
  if (array[i] === null || array[i] === void 0) {
1475
1640
  throw new TypeError('arrays cannot contain null or undefined values');
1476
1641
  }
1477
- res += stringifyValue(array[i], extendedTypeOf(array[i]), depth - 1, numberAsFloat);
1642
+ res += stringifyValue(array[i], extendedTypeOf(array[i]), depth - 1, numberAsFloat, strictTemporal);
1478
1643
  }
1479
1644
  return res + ' ]';
1480
1645
  }
1481
- function stringifyArrayTable(array, key, depth, numberAsFloat) {
1646
+ function stringifyArrayTable(array, key, depth, numberAsFloat, strictTemporal) {
1482
1647
  if (depth === 0) {
1483
1648
  throw new Error('Could not stringify the object: maximum object depth exceeded');
1484
1649
  }
1485
1650
  let res = '';
1486
1651
  for (let i = 0; i < array.length; i++) {
1487
1652
  res += `${res && '\n'}[[${key}]]\n`;
1488
- res += stringifyTable(0, array[i], key, depth, numberAsFloat);
1653
+ res += stringifyTable(0, array[i], key, depth, numberAsFloat, strictTemporal);
1489
1654
  }
1490
1655
  return res;
1491
1656
  }
1492
- function stringifyTable(tableKey, obj, prefix, depth, numberAsFloat) {
1657
+ function stringifyTable(tableKey, obj, prefix, depth, numberAsFloat, strictTemporal) {
1493
1658
  if (depth === 0) {
1494
1659
  throw new Error('Could not stringify the object: maximum object depth exceeded');
1495
1660
  }
@@ -1503,18 +1668,18 @@ function stringifyTable(tableKey, obj, prefix, depth, numberAsFloat) {
1503
1668
  if (type === 'symbol' || type === 'function') {
1504
1669
  throw new TypeError(`cannot serialize values of type '${type}'`);
1505
1670
  }
1506
- let key = BARE_KEY.test(k) ? k : formatString(k);
1671
+ let key = formatKey(k);
1507
1672
  if (type === 'array' && isArrayOfTables(obj[k])) {
1508
- tables += (tables && '\n') + stringifyArrayTable(obj[k], prefix ? `${prefix}.${key}` : key, depth - 1, numberAsFloat);
1673
+ tables += (tables && '\n') + stringifyArrayTable(obj[k], prefix ? `${prefix}.${key}` : key, depth - 1, numberAsFloat, strictTemporal);
1509
1674
  }
1510
1675
  else if (type === 'object') {
1511
1676
  let tblKey = prefix ? `${prefix}.${key}` : key;
1512
- tables += (tables && '\n') + stringifyTable(tblKey, obj[k], tblKey, depth - 1, numberAsFloat);
1677
+ tables += (tables && '\n') + stringifyTable(tblKey, obj[k], tblKey, depth - 1, numberAsFloat, strictTemporal);
1513
1678
  }
1514
1679
  else {
1515
1680
  preamble += key;
1516
1681
  preamble += ' = ';
1517
- preamble += stringifyValue(obj[k], type, depth, numberAsFloat);
1682
+ preamble += stringifyValue(obj[k], type, depth, numberAsFloat, strictTemporal);
1518
1683
  preamble += '\n';
1519
1684
  }
1520
1685
  }
@@ -1525,11 +1690,11 @@ function stringifyTable(tableKey, obj, prefix, depth, numberAsFloat) {
1525
1690
  ? `${preamble}\n${tables}`
1526
1691
  : preamble || tables;
1527
1692
  }
1528
- function stringify$1(obj, { maxDepth = 1000, numbersAsFloat = false } = {}) {
1693
+ function stringify$1(obj, { maxDepth = 1000, numbersAsFloat = false, strictTemporal = false } = {}) {
1529
1694
  if (extendedTypeOf(obj) !== 'object') {
1530
1695
  throw new TypeError('stringify can only be called with an object');
1531
1696
  }
1532
- let str = stringifyTable(0, obj, '', maxDepth, numbersAsFloat);
1697
+ let str = stringifyTable(0, obj, '', maxDepth, numbersAsFloat, strictTemporal);
1533
1698
  if (str[str.length - 1] !== '\n')
1534
1699
  return str + '\n';
1535
1700
  return str;
@@ -21231,6 +21396,30 @@ function locateRootValue(source) {
21231
21396
  }
21232
21397
  return null;
21233
21398
  }
21399
+ /**
21400
+ * A parsed TOML value with every table rebuilt as a plain object, so a deep comparison is about the
21401
+ * values and not about which prototype a table carries. smol-toml >= 1.9 parses every table, the document
21402
+ * root included (and inline tables), to a null-prototype object, and `isDeepStrictEqual` treats a null
21403
+ * prototype as different from `Object.prototype`, while the spread `{ ...original, repos_root }` that
21404
+ * `editReposRoot` compares against is plain at its top level: on 1.9 every edit that reached the
21405
+ * comparison failed it, whatever the document held. An array maps each element; an object whose prototype
21406
+ * is `null` or `Object.prototype` becomes a fresh plain object with each value mapped the same way;
21407
+ * anything else (a string, number, boolean, `null`, a `Date` or smol-toml's `TomlDate`, an object of any
21408
+ * other class) is returned as it is. So the comparison ignores prototypes and compares everything else as
21409
+ * before, except that a rebuilt table keeps only its own enumerable string keys (a TOML document has no
21410
+ * others). The input is not mutated.
21411
+ */
21412
+ function withPlainPrototypes(value) {
21413
+ if (Array.isArray(value))
21414
+ return value.map((element) => withPlainPrototypes(element));
21415
+ if (typeof value === "object" && value !== null) {
21416
+ const prototype = Object.getPrototypeOf(value);
21417
+ if (prototype === null || prototype === Object.prototype) {
21418
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, withPlainPrototypes(entry)]));
21419
+ }
21420
+ }
21421
+ return value;
21422
+ }
21234
21423
  /**
21235
21424
  * Edit only the top-level string `repos_root` assignment while preserving every unrelated byte.
21236
21425
  * `missingValue` is the unoverlaid value a missing key means to `loadConfig`; when it already equals
@@ -21262,7 +21451,7 @@ function editReposRoot(source, next, missingValue, locateValue = locateRootValue
21262
21451
  }
21263
21452
  const reparsed = parseDocument$1(proposed);
21264
21453
  const expected = { ...original, repos_root: next };
21265
- if (!isDeepStrictEqual(reparsed, expected)) {
21454
+ if (!isDeepStrictEqual(withPlainPrototypes(reparsed), withPlainPrototypes(expected))) {
21266
21455
  fail$1("proposed edit changed more than the requested value");
21267
21456
  }
21268
21457
  return proposed;
@@ -74285,8 +74474,8 @@ function parse(src, reviver, options) {
74285
74474
  * tunnel, capability `agent.tunnel.lint`. The design decision (recorded in the registry): the lint runs
74286
74475
  * as an agent verb, because the console is a container that cannot read the tunnel host's config (the
74287
74476
  * issue's own finding) and has no Tooling clone (roshne, 2026-09-16, recorded at
74288
- * `admin-cards.test.ts`); the console is meant to ask each paired machine through `host.agent.call`
74289
- * (PR 2).
74477
+ * `admin-cards.test.ts`); the console asks each paired machine through `host.agent.call` (#416 PR 2,
74478
+ * `plugins/admin/src/tunnel-audit.ts`).
74290
74479
  *
74291
74480
  * The verb takes NO request input: which file is read and which script is run come only from
74292
74481
  * `agent.toml` / the `AC_AGENT_TUNNEL_*` env (`config.ts`), set by the machine's own operator. Two