Index: frontend/node_modules/yaml/LICENSE
===================================================================
--- frontend/node_modules/yaml/LICENSE	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/LICENSE	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,13 @@
+Copyright 2018 Eemeli Aro <eemeli@gmail.com>
+
+Permission to use, copy, modify, and/or distribute this software for any purpose
+with or without fee is hereby granted, provided that the above copyright notice
+and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+THIS SOFTWARE.
Index: frontend/node_modules/yaml/README.md
===================================================================
--- frontend/node_modules/yaml/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/README.md	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,127 @@
+# YAML <a href="https://www.npmjs.com/package/yaml"><img align="right" src="https://badge.fury.io/js/yaml.svg" title="npm package" /></a>
+
+`yaml` is a JavaScript parser and stringifier for [YAML](http://yaml.org/), a human friendly data serialization standard. It supports both parsing and stringifying data using all versions of YAML, along with all common data schemas. As a particularly distinguishing feature, `yaml` fully supports reading and writing comments and blank lines in YAML documents.
+
+The library is released under the ISC open source license, and the code is [available on GitHub](https://github.com/eemeli/yaml/). It has no external dependencies and runs on Node.js 6 and later, and in browsers from IE 11 upwards.
+
+For the purposes of versioning, any changes that break any of the endpoints or APIs documented here will be considered semver-major breaking changes. Undocumented library internals may change between minor versions, and previous APIs may be deprecated (but not removed).
+
+For more information, see the project's documentation site: [**eemeli.org/yaml/v1**](https://eemeli.org/yaml/v1/)
+
+To install:
+
+```sh
+npm install yaml
+```
+
+**Note:** This is `yaml@1`. You may also be interested in the next version, currently available as [`yaml@next`](https://www.npmjs.com/package/yaml/v/next).
+
+## API Overview
+
+The API provided by `yaml` has three layers, depending on how deep you need to go: [Parse & Stringify](https://eemeli.org/yaml/v1/#parse-amp-stringify), [Documents](https://eemeli.org/yaml/#documents), and the [CST Parser](https://eemeli.org/yaml/#cst-parser). The first has the simplest API and "just works", the second gets you all the bells and whistles supported by the library along with a decent [AST](https://eemeli.org/yaml/#content-nodes), and the third is the closest to YAML source, making it fast, raw, and crude.
+
+```js
+import YAML from 'yaml'
+// or
+const YAML = require('yaml')
+```
+
+### Parse & Stringify
+
+- [`YAML.parse(str, options): value`](https://eemeli.org/yaml/v1/#yaml-parse)
+- [`YAML.stringify(value, options): string`](https://eemeli.org/yaml/v1/#yaml-stringify)
+
+### YAML Documents
+
+- [`YAML.createNode(value, wrapScalars, tag): Node`](https://eemeli.org/yaml/v1/#creating-nodes)
+- [`YAML.defaultOptions`](https://eemeli.org/yaml/v1/#options)
+- [`YAML.Document`](https://eemeli.org/yaml/v1/#yaml-documents)
+  - [`constructor(options)`](https://eemeli.org/yaml/v1/#creating-documents)
+  - [`defaults`](https://eemeli.org/yaml/v1/#options)
+  - [`#anchors`](https://eemeli.org/yaml/v1/#working-with-anchors)
+  - [`#contents`](https://eemeli.org/yaml/v1/#content-nodes)
+  - [`#errors`](https://eemeli.org/yaml/v1/#errors)
+- [`YAML.parseAllDocuments(str, options): YAML.Document[]`](https://eemeli.org/yaml/v1/#parsing-documents)
+- [`YAML.parseDocument(str, options): YAML.Document`](https://eemeli.org/yaml/v1/#parsing-documents)
+
+```js
+import { Pair, YAMLMap, YAMLSeq } from 'yaml/types'
+```
+
+- [`new Pair(key, value)`](https://eemeli.org/yaml/v1/#creating-nodes)
+- [`new YAMLMap()`](https://eemeli.org/yaml/v1/#creating-nodes)
+- [`new YAMLSeq()`](https://eemeli.org/yaml/v1/#creating-nodes)
+
+### CST Parser
+
+```js
+import parseCST from 'yaml/parse-cst'
+```
+
+- [`parseCST(str): CSTDocument[]`](https://eemeli.org/yaml/v1/#parsecst)
+- [`YAML.parseCST(str): CSTDocument[]`](https://eemeli.org/yaml/v1/#parsecst)
+
+## YAML.parse
+
+```yaml
+# file.yml
+YAML:
+  - A human-readable data serialization language
+  - https://en.wikipedia.org/wiki/YAML
+yaml:
+  - A complete JavaScript implementation
+  - https://www.npmjs.com/package/yaml
+```
+
+```js
+import fs from 'fs'
+import YAML from 'yaml'
+
+YAML.parse('3.14159')
+// 3.14159
+
+YAML.parse('[ true, false, maybe, null ]\n')
+// [ true, false, 'maybe', null ]
+
+const file = fs.readFileSync('./file.yml', 'utf8')
+YAML.parse(file)
+// { YAML:
+//   [ 'A human-readable data serialization language',
+//     'https://en.wikipedia.org/wiki/YAML' ],
+//   yaml:
+//   [ 'A complete JavaScript implementation',
+//     'https://www.npmjs.com/package/yaml' ] }
+```
+
+## YAML.stringify
+
+```js
+import YAML from 'yaml'
+
+YAML.stringify(3.14159)
+// '3.14159\n'
+
+YAML.stringify([true, false, 'maybe', null])
+// `- true
+// - false
+// - maybe
+// - null
+// `
+
+YAML.stringify({ number: 3, plain: 'string', block: 'two\nlines\n' })
+// `number: 3
+// plain: string
+// block: >
+//   two
+//
+//   lines
+// `
+```
+
+---
+
+Browser testing provided by:
+
+<a href="https://www.browserstack.com/open-source">
+<img width=200 src="https://eemeli.org/yaml/images/browserstack.svg" />
+</a>
Index: frontend/node_modules/yaml/browser/dist/PlainValue-183afbad.js
===================================================================
--- frontend/node_modules/yaml/browser/dist/PlainValue-183afbad.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/dist/PlainValue-183afbad.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,751 @@
+const Char = {
+  ANCHOR: '&',
+  COMMENT: '#',
+  TAG: '!',
+  DIRECTIVES_END: '-',
+  DOCUMENT_END: '.'
+};
+const Type = {
+  ALIAS: 'ALIAS',
+  BLANK_LINE: 'BLANK_LINE',
+  BLOCK_FOLDED: 'BLOCK_FOLDED',
+  BLOCK_LITERAL: 'BLOCK_LITERAL',
+  COMMENT: 'COMMENT',
+  DIRECTIVE: 'DIRECTIVE',
+  DOCUMENT: 'DOCUMENT',
+  FLOW_MAP: 'FLOW_MAP',
+  FLOW_SEQ: 'FLOW_SEQ',
+  MAP: 'MAP',
+  MAP_KEY: 'MAP_KEY',
+  MAP_VALUE: 'MAP_VALUE',
+  PLAIN: 'PLAIN',
+  QUOTE_DOUBLE: 'QUOTE_DOUBLE',
+  QUOTE_SINGLE: 'QUOTE_SINGLE',
+  SEQ: 'SEQ',
+  SEQ_ITEM: 'SEQ_ITEM'
+};
+const defaultTagPrefix = 'tag:yaml.org,2002:';
+const defaultTags = {
+  MAP: 'tag:yaml.org,2002:map',
+  SEQ: 'tag:yaml.org,2002:seq',
+  STR: 'tag:yaml.org,2002:str'
+};
+
+function findLineStarts(src) {
+  const ls = [0];
+  let offset = src.indexOf('\n');
+  while (offset !== -1) {
+    offset += 1;
+    ls.push(offset);
+    offset = src.indexOf('\n', offset);
+  }
+  return ls;
+}
+function getSrcInfo(cst) {
+  let lineStarts, src;
+  if (typeof cst === 'string') {
+    lineStarts = findLineStarts(cst);
+    src = cst;
+  } else {
+    if (Array.isArray(cst)) cst = cst[0];
+    if (cst && cst.context) {
+      if (!cst.lineStarts) cst.lineStarts = findLineStarts(cst.context.src);
+      lineStarts = cst.lineStarts;
+      src = cst.context.src;
+    }
+  }
+  return {
+    lineStarts,
+    src
+  };
+}
+
+/**
+ * @typedef {Object} LinePos - One-indexed position in the source
+ * @property {number} line
+ * @property {number} col
+ */
+
+/**
+ * Determine the line/col position matching a character offset.
+ *
+ * Accepts a source string or a CST document as the second parameter. With
+ * the latter, starting indices for lines are cached in the document as
+ * `lineStarts: number[]`.
+ *
+ * Returns a one-indexed `{ line, col }` location if found, or
+ * `undefined` otherwise.
+ *
+ * @param {number} offset
+ * @param {string|Document|Document[]} cst
+ * @returns {?LinePos}
+ */
+function getLinePos(offset, cst) {
+  if (typeof offset !== 'number' || offset < 0) return null;
+  const {
+    lineStarts,
+    src
+  } = getSrcInfo(cst);
+  if (!lineStarts || !src || offset > src.length) return null;
+  for (let i = 0; i < lineStarts.length; ++i) {
+    const start = lineStarts[i];
+    if (offset < start) {
+      return {
+        line: i,
+        col: offset - lineStarts[i - 1] + 1
+      };
+    }
+    if (offset === start) return {
+      line: i + 1,
+      col: 1
+    };
+  }
+  const line = lineStarts.length;
+  return {
+    line,
+    col: offset - lineStarts[line - 1] + 1
+  };
+}
+
+/**
+ * Get a specified line from the source.
+ *
+ * Accepts a source string or a CST document as the second parameter. With
+ * the latter, starting indices for lines are cached in the document as
+ * `lineStarts: number[]`.
+ *
+ * Returns the line as a string if found, or `null` otherwise.
+ *
+ * @param {number} line One-indexed line number
+ * @param {string|Document|Document[]} cst
+ * @returns {?string}
+ */
+function getLine(line, cst) {
+  const {
+    lineStarts,
+    src
+  } = getSrcInfo(cst);
+  if (!lineStarts || !(line >= 1) || line > lineStarts.length) return null;
+  const start = lineStarts[line - 1];
+  let end = lineStarts[line]; // undefined for last line; that's ok for slice()
+  while (end && end > start && src[end - 1] === '\n') --end;
+  return src.slice(start, end);
+}
+
+/**
+ * Pretty-print the starting line from the source indicated by the range `pos`
+ *
+ * Trims output to `maxWidth` chars while keeping the starting column visible,
+ * using `…` at either end to indicate dropped characters.
+ *
+ * Returns a two-line string (or `null`) with `\n` as separator; the second line
+ * will hold appropriately indented `^` marks indicating the column range.
+ *
+ * @param {Object} pos
+ * @param {LinePos} pos.start
+ * @param {LinePos} [pos.end]
+ * @param {string|Document|Document[]*} cst
+ * @param {number} [maxWidth=80]
+ * @returns {?string}
+ */
+function getPrettyContext({
+  start,
+  end
+}, cst, maxWidth = 80) {
+  let src = getLine(start.line, cst);
+  if (!src) return null;
+  let {
+    col
+  } = start;
+  if (src.length > maxWidth) {
+    if (col <= maxWidth - 10) {
+      src = src.substr(0, maxWidth - 1) + '…';
+    } else {
+      const halfWidth = Math.round(maxWidth / 2);
+      if (src.length > col + halfWidth) src = src.substr(0, col + halfWidth - 1) + '…';
+      col -= src.length - maxWidth;
+      src = '…' + src.substr(1 - maxWidth);
+    }
+  }
+  let errLen = 1;
+  let errEnd = '';
+  if (end) {
+    if (end.line === start.line && col + (end.col - start.col) <= maxWidth + 1) {
+      errLen = end.col - start.col;
+    } else {
+      errLen = Math.min(src.length + 1, maxWidth) - col;
+      errEnd = '…';
+    }
+  }
+  const offset = col > 1 ? ' '.repeat(col - 1) : '';
+  const err = '^'.repeat(errLen);
+  return `${src}\n${offset}${err}${errEnd}`;
+}
+
+class Range {
+  static copy(orig) {
+    return new Range(orig.start, orig.end);
+  }
+  constructor(start, end) {
+    this.start = start;
+    this.end = end || start;
+  }
+  isEmpty() {
+    return typeof this.start !== 'number' || !this.end || this.end <= this.start;
+  }
+
+  /**
+   * Set `origStart` and `origEnd` to point to the original source range for
+   * this node, which may differ due to dropped CR characters.
+   *
+   * @param {number[]} cr - Positions of dropped CR characters
+   * @param {number} offset - Starting index of `cr` from the last call
+   * @returns {number} - The next offset, matching the one found for `origStart`
+   */
+  setOrigRange(cr, offset) {
+    const {
+      start,
+      end
+    } = this;
+    if (cr.length === 0 || end <= cr[0]) {
+      this.origStart = start;
+      this.origEnd = end;
+      return offset;
+    }
+    let i = offset;
+    while (i < cr.length) {
+      if (cr[i] > start) break;else ++i;
+    }
+    this.origStart = start + i;
+    const nextOffset = i;
+    while (i < cr.length) {
+      // if end was at \n, it should now be at \r
+      if (cr[i] >= end) break;else ++i;
+    }
+    this.origEnd = end + i;
+    return nextOffset;
+  }
+}
+
+/** Root class of all nodes */
+class Node {
+  static addStringTerminator(src, offset, str) {
+    if (str[str.length - 1] === '\n') return str;
+    const next = Node.endOfWhiteSpace(src, offset);
+    return next >= src.length || src[next] === '\n' ? str + '\n' : str;
+  }
+
+  // ^(---|...)
+  static atDocumentBoundary(src, offset, sep) {
+    const ch0 = src[offset];
+    if (!ch0) return true;
+    const prev = src[offset - 1];
+    if (prev && prev !== '\n') return false;
+    if (sep) {
+      if (ch0 !== sep) return false;
+    } else {
+      if (ch0 !== Char.DIRECTIVES_END && ch0 !== Char.DOCUMENT_END) return false;
+    }
+    const ch1 = src[offset + 1];
+    const ch2 = src[offset + 2];
+    if (ch1 !== ch0 || ch2 !== ch0) return false;
+    const ch3 = src[offset + 3];
+    return !ch3 || ch3 === '\n' || ch3 === '\t' || ch3 === ' ';
+  }
+  static endOfIdentifier(src, offset) {
+    let ch = src[offset];
+    const isVerbatim = ch === '<';
+    const notOk = isVerbatim ? ['\n', '\t', ' ', '>'] : ['\n', '\t', ' ', '[', ']', '{', '}', ','];
+    while (ch && notOk.indexOf(ch) === -1) ch = src[offset += 1];
+    if (isVerbatim && ch === '>') offset += 1;
+    return offset;
+  }
+  static endOfIndent(src, offset) {
+    let ch = src[offset];
+    while (ch === ' ') ch = src[offset += 1];
+    return offset;
+  }
+  static endOfLine(src, offset) {
+    let ch = src[offset];
+    while (ch && ch !== '\n') ch = src[offset += 1];
+    return offset;
+  }
+  static endOfWhiteSpace(src, offset) {
+    let ch = src[offset];
+    while (ch === '\t' || ch === ' ') ch = src[offset += 1];
+    return offset;
+  }
+  static startOfLine(src, offset) {
+    let ch = src[offset - 1];
+    if (ch === '\n') return offset;
+    while (ch && ch !== '\n') ch = src[offset -= 1];
+    return offset + 1;
+  }
+
+  /**
+   * End of indentation, or null if the line's indent level is not more
+   * than `indent`
+   *
+   * @param {string} src
+   * @param {number} indent
+   * @param {number} lineStart
+   * @returns {?number}
+   */
+  static endOfBlockIndent(src, indent, lineStart) {
+    const inEnd = Node.endOfIndent(src, lineStart);
+    if (inEnd > lineStart + indent) {
+      return inEnd;
+    } else {
+      const wsEnd = Node.endOfWhiteSpace(src, inEnd);
+      const ch = src[wsEnd];
+      if (!ch || ch === '\n') return wsEnd;
+    }
+    return null;
+  }
+  static atBlank(src, offset, endAsBlank) {
+    const ch = src[offset];
+    return ch === '\n' || ch === '\t' || ch === ' ' || endAsBlank && !ch;
+  }
+  static nextNodeIsIndented(ch, indentDiff, indicatorAsIndent) {
+    if (!ch || indentDiff < 0) return false;
+    if (indentDiff > 0) return true;
+    return indicatorAsIndent && ch === '-';
+  }
+
+  // should be at line or string end, or at next non-whitespace char
+  static normalizeOffset(src, offset) {
+    const ch = src[offset];
+    return !ch ? offset : ch !== '\n' && src[offset - 1] === '\n' ? offset - 1 : Node.endOfWhiteSpace(src, offset);
+  }
+
+  // fold single newline into space, multiple newlines to N - 1 newlines
+  // presumes src[offset] === '\n'
+  static foldNewline(src, offset, indent) {
+    let inCount = 0;
+    let error = false;
+    let fold = '';
+    let ch = src[offset + 1];
+    while (ch === ' ' || ch === '\t' || ch === '\n') {
+      switch (ch) {
+        case '\n':
+          inCount = 0;
+          offset += 1;
+          fold += '\n';
+          break;
+        case '\t':
+          if (inCount <= indent) error = true;
+          offset = Node.endOfWhiteSpace(src, offset + 2) - 1;
+          break;
+        case ' ':
+          inCount += 1;
+          offset += 1;
+          break;
+      }
+      ch = src[offset + 1];
+    }
+    if (!fold) fold = ' ';
+    if (ch && inCount <= indent) error = true;
+    return {
+      fold,
+      offset,
+      error
+    };
+  }
+  constructor(type, props, context) {
+    Object.defineProperty(this, 'context', {
+      value: context || null,
+      writable: true
+    });
+    this.error = null;
+    this.range = null;
+    this.valueRange = null;
+    this.props = props || [];
+    this.type = type;
+    this.value = null;
+  }
+  getPropValue(idx, key, skipKey) {
+    if (!this.context) return null;
+    const {
+      src
+    } = this.context;
+    const prop = this.props[idx];
+    return prop && src[prop.start] === key ? src.slice(prop.start + (skipKey ? 1 : 0), prop.end) : null;
+  }
+  get anchor() {
+    for (let i = 0; i < this.props.length; ++i) {
+      const anchor = this.getPropValue(i, Char.ANCHOR, true);
+      if (anchor != null) return anchor;
+    }
+    return null;
+  }
+  get comment() {
+    const comments = [];
+    for (let i = 0; i < this.props.length; ++i) {
+      const comment = this.getPropValue(i, Char.COMMENT, true);
+      if (comment != null) comments.push(comment);
+    }
+    return comments.length > 0 ? comments.join('\n') : null;
+  }
+  commentHasRequiredWhitespace(start) {
+    const {
+      src
+    } = this.context;
+    if (this.header && start === this.header.end) return false;
+    if (!this.valueRange) return false;
+    const {
+      end
+    } = this.valueRange;
+    return start !== end || Node.atBlank(src, end - 1);
+  }
+  get hasComment() {
+    if (this.context) {
+      const {
+        src
+      } = this.context;
+      for (let i = 0; i < this.props.length; ++i) {
+        if (src[this.props[i].start] === Char.COMMENT) return true;
+      }
+    }
+    return false;
+  }
+  get hasProps() {
+    if (this.context) {
+      const {
+        src
+      } = this.context;
+      for (let i = 0; i < this.props.length; ++i) {
+        if (src[this.props[i].start] !== Char.COMMENT) return true;
+      }
+    }
+    return false;
+  }
+  get includesTrailingLines() {
+    return false;
+  }
+  get jsonLike() {
+    const jsonLikeTypes = [Type.FLOW_MAP, Type.FLOW_SEQ, Type.QUOTE_DOUBLE, Type.QUOTE_SINGLE];
+    return jsonLikeTypes.indexOf(this.type) !== -1;
+  }
+  get rangeAsLinePos() {
+    if (!this.range || !this.context) return undefined;
+    const start = getLinePos(this.range.start, this.context.root);
+    if (!start) return undefined;
+    const end = getLinePos(this.range.end, this.context.root);
+    return {
+      start,
+      end
+    };
+  }
+  get rawValue() {
+    if (!this.valueRange || !this.context) return null;
+    const {
+      start,
+      end
+    } = this.valueRange;
+    return this.context.src.slice(start, end);
+  }
+  get tag() {
+    for (let i = 0; i < this.props.length; ++i) {
+      const tag = this.getPropValue(i, Char.TAG, false);
+      if (tag != null) {
+        if (tag[1] === '<') {
+          return {
+            verbatim: tag.slice(2, -1)
+          };
+        } else {
+          // eslint-disable-next-line no-unused-vars
+          const [_, handle, suffix] = tag.match(/^(.*!)([^!]*)$/);
+          return {
+            handle,
+            suffix
+          };
+        }
+      }
+    }
+    return null;
+  }
+  get valueRangeContainsNewline() {
+    if (!this.valueRange || !this.context) return false;
+    const {
+      start,
+      end
+    } = this.valueRange;
+    const {
+      src
+    } = this.context;
+    for (let i = start; i < end; ++i) {
+      if (src[i] === '\n') return true;
+    }
+    return false;
+  }
+  parseComment(start) {
+    const {
+      src
+    } = this.context;
+    if (src[start] === Char.COMMENT) {
+      const end = Node.endOfLine(src, start + 1);
+      const commentRange = new Range(start, end);
+      this.props.push(commentRange);
+      return end;
+    }
+    return start;
+  }
+
+  /**
+   * Populates the `origStart` and `origEnd` values of all ranges for this
+   * node. Extended by child classes to handle descendant nodes.
+   *
+   * @param {number[]} cr - Positions of dropped CR characters
+   * @param {number} offset - Starting index of `cr` from the last call
+   * @returns {number} - The next offset, matching the one found for `origStart`
+   */
+  setOrigRanges(cr, offset) {
+    if (this.range) offset = this.range.setOrigRange(cr, offset);
+    if (this.valueRange) this.valueRange.setOrigRange(cr, offset);
+    this.props.forEach(prop => prop.setOrigRange(cr, offset));
+    return offset;
+  }
+  toString() {
+    const {
+      context: {
+        src
+      },
+      range,
+      value
+    } = this;
+    if (value != null) return value;
+    const str = src.slice(range.start, range.end);
+    return Node.addStringTerminator(src, range.end, str);
+  }
+}
+
+class YAMLError extends Error {
+  constructor(name, source, message) {
+    if (!message || !(source instanceof Node)) throw new Error(`Invalid arguments for new ${name}`);
+    super();
+    this.name = name;
+    this.message = message;
+    this.source = source;
+  }
+  makePretty() {
+    if (!this.source) return;
+    this.nodeType = this.source.type;
+    const cst = this.source.context && this.source.context.root;
+    if (typeof this.offset === 'number') {
+      this.range = new Range(this.offset, this.offset + 1);
+      const start = cst && getLinePos(this.offset, cst);
+      if (start) {
+        const end = {
+          line: start.line,
+          col: start.col + 1
+        };
+        this.linePos = {
+          start,
+          end
+        };
+      }
+      delete this.offset;
+    } else {
+      this.range = this.source.range;
+      this.linePos = this.source.rangeAsLinePos;
+    }
+    if (this.linePos) {
+      const {
+        line,
+        col
+      } = this.linePos.start;
+      this.message += ` at line ${line}, column ${col}`;
+      const ctx = cst && getPrettyContext(this.linePos, cst);
+      if (ctx) this.message += `:\n\n${ctx}\n`;
+    }
+    delete this.source;
+  }
+}
+class YAMLReferenceError extends YAMLError {
+  constructor(source, message) {
+    super('YAMLReferenceError', source, message);
+  }
+}
+class YAMLSemanticError extends YAMLError {
+  constructor(source, message) {
+    super('YAMLSemanticError', source, message);
+  }
+}
+class YAMLSyntaxError extends YAMLError {
+  constructor(source, message) {
+    super('YAMLSyntaxError', source, message);
+  }
+}
+class YAMLWarning extends YAMLError {
+  constructor(source, message) {
+    super('YAMLWarning', source, message);
+  }
+}
+
+function _defineProperty(e, r, t) {
+  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
+    value: t,
+    enumerable: !0,
+    configurable: !0,
+    writable: !0
+  }) : e[r] = t, e;
+}
+function _toPrimitive(t, r) {
+  if ("object" != typeof t || !t) return t;
+  var e = t[Symbol.toPrimitive];
+  if (void 0 !== e) {
+    var i = e.call(t, r || "default");
+    if ("object" != typeof i) return i;
+    throw new TypeError("@@toPrimitive must return a primitive value.");
+  }
+  return ("string" === r ? String : Number)(t);
+}
+function _toPropertyKey(t) {
+  var i = _toPrimitive(t, "string");
+  return "symbol" == typeof i ? i : i + "";
+}
+
+class PlainValue extends Node {
+  static endOfLine(src, start, inFlow) {
+    let ch = src[start];
+    let offset = start;
+    while (ch && ch !== '\n') {
+      if (inFlow && (ch === '[' || ch === ']' || ch === '{' || ch === '}' || ch === ',')) break;
+      const next = src[offset + 1];
+      if (ch === ':' && (!next || next === '\n' || next === '\t' || next === ' ' || inFlow && next === ',')) break;
+      if ((ch === ' ' || ch === '\t') && next === '#') break;
+      offset += 1;
+      ch = next;
+    }
+    return offset;
+  }
+  get strValue() {
+    if (!this.valueRange || !this.context) return null;
+    let {
+      start,
+      end
+    } = this.valueRange;
+    const {
+      src
+    } = this.context;
+    let ch = src[end - 1];
+    while (start < end && (ch === '\n' || ch === '\t' || ch === ' ')) ch = src[--end - 1];
+    let str = '';
+    for (let i = start; i < end; ++i) {
+      const ch = src[i];
+      if (ch === '\n') {
+        const {
+          fold,
+          offset
+        } = Node.foldNewline(src, i, -1);
+        str += fold;
+        i = offset;
+      } else if (ch === ' ' || ch === '\t') {
+        // trim trailing whitespace
+        const wsStart = i;
+        let next = src[i + 1];
+        while (i < end && (next === ' ' || next === '\t')) {
+          i += 1;
+          next = src[i + 1];
+        }
+        if (next !== '\n') str += i > wsStart ? src.slice(wsStart, i + 1) : ch;
+      } else {
+        str += ch;
+      }
+    }
+    const ch0 = src[start];
+    switch (ch0) {
+      case '\t':
+        {
+          const msg = 'Plain value cannot start with a tab character';
+          const errors = [new YAMLSemanticError(this, msg)];
+          return {
+            errors,
+            str
+          };
+        }
+      case '@':
+      case '`':
+        {
+          const msg = `Plain value cannot start with reserved character ${ch0}`;
+          const errors = [new YAMLSemanticError(this, msg)];
+          return {
+            errors,
+            str
+          };
+        }
+      default:
+        return str;
+    }
+  }
+  parseBlockValue(start) {
+    const {
+      indent,
+      inFlow,
+      src
+    } = this.context;
+    let offset = start;
+    let valueEnd = start;
+    for (let ch = src[offset]; ch === '\n'; ch = src[offset]) {
+      if (Node.atDocumentBoundary(src, offset + 1)) break;
+      const end = Node.endOfBlockIndent(src, indent, offset + 1);
+      if (end === null || src[end] === '#') break;
+      if (src[end] === '\n') {
+        offset = end;
+      } else {
+        valueEnd = PlainValue.endOfLine(src, end, inFlow);
+        offset = valueEnd;
+      }
+    }
+    if (this.valueRange.isEmpty()) this.valueRange.start = start;
+    this.valueRange.end = valueEnd;
+    return valueEnd;
+  }
+
+  /**
+   * Parses a plain value from the source
+   *
+   * Accepted forms are:
+   * ```
+   * #comment
+   *
+   * first line
+   *
+   * first line #comment
+   *
+   * first line
+   * block
+   * lines
+   *
+   * #comment
+   * block
+   * lines
+   * ```
+   * where block lines are empty or have an indent level greater than `indent`.
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this scalar, may be `\n`
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      inFlow,
+      src
+    } = context;
+    let offset = start;
+    const ch = src[offset];
+    if (ch && ch !== '#' && ch !== '\n') {
+      offset = PlainValue.endOfLine(src, start, inFlow);
+    }
+    this.valueRange = new Range(start, offset);
+    offset = Node.endOfWhiteSpace(src, offset);
+    offset = this.parseComment(offset);
+    if (!this.hasComment || this.valueRange.isEmpty()) {
+      offset = this.parseBlockValue(offset);
+    }
+    return offset;
+  }
+}
+
+export { Char as C, Node as N, PlainValue as P, Range as R, Type as T, YAMLSyntaxError as Y, _defineProperty as _, YAMLWarning as a, YAMLSemanticError as b, YAMLError as c, defaultTagPrefix as d, defaultTags as e, YAMLReferenceError as f };
Index: frontend/node_modules/yaml/browser/dist/Schema-9530c078.js
===================================================================
--- frontend/node_modules/yaml/browser/dist/Schema-9530c078.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/dist/Schema-9530c078.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,467 @@
+import { _ as _defineProperty, d as defaultTagPrefix, e as defaultTags } from './PlainValue-183afbad.js';
+import { d as YAMLMap, g as resolveMap, Y as YAMLSeq, h as resolveSeq, j as resolveString, c as stringifyString, s as strOptions, S as Scalar, n as nullOptions, a as boolOptions, i as intOptions, k as stringifyNumber, N as Node, A as Alias, P as Pair } from './resolveSeq-67caf78a.js';
+import { b as binary, o as omap, p as pairs, s as set, i as intTime, f as floatTime, t as timestamp, a as warnOptionDeprecation } from './warnings-5e4358fe.js';
+
+function createMap(schema, obj, ctx) {
+  const map = new YAMLMap(schema);
+  if (obj instanceof Map) {
+    for (const [key, value] of obj) map.items.push(schema.createPair(key, value, ctx));
+  } else if (obj && typeof obj === 'object') {
+    for (const key of Object.keys(obj)) map.items.push(schema.createPair(key, obj[key], ctx));
+  }
+  if (typeof schema.sortMapEntries === 'function') {
+    map.items.sort(schema.sortMapEntries);
+  }
+  return map;
+}
+const map = {
+  createNode: createMap,
+  default: true,
+  nodeClass: YAMLMap,
+  tag: 'tag:yaml.org,2002:map',
+  resolve: resolveMap
+};
+
+function createSeq(schema, obj, ctx) {
+  const seq = new YAMLSeq(schema);
+  if (obj && obj[Symbol.iterator]) {
+    for (const it of obj) {
+      const v = schema.createNode(it, ctx.wrapScalars, null, ctx);
+      seq.items.push(v);
+    }
+  }
+  return seq;
+}
+const seq = {
+  createNode: createSeq,
+  default: true,
+  nodeClass: YAMLSeq,
+  tag: 'tag:yaml.org,2002:seq',
+  resolve: resolveSeq
+};
+
+const string = {
+  identify: value => typeof value === 'string',
+  default: true,
+  tag: 'tag:yaml.org,2002:str',
+  resolve: resolveString,
+  stringify(item, ctx, onComment, onChompKeep) {
+    ctx = Object.assign({
+      actualString: true
+    }, ctx);
+    return stringifyString(item, ctx, onComment, onChompKeep);
+  },
+  options: strOptions
+};
+
+const failsafe = [map, seq, string];
+
+/* global BigInt */
+const intIdentify$2 = value => typeof value === 'bigint' || Number.isInteger(value);
+const intResolve$1 = (src, part, radix) => intOptions.asBigInt ? BigInt(src) : parseInt(part, radix);
+function intStringify$1(node, radix, prefix) {
+  const {
+    value
+  } = node;
+  if (intIdentify$2(value) && value >= 0) return prefix + value.toString(radix);
+  return stringifyNumber(node);
+}
+const nullObj = {
+  identify: value => value == null,
+  createNode: (schema, value, ctx) => ctx.wrapScalars ? new Scalar(null) : null,
+  default: true,
+  tag: 'tag:yaml.org,2002:null',
+  test: /^(?:~|[Nn]ull|NULL)?$/,
+  resolve: () => null,
+  options: nullOptions,
+  stringify: () => nullOptions.nullStr
+};
+const boolObj = {
+  identify: value => typeof value === 'boolean',
+  default: true,
+  tag: 'tag:yaml.org,2002:bool',
+  test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/,
+  resolve: str => str[0] === 't' || str[0] === 'T',
+  options: boolOptions,
+  stringify: ({
+    value
+  }) => value ? boolOptions.trueStr : boolOptions.falseStr
+};
+const octObj = {
+  identify: value => intIdentify$2(value) && value >= 0,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'OCT',
+  test: /^0o([0-7]+)$/,
+  resolve: (str, oct) => intResolve$1(str, oct, 8),
+  options: intOptions,
+  stringify: node => intStringify$1(node, 8, '0o')
+};
+const intObj = {
+  identify: intIdentify$2,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  test: /^[-+]?[0-9]+$/,
+  resolve: str => intResolve$1(str, str, 10),
+  options: intOptions,
+  stringify: stringifyNumber
+};
+const hexObj = {
+  identify: value => intIdentify$2(value) && value >= 0,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'HEX',
+  test: /^0x([0-9a-fA-F]+)$/,
+  resolve: (str, hex) => intResolve$1(str, hex, 16),
+  options: intOptions,
+  stringify: node => intStringify$1(node, 16, '0x')
+};
+const nanObj = {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  test: /^(?:[-+]?\.inf|(\.nan))$/i,
+  resolve: (str, nan) => nan ? NaN : str[0] === '-' ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+  stringify: stringifyNumber
+};
+const expObj = {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  format: 'EXP',
+  test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/,
+  resolve: str => parseFloat(str),
+  stringify: ({
+    value
+  }) => Number(value).toExponential()
+};
+const floatObj = {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  test: /^[-+]?(?:\.([0-9]+)|[0-9]+\.([0-9]*))$/,
+  resolve(str, frac1, frac2) {
+    const frac = frac1 || frac2;
+    const node = new Scalar(parseFloat(str));
+    if (frac && frac[frac.length - 1] === '0') node.minFractionDigits = frac.length;
+    return node;
+  },
+  stringify: stringifyNumber
+};
+const core = failsafe.concat([nullObj, boolObj, octObj, intObj, hexObj, nanObj, expObj, floatObj]);
+
+/* global BigInt */
+const intIdentify$1 = value => typeof value === 'bigint' || Number.isInteger(value);
+const stringifyJSON = ({
+  value
+}) => JSON.stringify(value);
+const json = [map, seq, {
+  identify: value => typeof value === 'string',
+  default: true,
+  tag: 'tag:yaml.org,2002:str',
+  resolve: resolveString,
+  stringify: stringifyJSON
+}, {
+  identify: value => value == null,
+  createNode: (schema, value, ctx) => ctx.wrapScalars ? new Scalar(null) : null,
+  default: true,
+  tag: 'tag:yaml.org,2002:null',
+  test: /^null$/,
+  resolve: () => null,
+  stringify: stringifyJSON
+}, {
+  identify: value => typeof value === 'boolean',
+  default: true,
+  tag: 'tag:yaml.org,2002:bool',
+  test: /^true|false$/,
+  resolve: str => str === 'true',
+  stringify: stringifyJSON
+}, {
+  identify: intIdentify$1,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  test: /^-?(?:0|[1-9][0-9]*)$/,
+  resolve: str => intOptions.asBigInt ? BigInt(str) : parseInt(str, 10),
+  stringify: ({
+    value
+  }) => intIdentify$1(value) ? value.toString() : JSON.stringify(value)
+}, {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/,
+  resolve: str => parseFloat(str),
+  stringify: stringifyJSON
+}];
+json.scalarFallback = str => {
+  throw new SyntaxError(`Unresolved plain scalar ${JSON.stringify(str)}`);
+};
+
+/* global BigInt */
+const boolStringify = ({
+  value
+}) => value ? boolOptions.trueStr : boolOptions.falseStr;
+const intIdentify = value => typeof value === 'bigint' || Number.isInteger(value);
+function intResolve(sign, src, radix) {
+  let str = src.replace(/_/g, '');
+  if (intOptions.asBigInt) {
+    switch (radix) {
+      case 2:
+        str = `0b${str}`;
+        break;
+      case 8:
+        str = `0o${str}`;
+        break;
+      case 16:
+        str = `0x${str}`;
+        break;
+    }
+    const n = BigInt(str);
+    return sign === '-' ? BigInt(-1) * n : n;
+  }
+  const n = parseInt(str, radix);
+  return sign === '-' ? -1 * n : n;
+}
+function intStringify(node, radix, prefix) {
+  const {
+    value
+  } = node;
+  if (intIdentify(value)) {
+    const str = value.toString(radix);
+    return value < 0 ? '-' + prefix + str.substr(1) : prefix + str;
+  }
+  return stringifyNumber(node);
+}
+const yaml11 = failsafe.concat([{
+  identify: value => value == null,
+  createNode: (schema, value, ctx) => ctx.wrapScalars ? new Scalar(null) : null,
+  default: true,
+  tag: 'tag:yaml.org,2002:null',
+  test: /^(?:~|[Nn]ull|NULL)?$/,
+  resolve: () => null,
+  options: nullOptions,
+  stringify: () => nullOptions.nullStr
+}, {
+  identify: value => typeof value === 'boolean',
+  default: true,
+  tag: 'tag:yaml.org,2002:bool',
+  test: /^(?:Y|y|[Yy]es|YES|[Tt]rue|TRUE|[Oo]n|ON)$/,
+  resolve: () => true,
+  options: boolOptions,
+  stringify: boolStringify
+}, {
+  identify: value => typeof value === 'boolean',
+  default: true,
+  tag: 'tag:yaml.org,2002:bool',
+  test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/i,
+  resolve: () => false,
+  options: boolOptions,
+  stringify: boolStringify
+}, {
+  identify: intIdentify,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'BIN',
+  test: /^([-+]?)0b([0-1_]+)$/,
+  resolve: (str, sign, bin) => intResolve(sign, bin, 2),
+  stringify: node => intStringify(node, 2, '0b')
+}, {
+  identify: intIdentify,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'OCT',
+  test: /^([-+]?)0([0-7_]+)$/,
+  resolve: (str, sign, oct) => intResolve(sign, oct, 8),
+  stringify: node => intStringify(node, 8, '0')
+}, {
+  identify: intIdentify,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  test: /^([-+]?)([0-9][0-9_]*)$/,
+  resolve: (str, sign, abs) => intResolve(sign, abs, 10),
+  stringify: stringifyNumber
+}, {
+  identify: intIdentify,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'HEX',
+  test: /^([-+]?)0x([0-9a-fA-F_]+)$/,
+  resolve: (str, sign, hex) => intResolve(sign, hex, 16),
+  stringify: node => intStringify(node, 16, '0x')
+}, {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  test: /^(?:[-+]?\.inf|(\.nan))$/i,
+  resolve: (str, nan) => nan ? NaN : str[0] === '-' ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+  stringify: stringifyNumber
+}, {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  format: 'EXP',
+  test: /^[-+]?([0-9][0-9_]*)?(\.[0-9_]*)?[eE][-+]?[0-9]+$/,
+  resolve: str => parseFloat(str.replace(/_/g, '')),
+  stringify: ({
+    value
+  }) => Number(value).toExponential()
+}, {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  test: /^[-+]?(?:[0-9][0-9_]*)?\.([0-9_]*)$/,
+  resolve(str, frac) {
+    const node = new Scalar(parseFloat(str.replace(/_/g, '')));
+    if (frac) {
+      const f = frac.replace(/_/g, '');
+      if (f[f.length - 1] === '0') node.minFractionDigits = f.length;
+    }
+    return node;
+  },
+  stringify: stringifyNumber
+}], binary, omap, pairs, set, intTime, floatTime, timestamp);
+
+const schemas = {
+  core,
+  failsafe,
+  json,
+  yaml11
+};
+const tags = {
+  binary,
+  bool: boolObj,
+  float: floatObj,
+  floatExp: expObj,
+  floatNaN: nanObj,
+  floatTime,
+  int: intObj,
+  intHex: hexObj,
+  intOct: octObj,
+  intTime,
+  map,
+  null: nullObj,
+  omap,
+  pairs,
+  seq,
+  set,
+  timestamp
+};
+
+function findTagObject(value, tagName, tags) {
+  if (tagName) {
+    const match = tags.filter(t => t.tag === tagName);
+    const tagObj = match.find(t => !t.format) || match[0];
+    if (!tagObj) throw new Error(`Tag ${tagName} not found`);
+    return tagObj;
+  }
+
+  // TODO: deprecate/remove class check
+  return tags.find(t => (t.identify && t.identify(value) || t.class && value instanceof t.class) && !t.format);
+}
+function createNode(value, tagName, ctx) {
+  if (value instanceof Node) return value;
+  const {
+    defaultPrefix,
+    onTagObj,
+    prevObjects,
+    schema,
+    wrapScalars
+  } = ctx;
+  if (tagName && tagName.startsWith('!!')) tagName = defaultPrefix + tagName.slice(2);
+  let tagObj = findTagObject(value, tagName, schema.tags);
+  if (!tagObj) {
+    if (typeof value.toJSON === 'function') value = value.toJSON();
+    if (!value || typeof value !== 'object') return wrapScalars ? new Scalar(value) : value;
+    tagObj = value instanceof Map ? map : value[Symbol.iterator] ? seq : map;
+  }
+  if (onTagObj) {
+    onTagObj(tagObj);
+    delete ctx.onTagObj;
+  }
+
+  // Detect duplicate references to the same object & use Alias nodes for all
+  // after first. The `obj` wrapper allows for circular references to resolve.
+  const obj = {
+    value: undefined,
+    node: undefined
+  };
+  if (value && typeof value === 'object' && prevObjects) {
+    const prev = prevObjects.get(value);
+    if (prev) {
+      const alias = new Alias(prev); // leaves source dirty; must be cleaned by caller
+      ctx.aliasNodes.push(alias); // defined along with prevObjects
+      return alias;
+    }
+    obj.value = value;
+    prevObjects.set(value, obj);
+  }
+  obj.node = tagObj.createNode ? tagObj.createNode(ctx.schema, value, ctx) : wrapScalars ? new Scalar(value) : value;
+  if (tagName && obj.node instanceof Node) obj.node.tag = tagName;
+  return obj.node;
+}
+
+function getSchemaTags(schemas, knownTags, customTags, schemaId) {
+  let tags = schemas[schemaId.replace(/\W/g, '')]; // 'yaml-1.1' -> 'yaml11'
+  if (!tags) {
+    const keys = Object.keys(schemas).map(key => JSON.stringify(key)).join(', ');
+    throw new Error(`Unknown schema "${schemaId}"; use one of ${keys}`);
+  }
+  if (Array.isArray(customTags)) {
+    for (const tag of customTags) tags = tags.concat(tag);
+  } else if (typeof customTags === 'function') {
+    tags = customTags(tags.slice());
+  }
+  for (let i = 0; i < tags.length; ++i) {
+    const tag = tags[i];
+    if (typeof tag === 'string') {
+      const tagObj = knownTags[tag];
+      if (!tagObj) {
+        const keys = Object.keys(knownTags).map(key => JSON.stringify(key)).join(', ');
+        throw new Error(`Unknown custom tag "${tag}"; use one of ${keys}`);
+      }
+      tags[i] = tagObj;
+    }
+  }
+  return tags;
+}
+
+const sortMapEntriesByKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
+class Schema {
+  // TODO: remove in v2
+
+  constructor({
+    customTags,
+    merge,
+    schema,
+    sortMapEntries,
+    tags: deprecatedCustomTags
+  }) {
+    this.merge = !!merge;
+    this.name = schema;
+    this.sortMapEntries = sortMapEntries === true ? sortMapEntriesByKey : sortMapEntries || null;
+    if (!customTags && deprecatedCustomTags) warnOptionDeprecation('tags', 'customTags');
+    this.tags = getSchemaTags(schemas, tags, customTags || deprecatedCustomTags, schema);
+  }
+  createNode(value, wrapScalars, tagName, ctx) {
+    const baseCtx = {
+      defaultPrefix: Schema.defaultPrefix,
+      schema: this,
+      wrapScalars
+    };
+    const createCtx = ctx ? Object.assign(ctx, baseCtx) : baseCtx;
+    return createNode(value, tagName, createCtx);
+  }
+  createPair(key, value, ctx) {
+    if (!ctx) ctx = {
+      wrapScalars: true
+    };
+    const k = this.createNode(key, ctx.wrapScalars, null, ctx);
+    const v = this.createNode(value, ctx.wrapScalars, null, ctx);
+    return new Pair(k, v);
+  }
+}
+_defineProperty(Schema, "defaultPrefix", defaultTagPrefix);
+// TODO: remove in v2
+_defineProperty(Schema, "defaultTags", defaultTags);
+
+export { Schema as S };
Index: frontend/node_modules/yaml/browser/dist/index.js
===================================================================
--- frontend/node_modules/yaml/browser/dist/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/dist/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,692 @@
+import { parse as parse$1 } from './parse-cst.js';
+import { d as defaultTagPrefix, _ as _defineProperty, T as Type, Y as YAMLSyntaxError, a as YAMLWarning, b as YAMLSemanticError, c as YAMLError } from './PlainValue-183afbad.js';
+import { b as binaryOptions, a as boolOptions, i as intOptions, n as nullOptions, s as strOptions, N as Node, P as Pair, S as Scalar, c as stringifyString, A as Alias, Y as YAMLSeq, d as YAMLMap, M as Merge, C as Collection, r as resolveNode, e as isEmptyPath, t as toJSON, f as addComment } from './resolveSeq-67caf78a.js';
+import { S as Schema } from './Schema-9530c078.js';
+import { w as warn } from './warnings-5e4358fe.js';
+
+const defaultOptions = {
+  anchorPrefix: 'a',
+  customTags: null,
+  indent: 2,
+  indentSeq: true,
+  keepCstNodes: false,
+  keepNodeTypes: true,
+  keepBlobsInJSON: true,
+  mapAsMap: false,
+  maxAliasCount: 100,
+  prettyErrors: false,
+  // TODO Set true in v2
+  simpleKeys: false,
+  version: '1.2'
+};
+const scalarOptions = {
+  get binary() {
+    return binaryOptions;
+  },
+  set binary(opt) {
+    Object.assign(binaryOptions, opt);
+  },
+  get bool() {
+    return boolOptions;
+  },
+  set bool(opt) {
+    Object.assign(boolOptions, opt);
+  },
+  get int() {
+    return intOptions;
+  },
+  set int(opt) {
+    Object.assign(intOptions, opt);
+  },
+  get null() {
+    return nullOptions;
+  },
+  set null(opt) {
+    Object.assign(nullOptions, opt);
+  },
+  get str() {
+    return strOptions;
+  },
+  set str(opt) {
+    Object.assign(strOptions, opt);
+  }
+};
+const documentOptions = {
+  '1.0': {
+    schema: 'yaml-1.1',
+    merge: true,
+    tagPrefixes: [{
+      handle: '!',
+      prefix: defaultTagPrefix
+    }, {
+      handle: '!!',
+      prefix: 'tag:private.yaml.org,2002:'
+    }]
+  },
+  1.1: {
+    schema: 'yaml-1.1',
+    merge: true,
+    tagPrefixes: [{
+      handle: '!',
+      prefix: '!'
+    }, {
+      handle: '!!',
+      prefix: defaultTagPrefix
+    }]
+  },
+  1.2: {
+    schema: 'core',
+    merge: false,
+    tagPrefixes: [{
+      handle: '!',
+      prefix: '!'
+    }, {
+      handle: '!!',
+      prefix: defaultTagPrefix
+    }]
+  }
+};
+
+function stringifyTag(doc, tag) {
+  if ((doc.version || doc.options.version) === '1.0') {
+    const priv = tag.match(/^tag:private\.yaml\.org,2002:([^:/]+)$/);
+    if (priv) return '!' + priv[1];
+    const vocab = tag.match(/^tag:([a-zA-Z0-9-]+)\.yaml\.org,2002:(.*)/);
+    return vocab ? `!${vocab[1]}/${vocab[2]}` : `!${tag.replace(/^tag:/, '')}`;
+  }
+  let p = doc.tagPrefixes.find(p => tag.indexOf(p.prefix) === 0);
+  if (!p) {
+    const dtp = doc.getDefaults().tagPrefixes;
+    p = dtp && dtp.find(p => tag.indexOf(p.prefix) === 0);
+  }
+  if (!p) return tag[0] === '!' ? tag : `!<${tag}>`;
+  const suffix = tag.substr(p.prefix.length).replace(/[!,[\]{}]/g, ch => ({
+    '!': '%21',
+    ',': '%2C',
+    '[': '%5B',
+    ']': '%5D',
+    '{': '%7B',
+    '}': '%7D'
+  })[ch]);
+  return p.handle + suffix;
+}
+
+function getTagObject(tags, item) {
+  if (item instanceof Alias) return Alias;
+  if (item.tag) {
+    const match = tags.filter(t => t.tag === item.tag);
+    if (match.length > 0) return match.find(t => t.format === item.format) || match[0];
+  }
+  let tagObj, obj;
+  if (item instanceof Scalar) {
+    obj = item.value;
+    // TODO: deprecate/remove class check
+    const match = tags.filter(t => t.identify && t.identify(obj) || t.class && obj instanceof t.class);
+    tagObj = match.find(t => t.format === item.format) || match.find(t => !t.format);
+  } else {
+    obj = item;
+    tagObj = tags.find(t => t.nodeClass && obj instanceof t.nodeClass);
+  }
+  if (!tagObj) {
+    const name = obj && obj.constructor ? obj.constructor.name : typeof obj;
+    throw new Error(`Tag not resolved for ${name} value`);
+  }
+  return tagObj;
+}
+
+// needs to be called before value stringifier to allow for circular anchor refs
+function stringifyProps(node, tagObj, {
+  anchors,
+  doc
+}) {
+  const props = [];
+  const anchor = doc.anchors.getName(node);
+  if (anchor) {
+    anchors[anchor] = node;
+    props.push(`&${anchor}`);
+  }
+  if (node.tag) {
+    props.push(stringifyTag(doc, node.tag));
+  } else if (!tagObj.default) {
+    props.push(stringifyTag(doc, tagObj.tag));
+  }
+  return props.join(' ');
+}
+function stringify$1(item, ctx, onComment, onChompKeep) {
+  const {
+    anchors,
+    schema
+  } = ctx.doc;
+  let tagObj;
+  if (!(item instanceof Node)) {
+    const createCtx = {
+      aliasNodes: [],
+      onTagObj: o => tagObj = o,
+      prevObjects: new Map()
+    };
+    item = schema.createNode(item, true, null, createCtx);
+    for (const alias of createCtx.aliasNodes) {
+      alias.source = alias.source.node;
+      let name = anchors.getName(alias.source);
+      if (!name) {
+        name = anchors.newName();
+        anchors.map[name] = alias.source;
+      }
+    }
+  }
+  if (item instanceof Pair) return item.toString(ctx, onComment, onChompKeep);
+  if (!tagObj) tagObj = getTagObject(schema.tags, item);
+  const props = stringifyProps(item, tagObj, ctx);
+  if (props.length > 0) ctx.indentAtStart = (ctx.indentAtStart || 0) + props.length + 1;
+  const str = typeof tagObj.stringify === 'function' ? tagObj.stringify(item, ctx, onComment, onChompKeep) : item instanceof Scalar ? stringifyString(item, ctx, onComment, onChompKeep) : item.toString(ctx, onComment, onChompKeep);
+  if (!props) return str;
+  return item instanceof Scalar || str[0] === '{' || str[0] === '[' ? `${props} ${str}` : `${props}\n${ctx.indent}${str}`;
+}
+
+class Anchors {
+  static validAnchorNode(node) {
+    return node instanceof Scalar || node instanceof YAMLSeq || node instanceof YAMLMap;
+  }
+  constructor(prefix) {
+    _defineProperty(this, "map", Object.create(null));
+    this.prefix = prefix;
+  }
+  createAlias(node, name) {
+    this.setAnchor(node, name);
+    return new Alias(node);
+  }
+  createMergePair(...sources) {
+    const merge = new Merge();
+    merge.value.items = sources.map(s => {
+      if (s instanceof Alias) {
+        if (s.source instanceof YAMLMap) return s;
+      } else if (s instanceof YAMLMap) {
+        return this.createAlias(s);
+      }
+      throw new Error('Merge sources must be Map nodes or their Aliases');
+    });
+    return merge;
+  }
+  getName(node) {
+    const {
+      map
+    } = this;
+    return Object.keys(map).find(a => map[a] === node);
+  }
+  getNames() {
+    return Object.keys(this.map);
+  }
+  getNode(name) {
+    return this.map[name];
+  }
+  newName(prefix) {
+    if (!prefix) prefix = this.prefix;
+    const names = Object.keys(this.map);
+    for (let i = 1; true; ++i) {
+      const name = `${prefix}${i}`;
+      if (!names.includes(name)) return name;
+    }
+  }
+
+  // During parsing, map & aliases contain CST nodes
+  resolveNodes() {
+    const {
+      map,
+      _cstAliases
+    } = this;
+    Object.keys(map).forEach(a => {
+      map[a] = map[a].resolved;
+    });
+    _cstAliases.forEach(a => {
+      a.source = a.source.resolved;
+    });
+    delete this._cstAliases;
+  }
+  setAnchor(node, name) {
+    if (node != null && !Anchors.validAnchorNode(node)) {
+      throw new Error('Anchors may only be set for Scalar, Seq and Map nodes');
+    }
+    if (name && /[\x00-\x19\s,[\]{}]/.test(name)) {
+      throw new Error('Anchor names must not contain whitespace or control characters');
+    }
+    const {
+      map
+    } = this;
+    const prev = node && Object.keys(map).find(a => map[a] === node);
+    if (prev) {
+      if (!name) {
+        return prev;
+      } else if (prev !== name) {
+        delete map[prev];
+        map[name] = node;
+      }
+    } else {
+      if (!name) {
+        if (!node) return null;
+        name = this.newName();
+      }
+      map[name] = node;
+    }
+    return name;
+  }
+}
+
+const visit = (node, tags) => {
+  if (node && typeof node === 'object') {
+    const {
+      tag
+    } = node;
+    if (node instanceof Collection) {
+      if (tag) tags[tag] = true;
+      node.items.forEach(n => visit(n, tags));
+    } else if (node instanceof Pair) {
+      visit(node.key, tags);
+      visit(node.value, tags);
+    } else if (node instanceof Scalar) {
+      if (tag) tags[tag] = true;
+    }
+  }
+  return tags;
+};
+const listTagNames = node => Object.keys(visit(node, {}));
+
+function parseContents(doc, contents) {
+  const comments = {
+    before: [],
+    after: []
+  };
+  let body = undefined;
+  let spaceBefore = false;
+  for (const node of contents) {
+    if (node.valueRange) {
+      if (body !== undefined) {
+        const msg = 'Document contains trailing content not separated by a ... or --- line';
+        doc.errors.push(new YAMLSyntaxError(node, msg));
+        break;
+      }
+      const res = resolveNode(doc, node);
+      if (spaceBefore) {
+        res.spaceBefore = true;
+        spaceBefore = false;
+      }
+      body = res;
+    } else if (node.comment !== null) {
+      const cc = body === undefined ? comments.before : comments.after;
+      cc.push(node.comment);
+    } else if (node.type === Type.BLANK_LINE) {
+      spaceBefore = true;
+      if (body === undefined && comments.before.length > 0 && !doc.commentBefore) {
+        // space-separated comments at start are parsed as document comments
+        doc.commentBefore = comments.before.join('\n');
+        comments.before = [];
+      }
+    }
+  }
+  doc.contents = body || null;
+  if (!body) {
+    doc.comment = comments.before.concat(comments.after).join('\n') || null;
+  } else {
+    const cb = comments.before.join('\n');
+    if (cb) {
+      const cbNode = body instanceof Collection && body.items[0] ? body.items[0] : body;
+      cbNode.commentBefore = cbNode.commentBefore ? `${cb}\n${cbNode.commentBefore}` : cb;
+    }
+    doc.comment = comments.after.join('\n') || null;
+  }
+}
+
+function resolveTagDirective({
+  tagPrefixes
+}, directive) {
+  const [handle, prefix] = directive.parameters;
+  if (!handle || !prefix) {
+    const msg = 'Insufficient parameters given for %TAG directive';
+    throw new YAMLSemanticError(directive, msg);
+  }
+  if (tagPrefixes.some(p => p.handle === handle)) {
+    const msg = 'The %TAG directive must only be given at most once per handle in the same document.';
+    throw new YAMLSemanticError(directive, msg);
+  }
+  return {
+    handle,
+    prefix
+  };
+}
+function resolveYamlDirective(doc, directive) {
+  let [version] = directive.parameters;
+  if (directive.name === 'YAML:1.0') version = '1.0';
+  if (!version) {
+    const msg = 'Insufficient parameters given for %YAML directive';
+    throw new YAMLSemanticError(directive, msg);
+  }
+  if (!documentOptions[version]) {
+    const v0 = doc.version || doc.options.version;
+    const msg = `Document will be parsed as YAML ${v0} rather than YAML ${version}`;
+    doc.warnings.push(new YAMLWarning(directive, msg));
+  }
+  return version;
+}
+function parseDirectives(doc, directives, prevDoc) {
+  const directiveComments = [];
+  let hasDirectives = false;
+  for (const directive of directives) {
+    const {
+      comment,
+      name
+    } = directive;
+    switch (name) {
+      case 'TAG':
+        try {
+          doc.tagPrefixes.push(resolveTagDirective(doc, directive));
+        } catch (error) {
+          doc.errors.push(error);
+        }
+        hasDirectives = true;
+        break;
+      case 'YAML':
+      case 'YAML:1.0':
+        if (doc.version) {
+          const msg = 'The %YAML directive must only be given at most once per document.';
+          doc.errors.push(new YAMLSemanticError(directive, msg));
+        }
+        try {
+          doc.version = resolveYamlDirective(doc, directive);
+        } catch (error) {
+          doc.errors.push(error);
+        }
+        hasDirectives = true;
+        break;
+      default:
+        if (name) {
+          const msg = `YAML only supports %TAG and %YAML directives, and not %${name}`;
+          doc.warnings.push(new YAMLWarning(directive, msg));
+        }
+    }
+    if (comment) directiveComments.push(comment);
+  }
+  if (prevDoc && !hasDirectives && '1.1' === (doc.version || prevDoc.version || doc.options.version)) {
+    const copyTagPrefix = ({
+      handle,
+      prefix
+    }) => ({
+      handle,
+      prefix
+    });
+    doc.tagPrefixes = prevDoc.tagPrefixes.map(copyTagPrefix);
+    doc.version = prevDoc.version;
+  }
+  doc.commentBefore = directiveComments.join('\n') || null;
+}
+
+function assertCollection(contents) {
+  if (contents instanceof Collection) return true;
+  throw new Error('Expected a YAML collection as document contents');
+}
+class Document$1 {
+  constructor(options) {
+    this.anchors = new Anchors(options.anchorPrefix);
+    this.commentBefore = null;
+    this.comment = null;
+    this.contents = null;
+    this.directivesEndMarker = null;
+    this.errors = [];
+    this.options = options;
+    this.schema = null;
+    this.tagPrefixes = [];
+    this.version = null;
+    this.warnings = [];
+  }
+  add(value) {
+    assertCollection(this.contents);
+    return this.contents.add(value);
+  }
+  addIn(path, value) {
+    assertCollection(this.contents);
+    this.contents.addIn(path, value);
+  }
+  delete(key) {
+    assertCollection(this.contents);
+    return this.contents.delete(key);
+  }
+  deleteIn(path) {
+    if (isEmptyPath(path)) {
+      if (this.contents == null) return false;
+      this.contents = null;
+      return true;
+    }
+    assertCollection(this.contents);
+    return this.contents.deleteIn(path);
+  }
+  getDefaults() {
+    return Document$1.defaults[this.version] || Document$1.defaults[this.options.version] || {};
+  }
+  get(key, keepScalar) {
+    return this.contents instanceof Collection ? this.contents.get(key, keepScalar) : undefined;
+  }
+  getIn(path, keepScalar) {
+    if (isEmptyPath(path)) return !keepScalar && this.contents instanceof Scalar ? this.contents.value : this.contents;
+    return this.contents instanceof Collection ? this.contents.getIn(path, keepScalar) : undefined;
+  }
+  has(key) {
+    return this.contents instanceof Collection ? this.contents.has(key) : false;
+  }
+  hasIn(path) {
+    if (isEmptyPath(path)) return this.contents !== undefined;
+    return this.contents instanceof Collection ? this.contents.hasIn(path) : false;
+  }
+  set(key, value) {
+    assertCollection(this.contents);
+    this.contents.set(key, value);
+  }
+  setIn(path, value) {
+    if (isEmptyPath(path)) this.contents = value;else {
+      assertCollection(this.contents);
+      this.contents.setIn(path, value);
+    }
+  }
+  setSchema(id, customTags) {
+    if (!id && !customTags && this.schema) return;
+    if (typeof id === 'number') id = id.toFixed(1);
+    if (id === '1.0' || id === '1.1' || id === '1.2') {
+      if (this.version) this.version = id;else this.options.version = id;
+      delete this.options.schema;
+    } else if (id && typeof id === 'string') {
+      this.options.schema = id;
+    }
+    if (Array.isArray(customTags)) this.options.customTags = customTags;
+    const opt = Object.assign({}, this.getDefaults(), this.options);
+    this.schema = new Schema(opt);
+  }
+  parse(node, prevDoc) {
+    if (this.options.keepCstNodes) this.cstNode = node;
+    if (this.options.keepNodeTypes) this.type = 'DOCUMENT';
+    const {
+      directives = [],
+      contents = [],
+      directivesEndMarker,
+      error,
+      valueRange
+    } = node;
+    if (error) {
+      if (!error.source) error.source = this;
+      this.errors.push(error);
+    }
+    parseDirectives(this, directives, prevDoc);
+    if (directivesEndMarker) this.directivesEndMarker = true;
+    this.range = valueRange ? [valueRange.start, valueRange.end] : null;
+    this.setSchema();
+    this.anchors._cstAliases = [];
+    parseContents(this, contents);
+    this.anchors.resolveNodes();
+    if (this.options.prettyErrors) {
+      for (const error of this.errors) if (error instanceof YAMLError) error.makePretty();
+      for (const warn of this.warnings) if (warn instanceof YAMLError) warn.makePretty();
+    }
+    return this;
+  }
+  listNonDefaultTags() {
+    return listTagNames(this.contents).filter(t => t.indexOf(Schema.defaultPrefix) !== 0);
+  }
+  setTagPrefix(handle, prefix) {
+    if (handle[0] !== '!' || handle[handle.length - 1] !== '!') throw new Error('Handle must start and end with !');
+    if (prefix) {
+      const prev = this.tagPrefixes.find(p => p.handle === handle);
+      if (prev) prev.prefix = prefix;else this.tagPrefixes.push({
+        handle,
+        prefix
+      });
+    } else {
+      this.tagPrefixes = this.tagPrefixes.filter(p => p.handle !== handle);
+    }
+  }
+  toJSON(arg, onAnchor) {
+    const {
+      keepBlobsInJSON,
+      mapAsMap,
+      maxAliasCount
+    } = this.options;
+    const keep = keepBlobsInJSON && (typeof arg !== 'string' || !(this.contents instanceof Scalar));
+    const ctx = {
+      doc: this,
+      indentStep: '  ',
+      keep,
+      mapAsMap: keep && !!mapAsMap,
+      maxAliasCount,
+      stringify: stringify$1 // Requiring directly in Pair would create circular dependencies
+    };
+    const anchorNames = Object.keys(this.anchors.map);
+    if (anchorNames.length > 0) ctx.anchors = new Map(anchorNames.map(name => [this.anchors.map[name], {
+      alias: [],
+      aliasCount: 0,
+      count: 1
+    }]));
+    const res = toJSON(this.contents, arg, ctx);
+    if (typeof onAnchor === 'function' && ctx.anchors) for (const {
+      count,
+      res
+    } of ctx.anchors.values()) onAnchor(res, count);
+    return res;
+  }
+  toString() {
+    if (this.errors.length > 0) throw new Error('Document with errors cannot be stringified');
+    const indentSize = this.options.indent;
+    if (!Number.isInteger(indentSize) || indentSize <= 0) {
+      const s = JSON.stringify(indentSize);
+      throw new Error(`"indent" option must be a positive integer, not ${s}`);
+    }
+    this.setSchema();
+    const lines = [];
+    let hasDirectives = false;
+    if (this.version) {
+      let vd = '%YAML 1.2';
+      if (this.schema.name === 'yaml-1.1') {
+        if (this.version === '1.0') vd = '%YAML:1.0';else if (this.version === '1.1') vd = '%YAML 1.1';
+      }
+      lines.push(vd);
+      hasDirectives = true;
+    }
+    const tagNames = this.listNonDefaultTags();
+    this.tagPrefixes.forEach(({
+      handle,
+      prefix
+    }) => {
+      if (tagNames.some(t => t.indexOf(prefix) === 0)) {
+        lines.push(`%TAG ${handle} ${prefix}`);
+        hasDirectives = true;
+      }
+    });
+    if (hasDirectives || this.directivesEndMarker) lines.push('---');
+    if (this.commentBefore) {
+      if (hasDirectives || !this.directivesEndMarker) lines.unshift('');
+      lines.unshift(this.commentBefore.replace(/^/gm, '#'));
+    }
+    const ctx = {
+      anchors: Object.create(null),
+      doc: this,
+      indent: '',
+      indentStep: ' '.repeat(indentSize),
+      stringify: stringify$1 // Requiring directly in nodes would create circular dependencies
+    };
+    let chompKeep = false;
+    let contentComment = null;
+    if (this.contents) {
+      if (this.contents instanceof Node) {
+        if (this.contents.spaceBefore && (hasDirectives || this.directivesEndMarker)) lines.push('');
+        if (this.contents.commentBefore) lines.push(this.contents.commentBefore.replace(/^/gm, '#'));
+        // top-level block scalars need to be indented if followed by a comment
+        ctx.forceBlockIndent = !!this.comment;
+        contentComment = this.contents.comment;
+      }
+      const onChompKeep = contentComment ? null : () => chompKeep = true;
+      const body = stringify$1(this.contents, ctx, () => contentComment = null, onChompKeep);
+      lines.push(addComment(body, '', contentComment));
+    } else if (this.contents !== undefined) {
+      lines.push(stringify$1(this.contents, ctx));
+    }
+    if (this.comment) {
+      if ((!chompKeep || contentComment) && lines[lines.length - 1] !== '') lines.push('');
+      lines.push(this.comment.replace(/^/gm, '#'));
+    }
+    return lines.join('\n') + '\n';
+  }
+}
+_defineProperty(Document$1, "defaults", documentOptions);
+
+function createNode(value, wrapScalars = true, tag) {
+  if (tag === undefined && typeof wrapScalars === 'string') {
+    tag = wrapScalars;
+    wrapScalars = true;
+  }
+  const options = Object.assign({}, Document$1.defaults[defaultOptions.version], defaultOptions);
+  const schema = new Schema(options);
+  return schema.createNode(value, wrapScalars, tag);
+}
+class Document extends Document$1 {
+  constructor(options) {
+    super(Object.assign({}, defaultOptions, options));
+  }
+}
+function parseAllDocuments(src, options) {
+  const stream = [];
+  let prev;
+  for (const cstDoc of parse$1(src)) {
+    const doc = new Document(options);
+    doc.parse(cstDoc, prev);
+    stream.push(doc);
+    prev = doc;
+  }
+  return stream;
+}
+function parseDocument(src, options) {
+  const cst = parse$1(src);
+  const doc = new Document(options).parse(cst[0]);
+  if (cst.length > 1) {
+    const errMsg = 'Source contains multiple documents; please use YAML.parseAllDocuments()';
+    doc.errors.unshift(new YAMLSemanticError(cst[1], errMsg));
+  }
+  return doc;
+}
+function parse(src, options) {
+  const doc = parseDocument(src, options);
+  doc.warnings.forEach(warning => warn(warning));
+  if (doc.errors.length > 0) throw doc.errors[0];
+  return doc.toJSON();
+}
+function stringify(value, options) {
+  const doc = new Document(options);
+  doc.contents = value;
+  return String(doc);
+}
+const YAML = {
+  createNode,
+  defaultOptions,
+  Document,
+  parse,
+  parseAllDocuments,
+  parseCST: parse$1,
+  parseDocument,
+  scalarOptions,
+  stringify
+};
+
+export { YAML };
Index: frontend/node_modules/yaml/browser/dist/legacy-exports.js
===================================================================
--- frontend/node_modules/yaml/browser/dist/legacy-exports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/dist/legacy-exports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+export { b as binary, f as floatTime, i as intTime, o as omap, p as pairs, s as set, t as timestamp, c as warnFileDeprecation } from './warnings-5e4358fe.js';
+import './PlainValue-183afbad.js';
+import './resolveSeq-67caf78a.js';
Index: frontend/node_modules/yaml/browser/dist/package.json
===================================================================
--- frontend/node_modules/yaml/browser/dist/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/dist/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+{ "type": "module" }
Index: frontend/node_modules/yaml/browser/dist/parse-cst.js
===================================================================
--- frontend/node_modules/yaml/browser/dist/parse-cst.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/dist/parse-cst.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1505 @@
+import { N as Node, T as Type, R as Range, b as YAMLSemanticError, Y as YAMLSyntaxError, C as Char, _ as _defineProperty, P as PlainValue } from './PlainValue-183afbad.js';
+
+class BlankLine extends Node {
+  constructor() {
+    super(Type.BLANK_LINE);
+  }
+
+  /* istanbul ignore next */
+  get includesTrailingLines() {
+    // This is never called from anywhere, but if it were,
+    // this is the value it should return.
+    return true;
+  }
+
+  /**
+   * Parses a blank line from the source
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first \n character
+   * @returns {number} - Index of the character after this
+   */
+  parse(context, start) {
+    this.context = context;
+    this.range = new Range(start, start + 1);
+    return start + 1;
+  }
+}
+
+class CollectionItem extends Node {
+  constructor(type, props) {
+    super(type, props);
+    this.node = null;
+  }
+  get includesTrailingLines() {
+    return !!this.node && this.node.includesTrailingLines;
+  }
+
+  /**
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      parseNode,
+      src
+    } = context;
+    let {
+      atLineStart,
+      lineStart
+    } = context;
+    if (!atLineStart && this.type === Type.SEQ_ITEM) this.error = new YAMLSemanticError(this, 'Sequence items must not have preceding content on the same line');
+    const indent = atLineStart ? start - lineStart : context.indent;
+    let offset = Node.endOfWhiteSpace(src, start + 1);
+    let ch = src[offset];
+    const inlineComment = ch === '#';
+    const comments = [];
+    let blankLine = null;
+    while (ch === '\n' || ch === '#') {
+      if (ch === '#') {
+        const end = Node.endOfLine(src, offset + 1);
+        comments.push(new Range(offset, end));
+        offset = end;
+      } else {
+        atLineStart = true;
+        lineStart = offset + 1;
+        const wsEnd = Node.endOfWhiteSpace(src, lineStart);
+        if (src[wsEnd] === '\n' && comments.length === 0) {
+          blankLine = new BlankLine();
+          lineStart = blankLine.parse({
+            src
+          }, lineStart);
+        }
+        offset = Node.endOfIndent(src, lineStart);
+      }
+      ch = src[offset];
+    }
+    if (Node.nextNodeIsIndented(ch, offset - (lineStart + indent), this.type !== Type.SEQ_ITEM)) {
+      this.node = parseNode({
+        atLineStart,
+        inCollection: false,
+        indent,
+        lineStart,
+        parent: this
+      }, offset);
+    } else if (ch && lineStart > start + 1) {
+      offset = lineStart - 1;
+    }
+    if (this.node) {
+      if (blankLine) {
+        // Only blank lines preceding non-empty nodes are captured. Note that
+        // this means that collection item range start indices do not always
+        // increase monotonically. -- eemeli/yaml#126
+        const items = context.parent.items || context.parent.contents;
+        if (items) items.push(blankLine);
+      }
+      if (comments.length) Array.prototype.push.apply(this.props, comments);
+      offset = this.node.range.end;
+    } else {
+      if (inlineComment) {
+        const c = comments[0];
+        this.props.push(c);
+        offset = c.end;
+      } else {
+        offset = Node.endOfLine(src, start + 1);
+      }
+    }
+    const end = this.node ? this.node.valueRange.end : offset;
+    this.valueRange = new Range(start, end);
+    return offset;
+  }
+  setOrigRanges(cr, offset) {
+    offset = super.setOrigRanges(cr, offset);
+    return this.node ? this.node.setOrigRanges(cr, offset) : offset;
+  }
+  toString() {
+    const {
+      context: {
+        src
+      },
+      node,
+      range,
+      value
+    } = this;
+    if (value != null) return value;
+    const str = node ? src.slice(range.start, node.range.start) + String(node) : src.slice(range.start, range.end);
+    return Node.addStringTerminator(src, range.end, str);
+  }
+}
+
+class Comment extends Node {
+  constructor() {
+    super(Type.COMMENT);
+  }
+
+  /**
+   * Parses a comment line from the source
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this scalar
+   */
+  parse(context, start) {
+    this.context = context;
+    const offset = this.parseComment(start);
+    this.range = new Range(start, offset);
+    return offset;
+  }
+}
+
+function grabCollectionEndComments(node) {
+  let cnode = node;
+  while (cnode instanceof CollectionItem) cnode = cnode.node;
+  if (!(cnode instanceof Collection)) return null;
+  const len = cnode.items.length;
+  let ci = -1;
+  for (let i = len - 1; i >= 0; --i) {
+    const n = cnode.items[i];
+    if (n.type === Type.COMMENT) {
+      // Keep sufficiently indented comments with preceding node
+      const {
+        indent,
+        lineStart
+      } = n.context;
+      if (indent > 0 && n.range.start >= lineStart + indent) break;
+      ci = i;
+    } else if (n.type === Type.BLANK_LINE) ci = i;else break;
+  }
+  if (ci === -1) return null;
+  const ca = cnode.items.splice(ci, len - ci);
+  const prevEnd = ca[0].range.start;
+  while (true) {
+    cnode.range.end = prevEnd;
+    if (cnode.valueRange && cnode.valueRange.end > prevEnd) cnode.valueRange.end = prevEnd;
+    if (cnode === node) break;
+    cnode = cnode.context.parent;
+  }
+  return ca;
+}
+class Collection extends Node {
+  static nextContentHasIndent(src, offset, indent) {
+    const lineStart = Node.endOfLine(src, offset) + 1;
+    offset = Node.endOfWhiteSpace(src, lineStart);
+    const ch = src[offset];
+    if (!ch) return false;
+    if (offset >= lineStart + indent) return true;
+    if (ch !== '#' && ch !== '\n') return false;
+    return Collection.nextContentHasIndent(src, offset, indent);
+  }
+  constructor(firstItem) {
+    super(firstItem.type === Type.SEQ_ITEM ? Type.SEQ : Type.MAP);
+    for (let i = firstItem.props.length - 1; i >= 0; --i) {
+      if (firstItem.props[i].start < firstItem.context.lineStart) {
+        // props on previous line are assumed by the collection
+        this.props = firstItem.props.slice(0, i + 1);
+        firstItem.props = firstItem.props.slice(i + 1);
+        const itemRange = firstItem.props[0] || firstItem.valueRange;
+        firstItem.range.start = itemRange.start;
+        break;
+      }
+    }
+    this.items = [firstItem];
+    const ec = grabCollectionEndComments(firstItem);
+    if (ec) Array.prototype.push.apply(this.items, ec);
+  }
+  get includesTrailingLines() {
+    return this.items.length > 0;
+  }
+
+  /**
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      parseNode,
+      src
+    } = context;
+    // It's easier to recalculate lineStart here rather than tracking down the
+    // last context from which to read it -- eemeli/yaml#2
+    let lineStart = Node.startOfLine(src, start);
+    const firstItem = this.items[0];
+    // First-item context needs to be correct for later comment handling
+    // -- eemeli/yaml#17
+    firstItem.context.parent = this;
+    this.valueRange = Range.copy(firstItem.valueRange);
+    const indent = firstItem.range.start - firstItem.context.lineStart;
+    let offset = start;
+    offset = Node.normalizeOffset(src, offset);
+    let ch = src[offset];
+    let atLineStart = Node.endOfWhiteSpace(src, lineStart) === offset;
+    let prevIncludesTrailingLines = false;
+    while (ch) {
+      while (ch === '\n' || ch === '#') {
+        if (atLineStart && ch === '\n' && !prevIncludesTrailingLines) {
+          const blankLine = new BlankLine();
+          offset = blankLine.parse({
+            src
+          }, offset);
+          this.valueRange.end = offset;
+          if (offset >= src.length) {
+            ch = null;
+            break;
+          }
+          this.items.push(blankLine);
+          offset -= 1; // blankLine.parse() consumes terminal newline
+        } else if (ch === '#') {
+          if (offset < lineStart + indent && !Collection.nextContentHasIndent(src, offset, indent)) {
+            return offset;
+          }
+          const comment = new Comment();
+          offset = comment.parse({
+            indent,
+            lineStart,
+            src
+          }, offset);
+          this.items.push(comment);
+          this.valueRange.end = offset;
+          if (offset >= src.length) {
+            ch = null;
+            break;
+          }
+        }
+        lineStart = offset + 1;
+        offset = Node.endOfIndent(src, lineStart);
+        if (Node.atBlank(src, offset)) {
+          const wsEnd = Node.endOfWhiteSpace(src, offset);
+          const next = src[wsEnd];
+          if (!next || next === '\n' || next === '#') {
+            offset = wsEnd;
+          }
+        }
+        ch = src[offset];
+        atLineStart = true;
+      }
+      if (!ch) {
+        break;
+      }
+      if (offset !== lineStart + indent && (atLineStart || ch !== ':')) {
+        if (offset < lineStart + indent) {
+          if (lineStart > start) offset = lineStart;
+          break;
+        } else if (!this.error) {
+          const msg = 'All collection items must start at the same column';
+          this.error = new YAMLSyntaxError(this, msg);
+        }
+      }
+      if (firstItem.type === Type.SEQ_ITEM) {
+        if (ch !== '-') {
+          if (lineStart > start) offset = lineStart;
+          break;
+        }
+      } else if (ch === '-' && !this.error) {
+        // map key may start with -, as long as it's followed by a non-whitespace char
+        const next = src[offset + 1];
+        if (!next || next === '\n' || next === '\t' || next === ' ') {
+          const msg = 'A collection cannot be both a mapping and a sequence';
+          this.error = new YAMLSyntaxError(this, msg);
+        }
+      }
+      const node = parseNode({
+        atLineStart,
+        inCollection: true,
+        indent,
+        lineStart,
+        parent: this
+      }, offset);
+      if (!node) return offset; // at next document start
+      this.items.push(node);
+      this.valueRange.end = node.valueRange.end;
+      offset = Node.normalizeOffset(src, node.range.end);
+      ch = src[offset];
+      atLineStart = false;
+      prevIncludesTrailingLines = node.includesTrailingLines;
+      // Need to reset lineStart and atLineStart here if preceding node's range
+      // has advanced to check the current line's indentation level
+      // -- eemeli/yaml#10 & eemeli/yaml#38
+      if (ch) {
+        let ls = offset - 1;
+        let prev = src[ls];
+        while (prev === ' ' || prev === '\t') prev = src[--ls];
+        if (prev === '\n') {
+          lineStart = ls + 1;
+          atLineStart = true;
+        }
+      }
+      const ec = grabCollectionEndComments(node);
+      if (ec) Array.prototype.push.apply(this.items, ec);
+    }
+    return offset;
+  }
+  setOrigRanges(cr, offset) {
+    offset = super.setOrigRanges(cr, offset);
+    this.items.forEach(node => {
+      offset = node.setOrigRanges(cr, offset);
+    });
+    return offset;
+  }
+  toString() {
+    const {
+      context: {
+        src
+      },
+      items,
+      range,
+      value
+    } = this;
+    if (value != null) return value;
+    let str = src.slice(range.start, items[0].range.start) + String(items[0]);
+    for (let i = 1; i < items.length; ++i) {
+      const item = items[i];
+      const {
+        atLineStart,
+        indent
+      } = item.context;
+      if (atLineStart) for (let i = 0; i < indent; ++i) str += ' ';
+      str += String(item);
+    }
+    return Node.addStringTerminator(src, range.end, str);
+  }
+}
+
+class Directive extends Node {
+  constructor() {
+    super(Type.DIRECTIVE);
+    this.name = null;
+  }
+  get parameters() {
+    const raw = this.rawValue;
+    return raw ? raw.trim().split(/[ \t]+/) : [];
+  }
+  parseName(start) {
+    const {
+      src
+    } = this.context;
+    let offset = start;
+    let ch = src[offset];
+    while (ch && ch !== '\n' && ch !== '\t' && ch !== ' ') ch = src[offset += 1];
+    this.name = src.slice(start, offset);
+    return offset;
+  }
+  parseParameters(start) {
+    const {
+      src
+    } = this.context;
+    let offset = start;
+    let ch = src[offset];
+    while (ch && ch !== '\n' && ch !== '#') ch = src[offset += 1];
+    this.valueRange = new Range(start, offset);
+    return offset;
+  }
+  parse(context, start) {
+    this.context = context;
+    let offset = this.parseName(start + 1);
+    offset = this.parseParameters(offset);
+    offset = this.parseComment(offset);
+    this.range = new Range(start, offset);
+    return offset;
+  }
+}
+
+class Document extends Node {
+  static startCommentOrEndBlankLine(src, start) {
+    const offset = Node.endOfWhiteSpace(src, start);
+    const ch = src[offset];
+    return ch === '#' || ch === '\n' ? offset : start;
+  }
+  constructor() {
+    super(Type.DOCUMENT);
+    this.directives = null;
+    this.contents = null;
+    this.directivesEndMarker = null;
+    this.documentEndMarker = null;
+  }
+  parseDirectives(start) {
+    const {
+      src
+    } = this.context;
+    this.directives = [];
+    let atLineStart = true;
+    let hasDirectives = false;
+    let offset = start;
+    while (!Node.atDocumentBoundary(src, offset, Char.DIRECTIVES_END)) {
+      offset = Document.startCommentOrEndBlankLine(src, offset);
+      switch (src[offset]) {
+        case '\n':
+          if (atLineStart) {
+            const blankLine = new BlankLine();
+            offset = blankLine.parse({
+              src
+            }, offset);
+            if (offset < src.length) {
+              this.directives.push(blankLine);
+            }
+          } else {
+            offset += 1;
+            atLineStart = true;
+          }
+          break;
+        case '#':
+          {
+            const comment = new Comment();
+            offset = comment.parse({
+              src
+            }, offset);
+            this.directives.push(comment);
+            atLineStart = false;
+          }
+          break;
+        case '%':
+          {
+            const directive = new Directive();
+            offset = directive.parse({
+              parent: this,
+              src
+            }, offset);
+            this.directives.push(directive);
+            hasDirectives = true;
+            atLineStart = false;
+          }
+          break;
+        default:
+          if (hasDirectives) {
+            this.error = new YAMLSemanticError(this, 'Missing directives-end indicator line');
+          } else if (this.directives.length > 0) {
+            this.contents = this.directives;
+            this.directives = [];
+          }
+          return offset;
+      }
+    }
+    if (src[offset]) {
+      this.directivesEndMarker = new Range(offset, offset + 3);
+      return offset + 3;
+    }
+    if (hasDirectives) {
+      this.error = new YAMLSemanticError(this, 'Missing directives-end indicator line');
+    } else if (this.directives.length > 0) {
+      this.contents = this.directives;
+      this.directives = [];
+    }
+    return offset;
+  }
+  parseContents(start) {
+    const {
+      parseNode,
+      src
+    } = this.context;
+    if (!this.contents) this.contents = [];
+    let lineStart = start;
+    while (src[lineStart - 1] === '-') lineStart -= 1;
+    let offset = Node.endOfWhiteSpace(src, start);
+    let atLineStart = lineStart === start;
+    this.valueRange = new Range(offset);
+    while (!Node.atDocumentBoundary(src, offset, Char.DOCUMENT_END)) {
+      switch (src[offset]) {
+        case '\n':
+          if (atLineStart) {
+            const blankLine = new BlankLine();
+            offset = blankLine.parse({
+              src
+            }, offset);
+            if (offset < src.length) {
+              this.contents.push(blankLine);
+            }
+          } else {
+            offset += 1;
+            atLineStart = true;
+          }
+          lineStart = offset;
+          break;
+        case '#':
+          {
+            const comment = new Comment();
+            offset = comment.parse({
+              src
+            }, offset);
+            this.contents.push(comment);
+            atLineStart = false;
+          }
+          break;
+        default:
+          {
+            const iEnd = Node.endOfIndent(src, offset);
+            const context = {
+              atLineStart,
+              indent: -1,
+              inFlow: false,
+              inCollection: false,
+              lineStart,
+              parent: this
+            };
+            const node = parseNode(context, iEnd);
+            if (!node) return this.valueRange.end = iEnd; // at next document start
+            this.contents.push(node);
+            offset = node.range.end;
+            atLineStart = false;
+            const ec = grabCollectionEndComments(node);
+            if (ec) Array.prototype.push.apply(this.contents, ec);
+          }
+      }
+      offset = Document.startCommentOrEndBlankLine(src, offset);
+    }
+    this.valueRange.end = offset;
+    if (src[offset]) {
+      this.documentEndMarker = new Range(offset, offset + 3);
+      offset += 3;
+      if (src[offset]) {
+        offset = Node.endOfWhiteSpace(src, offset);
+        if (src[offset] === '#') {
+          const comment = new Comment();
+          offset = comment.parse({
+            src
+          }, offset);
+          this.contents.push(comment);
+        }
+        switch (src[offset]) {
+          case '\n':
+            offset += 1;
+            break;
+          case undefined:
+            break;
+          default:
+            this.error = new YAMLSyntaxError(this, 'Document end marker line cannot have a non-comment suffix');
+        }
+      }
+    }
+    return offset;
+  }
+
+  /**
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this
+   */
+  parse(context, start) {
+    context.root = this;
+    this.context = context;
+    const {
+      src
+    } = context;
+    let offset = src.charCodeAt(start) === 0xfeff ? start + 1 : start; // skip BOM
+    offset = this.parseDirectives(offset);
+    offset = this.parseContents(offset);
+    return offset;
+  }
+  setOrigRanges(cr, offset) {
+    offset = super.setOrigRanges(cr, offset);
+    this.directives.forEach(node => {
+      offset = node.setOrigRanges(cr, offset);
+    });
+    if (this.directivesEndMarker) offset = this.directivesEndMarker.setOrigRange(cr, offset);
+    this.contents.forEach(node => {
+      offset = node.setOrigRanges(cr, offset);
+    });
+    if (this.documentEndMarker) offset = this.documentEndMarker.setOrigRange(cr, offset);
+    return offset;
+  }
+  toString() {
+    const {
+      contents,
+      directives,
+      value
+    } = this;
+    if (value != null) return value;
+    let str = directives.join('');
+    if (contents.length > 0) {
+      if (directives.length > 0 || contents[0].type === Type.COMMENT) str += '---\n';
+      str += contents.join('');
+    }
+    if (str[str.length - 1] !== '\n') str += '\n';
+    return str;
+  }
+}
+
+class Alias extends Node {
+  /**
+   * Parses an *alias from the source
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this scalar
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      src
+    } = context;
+    let offset = Node.endOfIdentifier(src, start + 1);
+    this.valueRange = new Range(start + 1, offset);
+    offset = Node.endOfWhiteSpace(src, offset);
+    offset = this.parseComment(offset);
+    return offset;
+  }
+}
+
+const Chomp = {
+  CLIP: 'CLIP',
+  KEEP: 'KEEP',
+  STRIP: 'STRIP'
+};
+class BlockValue extends Node {
+  constructor(type, props) {
+    super(type, props);
+    this.blockIndent = null;
+    this.chomping = Chomp.CLIP;
+    this.header = null;
+  }
+  get includesTrailingLines() {
+    return this.chomping === Chomp.KEEP;
+  }
+  get strValue() {
+    if (!this.valueRange || !this.context) return null;
+    let {
+      start,
+      end
+    } = this.valueRange;
+    const {
+      indent,
+      src
+    } = this.context;
+    if (this.valueRange.isEmpty()) return '';
+    let lastNewLine = null;
+    let ch = src[end - 1];
+    while (ch === '\n' || ch === '\t' || ch === ' ') {
+      end -= 1;
+      if (end <= start) {
+        if (this.chomping === Chomp.KEEP) break;else return ''; // probably never happens
+      }
+      if (ch === '\n') lastNewLine = end;
+      ch = src[end - 1];
+    }
+    let keepStart = end + 1;
+    if (lastNewLine) {
+      if (this.chomping === Chomp.KEEP) {
+        keepStart = lastNewLine;
+        end = this.valueRange.end;
+      } else {
+        end = lastNewLine;
+      }
+    }
+    const bi = indent + this.blockIndent;
+    const folded = this.type === Type.BLOCK_FOLDED;
+    let atStart = true;
+    let str = '';
+    let sep = '';
+    let prevMoreIndented = false;
+    for (let i = start; i < end; ++i) {
+      for (let j = 0; j < bi; ++j) {
+        if (src[i] !== ' ') break;
+        i += 1;
+      }
+      const ch = src[i];
+      if (ch === '\n') {
+        if (sep === '\n') str += '\n';else sep = '\n';
+      } else {
+        const lineEnd = Node.endOfLine(src, i);
+        const line = src.slice(i, lineEnd);
+        i = lineEnd;
+        if (folded && (ch === ' ' || ch === '\t') && i < keepStart) {
+          if (sep === ' ') sep = '\n';else if (!prevMoreIndented && !atStart && sep === '\n') sep = '\n\n';
+          str += sep + line; //+ ((lineEnd < end && src[lineEnd]) || '')
+          sep = lineEnd < end && src[lineEnd] || '';
+          prevMoreIndented = true;
+        } else {
+          str += sep + line;
+          sep = folded && i < keepStart ? ' ' : '\n';
+          prevMoreIndented = false;
+        }
+        if (atStart && line !== '') atStart = false;
+      }
+    }
+    return this.chomping === Chomp.STRIP ? str : str + '\n';
+  }
+  parseBlockHeader(start) {
+    const {
+      src
+    } = this.context;
+    let offset = start + 1;
+    let bi = '';
+    while (true) {
+      const ch = src[offset];
+      switch (ch) {
+        case '-':
+          this.chomping = Chomp.STRIP;
+          break;
+        case '+':
+          this.chomping = Chomp.KEEP;
+          break;
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+          bi += ch;
+          break;
+        default:
+          this.blockIndent = Number(bi) || null;
+          this.header = new Range(start, offset);
+          return offset;
+      }
+      offset += 1;
+    }
+  }
+  parseBlockValue(start) {
+    const {
+      indent,
+      src
+    } = this.context;
+    const explicit = !!this.blockIndent;
+    let offset = start;
+    let valueEnd = start;
+    let minBlockIndent = 1;
+    for (let ch = src[offset]; ch === '\n'; ch = src[offset]) {
+      offset += 1;
+      if (Node.atDocumentBoundary(src, offset)) break;
+      const end = Node.endOfBlockIndent(src, indent, offset); // should not include tab?
+      if (end === null) break;
+      const ch = src[end];
+      const lineIndent = end - (offset + indent);
+      if (!this.blockIndent) {
+        // no explicit block indent, none yet detected
+        if (src[end] !== '\n') {
+          // first line with non-whitespace content
+          if (lineIndent < minBlockIndent) {
+            const msg = 'Block scalars with more-indented leading empty lines must use an explicit indentation indicator';
+            this.error = new YAMLSemanticError(this, msg);
+          }
+          this.blockIndent = lineIndent;
+        } else if (lineIndent > minBlockIndent) {
+          // empty line with more whitespace
+          minBlockIndent = lineIndent;
+        }
+      } else if (ch && ch !== '\n' && lineIndent < this.blockIndent) {
+        if (src[end] === '#') break;
+        if (!this.error) {
+          const src = explicit ? 'explicit indentation indicator' : 'first line';
+          const msg = `Block scalars must not be less indented than their ${src}`;
+          this.error = new YAMLSemanticError(this, msg);
+        }
+      }
+      if (src[end] === '\n') {
+        offset = end;
+      } else {
+        offset = valueEnd = Node.endOfLine(src, end);
+      }
+    }
+    if (this.chomping !== Chomp.KEEP) {
+      offset = src[valueEnd] ? valueEnd + 1 : valueEnd;
+    }
+    this.valueRange = new Range(start + 1, offset);
+    return offset;
+  }
+
+  /**
+   * Parses a block value from the source
+   *
+   * Accepted forms are:
+   * ```
+   * BS
+   * block
+   * lines
+   *
+   * BS #comment
+   * block
+   * lines
+   * ```
+   * where the block style BS matches the regexp `[|>][-+1-9]*` and block lines
+   * are empty or have an indent level greater than `indent`.
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this block
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      src
+    } = context;
+    let offset = this.parseBlockHeader(start);
+    offset = Node.endOfWhiteSpace(src, offset);
+    offset = this.parseComment(offset);
+    offset = this.parseBlockValue(offset);
+    return offset;
+  }
+  setOrigRanges(cr, offset) {
+    offset = super.setOrigRanges(cr, offset);
+    return this.header ? this.header.setOrigRange(cr, offset) : offset;
+  }
+}
+
+class FlowCollection extends Node {
+  constructor(type, props) {
+    super(type, props);
+    this.items = null;
+  }
+  prevNodeIsJsonLike(idx = this.items.length) {
+    const node = this.items[idx - 1];
+    return !!node && (node.jsonLike || node.type === Type.COMMENT && this.prevNodeIsJsonLike(idx - 1));
+  }
+
+  /**
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      parseNode,
+      src
+    } = context;
+    let {
+      indent,
+      lineStart
+    } = context;
+    let char = src[start]; // { or [
+    this.items = [{
+      char,
+      offset: start
+    }];
+    let offset = Node.endOfWhiteSpace(src, start + 1);
+    char = src[offset];
+    while (char && char !== ']' && char !== '}') {
+      switch (char) {
+        case '\n':
+          {
+            lineStart = offset + 1;
+            const wsEnd = Node.endOfWhiteSpace(src, lineStart);
+            if (src[wsEnd] === '\n') {
+              const blankLine = new BlankLine();
+              lineStart = blankLine.parse({
+                src
+              }, lineStart);
+              this.items.push(blankLine);
+            }
+            offset = Node.endOfIndent(src, lineStart);
+            if (offset <= lineStart + indent) {
+              char = src[offset];
+              if (offset < lineStart + indent || char !== ']' && char !== '}') {
+                const msg = 'Insufficient indentation in flow collection';
+                this.error = new YAMLSemanticError(this, msg);
+              }
+            }
+          }
+          break;
+        case ',':
+          {
+            this.items.push({
+              char,
+              offset
+            });
+            offset += 1;
+          }
+          break;
+        case '#':
+          {
+            const comment = new Comment();
+            offset = comment.parse({
+              src
+            }, offset);
+            this.items.push(comment);
+          }
+          break;
+        case '?':
+        case ':':
+          {
+            const next = src[offset + 1];
+            if (next === '\n' || next === '\t' || next === ' ' || next === ',' ||
+            // in-flow : after JSON-like key does not need to be followed by whitespace
+            char === ':' && this.prevNodeIsJsonLike()) {
+              this.items.push({
+                char,
+                offset
+              });
+              offset += 1;
+              break;
+            }
+          }
+        // fallthrough
+        default:
+          {
+            const node = parseNode({
+              atLineStart: false,
+              inCollection: false,
+              inFlow: true,
+              indent: -1,
+              lineStart,
+              parent: this
+            }, offset);
+            if (!node) {
+              // at next document start
+              this.valueRange = new Range(start, offset);
+              return offset;
+            }
+            this.items.push(node);
+            offset = Node.normalizeOffset(src, node.range.end);
+          }
+      }
+      offset = Node.endOfWhiteSpace(src, offset);
+      char = src[offset];
+    }
+    this.valueRange = new Range(start, offset + 1);
+    if (char) {
+      this.items.push({
+        char,
+        offset
+      });
+      offset = Node.endOfWhiteSpace(src, offset + 1);
+      offset = this.parseComment(offset);
+    }
+    return offset;
+  }
+  setOrigRanges(cr, offset) {
+    offset = super.setOrigRanges(cr, offset);
+    this.items.forEach(node => {
+      if (node instanceof Node) {
+        offset = node.setOrigRanges(cr, offset);
+      } else if (cr.length === 0) {
+        node.origOffset = node.offset;
+      } else {
+        let i = offset;
+        while (i < cr.length) {
+          if (cr[i] > node.offset) break;else ++i;
+        }
+        node.origOffset = node.offset + i;
+        offset = i;
+      }
+    });
+    return offset;
+  }
+  toString() {
+    const {
+      context: {
+        src
+      },
+      items,
+      range,
+      value
+    } = this;
+    if (value != null) return value;
+    const nodes = items.filter(item => item instanceof Node);
+    let str = '';
+    let prevEnd = range.start;
+    nodes.forEach(node => {
+      const prefix = src.slice(prevEnd, node.range.start);
+      prevEnd = node.range.end;
+      str += prefix + String(node);
+      if (str[str.length - 1] === '\n' && src[prevEnd - 1] !== '\n' && src[prevEnd] === '\n') {
+        // Comment range does not include the terminal newline, but its
+        // stringified value does. Without this fix, newlines at comment ends
+        // get duplicated.
+        prevEnd += 1;
+      }
+    });
+    str += src.slice(prevEnd, range.end);
+    return Node.addStringTerminator(src, range.end, str);
+  }
+}
+
+class QuoteDouble extends Node {
+  static endOfQuote(src, offset) {
+    let ch = src[offset];
+    while (ch && ch !== '"') {
+      offset += ch === '\\' ? 2 : 1;
+      ch = src[offset];
+    }
+    return offset + 1;
+  }
+
+  /**
+   * @returns {string | { str: string, errors: YAMLSyntaxError[] }}
+   */
+  get strValue() {
+    if (!this.valueRange || !this.context) return null;
+    const errors = [];
+    const {
+      start,
+      end
+    } = this.valueRange;
+    const {
+      indent,
+      src
+    } = this.context;
+    if (src[end - 1] !== '"') errors.push(new YAMLSyntaxError(this, 'Missing closing "quote'));
+    // Using String#replace is too painful with escaped newlines preceded by
+    // escaped backslashes; also, this should be faster.
+    let str = '';
+    for (let i = start + 1; i < end - 1; ++i) {
+      const ch = src[i];
+      if (ch === '\n') {
+        if (Node.atDocumentBoundary(src, i + 1)) errors.push(new YAMLSemanticError(this, 'Document boundary indicators are not allowed within string values'));
+        const {
+          fold,
+          offset,
+          error
+        } = Node.foldNewline(src, i, indent);
+        str += fold;
+        i = offset;
+        if (error) errors.push(new YAMLSemanticError(this, 'Multi-line double-quoted string needs to be sufficiently indented'));
+      } else if (ch === '\\') {
+        i += 1;
+        switch (src[i]) {
+          case '0':
+            str += '\0';
+            break;
+          // null character
+          case 'a':
+            str += '\x07';
+            break;
+          // bell character
+          case 'b':
+            str += '\b';
+            break;
+          // backspace
+          case 'e':
+            str += '\x1b';
+            break;
+          // escape character
+          case 'f':
+            str += '\f';
+            break;
+          // form feed
+          case 'n':
+            str += '\n';
+            break;
+          // line feed
+          case 'r':
+            str += '\r';
+            break;
+          // carriage return
+          case 't':
+            str += '\t';
+            break;
+          // horizontal tab
+          case 'v':
+            str += '\v';
+            break;
+          // vertical tab
+          case 'N':
+            str += '\u0085';
+            break;
+          // Unicode next line
+          case '_':
+            str += '\u00a0';
+            break;
+          // Unicode non-breaking space
+          case 'L':
+            str += '\u2028';
+            break;
+          // Unicode line separator
+          case 'P':
+            str += '\u2029';
+            break;
+          // Unicode paragraph separator
+          case ' ':
+            str += ' ';
+            break;
+          case '"':
+            str += '"';
+            break;
+          case '/':
+            str += '/';
+            break;
+          case '\\':
+            str += '\\';
+            break;
+          case '\t':
+            str += '\t';
+            break;
+          case 'x':
+            str += this.parseCharCode(i + 1, 2, errors);
+            i += 2;
+            break;
+          case 'u':
+            str += this.parseCharCode(i + 1, 4, errors);
+            i += 4;
+            break;
+          case 'U':
+            str += this.parseCharCode(i + 1, 8, errors);
+            i += 8;
+            break;
+          case '\n':
+            // skip escaped newlines, but still trim the following line
+            while (src[i + 1] === ' ' || src[i + 1] === '\t') i += 1;
+            break;
+          default:
+            errors.push(new YAMLSyntaxError(this, `Invalid escape sequence ${src.substr(i - 1, 2)}`));
+            str += '\\' + src[i];
+        }
+      } else if (ch === ' ' || ch === '\t') {
+        // trim trailing whitespace
+        const wsStart = i;
+        let next = src[i + 1];
+        while (next === ' ' || next === '\t') {
+          i += 1;
+          next = src[i + 1];
+        }
+        if (next !== '\n') str += i > wsStart ? src.slice(wsStart, i + 1) : ch;
+      } else {
+        str += ch;
+      }
+    }
+    return errors.length > 0 ? {
+      errors,
+      str
+    } : str;
+  }
+  parseCharCode(offset, length, errors) {
+    const {
+      src
+    } = this.context;
+    const cc = src.substr(offset, length);
+    const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
+    const code = ok ? parseInt(cc, 16) : NaN;
+    if (isNaN(code)) {
+      errors.push(new YAMLSyntaxError(this, `Invalid escape sequence ${src.substr(offset - 2, length + 2)}`));
+      return src.substr(offset - 2, length + 2);
+    }
+    return String.fromCodePoint(code);
+  }
+
+  /**
+   * Parses a "double quoted" value from the source
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this scalar
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      src
+    } = context;
+    let offset = QuoteDouble.endOfQuote(src, start + 1);
+    this.valueRange = new Range(start, offset);
+    offset = Node.endOfWhiteSpace(src, offset);
+    offset = this.parseComment(offset);
+    return offset;
+  }
+}
+
+class QuoteSingle extends Node {
+  static endOfQuote(src, offset) {
+    let ch = src[offset];
+    while (ch) {
+      if (ch === "'") {
+        if (src[offset + 1] !== "'") break;
+        ch = src[offset += 2];
+      } else {
+        ch = src[offset += 1];
+      }
+    }
+    return offset + 1;
+  }
+
+  /**
+   * @returns {string | { str: string, errors: YAMLSyntaxError[] }}
+   */
+  get strValue() {
+    if (!this.valueRange || !this.context) return null;
+    const errors = [];
+    const {
+      start,
+      end
+    } = this.valueRange;
+    const {
+      indent,
+      src
+    } = this.context;
+    if (src[end - 1] !== "'") errors.push(new YAMLSyntaxError(this, "Missing closing 'quote"));
+    let str = '';
+    for (let i = start + 1; i < end - 1; ++i) {
+      const ch = src[i];
+      if (ch === '\n') {
+        if (Node.atDocumentBoundary(src, i + 1)) errors.push(new YAMLSemanticError(this, 'Document boundary indicators are not allowed within string values'));
+        const {
+          fold,
+          offset,
+          error
+        } = Node.foldNewline(src, i, indent);
+        str += fold;
+        i = offset;
+        if (error) errors.push(new YAMLSemanticError(this, 'Multi-line single-quoted string needs to be sufficiently indented'));
+      } else if (ch === "'") {
+        str += ch;
+        i += 1;
+        if (src[i] !== "'") errors.push(new YAMLSyntaxError(this, 'Unescaped single quote? This should not happen.'));
+      } else if (ch === ' ' || ch === '\t') {
+        // trim trailing whitespace
+        const wsStart = i;
+        let next = src[i + 1];
+        while (next === ' ' || next === '\t') {
+          i += 1;
+          next = src[i + 1];
+        }
+        if (next !== '\n') str += i > wsStart ? src.slice(wsStart, i + 1) : ch;
+      } else {
+        str += ch;
+      }
+    }
+    return errors.length > 0 ? {
+      errors,
+      str
+    } : str;
+  }
+
+  /**
+   * Parses a 'single quoted' value from the source
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this scalar
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      src
+    } = context;
+    let offset = QuoteSingle.endOfQuote(src, start + 1);
+    this.valueRange = new Range(start, offset);
+    offset = Node.endOfWhiteSpace(src, offset);
+    offset = this.parseComment(offset);
+    return offset;
+  }
+}
+
+function createNewNode(type, props) {
+  switch (type) {
+    case Type.ALIAS:
+      return new Alias(type, props);
+    case Type.BLOCK_FOLDED:
+    case Type.BLOCK_LITERAL:
+      return new BlockValue(type, props);
+    case Type.FLOW_MAP:
+    case Type.FLOW_SEQ:
+      return new FlowCollection(type, props);
+    case Type.MAP_KEY:
+    case Type.MAP_VALUE:
+    case Type.SEQ_ITEM:
+      return new CollectionItem(type, props);
+    case Type.COMMENT:
+    case Type.PLAIN:
+      return new PlainValue(type, props);
+    case Type.QUOTE_DOUBLE:
+      return new QuoteDouble(type, props);
+    case Type.QUOTE_SINGLE:
+      return new QuoteSingle(type, props);
+    /* istanbul ignore next */
+    default:
+      return null;
+    // should never happen
+  }
+}
+
+/**
+ * @param {boolean} atLineStart - Node starts at beginning of line
+ * @param {boolean} inFlow - true if currently in a flow context
+ * @param {boolean} inCollection - true if currently in a collection context
+ * @param {number} indent - Current level of indentation
+ * @param {number} lineStart - Start of the current line
+ * @param {Node} parent - The parent of the node
+ * @param {string} src - Source of the YAML document
+ */
+class ParseContext {
+  static parseType(src, offset, inFlow) {
+    switch (src[offset]) {
+      case '*':
+        return Type.ALIAS;
+      case '>':
+        return Type.BLOCK_FOLDED;
+      case '|':
+        return Type.BLOCK_LITERAL;
+      case '{':
+        return Type.FLOW_MAP;
+      case '[':
+        return Type.FLOW_SEQ;
+      case '?':
+        return !inFlow && Node.atBlank(src, offset + 1, true) ? Type.MAP_KEY : Type.PLAIN;
+      case ':':
+        return !inFlow && Node.atBlank(src, offset + 1, true) ? Type.MAP_VALUE : Type.PLAIN;
+      case '-':
+        return !inFlow && Node.atBlank(src, offset + 1, true) ? Type.SEQ_ITEM : Type.PLAIN;
+      case '"':
+        return Type.QUOTE_DOUBLE;
+      case "'":
+        return Type.QUOTE_SINGLE;
+      default:
+        return Type.PLAIN;
+    }
+  }
+  constructor(orig = {}, {
+    atLineStart,
+    inCollection,
+    inFlow,
+    indent,
+    lineStart,
+    parent
+  } = {}) {
+    /**
+     * Parses a node from the source
+     * @param {ParseContext} overlay
+     * @param {number} start - Index of first non-whitespace character for the node
+     * @returns {?Node} - null if at a document boundary
+     */
+    _defineProperty(this, "parseNode", (overlay, start) => {
+      if (Node.atDocumentBoundary(this.src, start)) return null;
+      const context = new ParseContext(this, overlay);
+      const {
+        props,
+        type,
+        valueStart
+      } = context.parseProps(start);
+      const node = createNewNode(type, props);
+      let offset = start;
+      try {
+        offset = node.parse(context, valueStart);
+      } catch (error) {
+        const msg = error instanceof Error ? error.message : String(error);
+        if (!node.error) node.error = new YAMLSyntaxError(node, msg);
+      }
+      node.range = new Range(start, offset);
+      /* istanbul ignore if */
+      if (offset <= start) {
+        // This should never happen, but if it does, let's make sure to at least
+        // step one character forward to avoid a busy loop.
+        if (!node.error) node.error = new Error(`Node#parse consumed no characters`);
+        node.error.parseEnd = offset;
+        node.error.source = node;
+        node.range.end = start + 1;
+      }
+      if (context.nodeStartsCollection(node)) {
+        if (!node.error && !context.atLineStart && context.parent.type === Type.DOCUMENT) {
+          node.error = new YAMLSyntaxError(node, 'Block collection must not have preceding content here (e.g. directives-end indicator)');
+        }
+        const collection = new Collection(node);
+        offset = collection.parse(new ParseContext(context), offset);
+        collection.range = new Range(start, offset);
+        return collection;
+      }
+      return node;
+    });
+    this.atLineStart = atLineStart != null ? atLineStart : orig.atLineStart || false;
+    this.inCollection = inCollection != null ? inCollection : orig.inCollection || false;
+    this.inFlow = inFlow != null ? inFlow : orig.inFlow || false;
+    this.indent = indent != null ? indent : orig.indent;
+    this.lineStart = lineStart != null ? lineStart : orig.lineStart;
+    this.parent = parent != null ? parent : orig.parent || {};
+    this.root = orig.root;
+    this.src = orig.src;
+  }
+  nodeStartsCollection(node) {
+    const {
+      inCollection,
+      inFlow,
+      src
+    } = this;
+    if (inCollection || inFlow) return false;
+    if (node instanceof CollectionItem) return true;
+    // check for implicit key
+    let offset = node.range.end;
+    if (src[offset] === '\n' || src[offset - 1] === '\n') return false;
+    offset = Node.endOfWhiteSpace(src, offset);
+    return src[offset] === ':';
+  }
+
+  // Anchor and tag are before type, which determines the node implementation
+  // class; hence this intermediate step.
+  parseProps(offset) {
+    const {
+      inFlow,
+      parent,
+      src
+    } = this;
+    const props = [];
+    let lineHasProps = false;
+    offset = this.atLineStart ? Node.endOfIndent(src, offset) : Node.endOfWhiteSpace(src, offset);
+    let ch = src[offset];
+    while (ch === Char.ANCHOR || ch === Char.COMMENT || ch === Char.TAG || ch === '\n') {
+      if (ch === '\n') {
+        let inEnd = offset;
+        let lineStart;
+        do {
+          lineStart = inEnd + 1;
+          inEnd = Node.endOfIndent(src, lineStart);
+        } while (src[inEnd] === '\n');
+        const indentDiff = inEnd - (lineStart + this.indent);
+        const noIndicatorAsIndent = parent.type === Type.SEQ_ITEM && parent.context.atLineStart;
+        if (src[inEnd] !== '#' && !Node.nextNodeIsIndented(src[inEnd], indentDiff, !noIndicatorAsIndent)) break;
+        this.atLineStart = true;
+        this.lineStart = lineStart;
+        lineHasProps = false;
+        offset = inEnd;
+      } else if (ch === Char.COMMENT) {
+        const end = Node.endOfLine(src, offset + 1);
+        props.push(new Range(offset, end));
+        offset = end;
+      } else {
+        let end = Node.endOfIdentifier(src, offset + 1);
+        if (ch === Char.TAG && src[end] === ',' && /^[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+,\d\d\d\d(-\d\d){0,2}\/\S/.test(src.slice(offset + 1, end + 13))) {
+          // Let's presume we're dealing with a YAML 1.0 domain tag here, rather
+          // than an empty but 'foo.bar' private-tagged node in a flow collection
+          // followed without whitespace by a plain string starting with a year
+          // or date divided by something.
+          end = Node.endOfIdentifier(src, end + 5);
+        }
+        props.push(new Range(offset, end));
+        lineHasProps = true;
+        offset = Node.endOfWhiteSpace(src, end);
+      }
+      ch = src[offset];
+    }
+    // '- &a : b' has an anchor on an empty node
+    if (lineHasProps && ch === ':' && Node.atBlank(src, offset + 1, true)) offset -= 1;
+    const type = ParseContext.parseType(src, offset, inFlow);
+    return {
+      props,
+      type,
+      valueStart: offset
+    };
+  }
+}
+
+// Published as 'yaml/parse-cst'
+function parse(src) {
+  const cr = [];
+  if (src.indexOf('\r') !== -1) {
+    src = src.replace(/\r\n?/g, (match, offset) => {
+      if (match.length > 1) cr.push(offset);
+      return '\n';
+    });
+  }
+  const documents = [];
+  let offset = 0;
+  do {
+    const doc = new Document();
+    const context = new ParseContext({
+      src
+    });
+    offset = doc.parse(context, offset);
+    documents.push(doc);
+  } while (offset < src.length);
+  documents.setOrigRanges = () => {
+    if (cr.length === 0) return false;
+    for (let i = 1; i < cr.length; ++i) cr[i] -= i;
+    let crOffset = 0;
+    for (let i = 0; i < documents.length; ++i) {
+      crOffset = documents[i].setOrigRanges(cr, crOffset);
+    }
+    cr.splice(0, cr.length);
+    return true;
+  };
+  documents.toString = () => documents.join('...\n');
+  return documents;
+}
+
+export { parse };
Index: frontend/node_modules/yaml/browser/dist/resolveSeq-67caf78a.js
===================================================================
--- frontend/node_modules/yaml/browser/dist/resolveSeq-67caf78a.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/dist/resolveSeq-67caf78a.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1835 @@
+import { _ as _defineProperty, T as Type, f as YAMLReferenceError, b as YAMLSemanticError, e as defaultTags, a as YAMLWarning, c as YAMLError, C as Char, Y as YAMLSyntaxError, P as PlainValue } from './PlainValue-183afbad.js';
+
+function addCommentBefore(str, indent, comment) {
+  if (!comment) return str;
+  const cc = comment.replace(/[\s\S]^/gm, `$&${indent}#`);
+  return `#${cc}\n${indent}${str}`;
+}
+function addComment(str, indent, comment) {
+  return !comment ? str : comment.indexOf('\n') === -1 ? `${str} #${comment}` : `${str}\n` + comment.replace(/^/gm, `${indent || ''}#`);
+}
+
+class Node {}
+
+function toJSON(value, arg, ctx) {
+  if (Array.isArray(value)) return value.map((v, i) => toJSON(v, String(i), ctx));
+  if (value && typeof value.toJSON === 'function') {
+    const anchor = ctx && ctx.anchors && ctx.anchors.get(value);
+    if (anchor) ctx.onCreate = res => {
+      anchor.res = res;
+      delete ctx.onCreate;
+    };
+    const res = value.toJSON(arg, ctx);
+    if (anchor && ctx.onCreate) ctx.onCreate(res);
+    return res;
+  }
+  if ((!ctx || !ctx.keep) && typeof value === 'bigint') return Number(value);
+  return value;
+}
+
+class Scalar extends Node {
+  constructor(value) {
+    super();
+    this.value = value;
+  }
+  toJSON(arg, ctx) {
+    return ctx && ctx.keep ? this.value : toJSON(this.value, arg, ctx);
+  }
+  toString() {
+    return String(this.value);
+  }
+}
+
+function collectionFromPath(schema, path, value) {
+  let v = value;
+  for (let i = path.length - 1; i >= 0; --i) {
+    const k = path[i];
+    if (Number.isInteger(k) && k >= 0) {
+      const a = [];
+      a[k] = v;
+      v = a;
+    } else {
+      const o = {};
+      Object.defineProperty(o, k, {
+        value: v,
+        writable: true,
+        enumerable: true,
+        configurable: true
+      });
+      v = o;
+    }
+  }
+  return schema.createNode(v, false);
+}
+
+// null, undefined, or an empty non-string iterable (e.g. [])
+const isEmptyPath = path => path == null || typeof path === 'object' && path[Symbol.iterator]().next().done;
+class Collection extends Node {
+  constructor(schema) {
+    super();
+    _defineProperty(this, "items", []);
+    this.schema = schema;
+  }
+  addIn(path, value) {
+    if (isEmptyPath(path)) this.add(value);else {
+      const [key, ...rest] = path;
+      const node = this.get(key, true);
+      if (node instanceof Collection) node.addIn(rest, value);else if (node === undefined && this.schema) this.set(key, collectionFromPath(this.schema, rest, value));else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
+    }
+  }
+  deleteIn([key, ...rest]) {
+    if (rest.length === 0) return this.delete(key);
+    const node = this.get(key, true);
+    if (node instanceof Collection) return node.deleteIn(rest);else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
+  }
+  getIn([key, ...rest], keepScalar) {
+    const node = this.get(key, true);
+    if (rest.length === 0) return !keepScalar && node instanceof Scalar ? node.value : node;else return node instanceof Collection ? node.getIn(rest, keepScalar) : undefined;
+  }
+  hasAllNullValues() {
+    return this.items.every(node => {
+      if (!node || node.type !== 'PAIR') return false;
+      const n = node.value;
+      return n == null || n instanceof Scalar && n.value == null && !n.commentBefore && !n.comment && !n.tag;
+    });
+  }
+  hasIn([key, ...rest]) {
+    if (rest.length === 0) return this.has(key);
+    const node = this.get(key, true);
+    return node instanceof Collection ? node.hasIn(rest) : false;
+  }
+  setIn([key, ...rest], value) {
+    if (rest.length === 0) {
+      this.set(key, value);
+    } else {
+      const node = this.get(key, true);
+      if (node instanceof Collection) node.setIn(rest, value);else if (node === undefined && this.schema) this.set(key, collectionFromPath(this.schema, rest, value));else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
+    }
+  }
+
+  // overridden in implementations
+  /* istanbul ignore next */
+  toJSON() {
+    return null;
+  }
+  toString(ctx, {
+    blockItem,
+    flowChars,
+    isMap,
+    itemIndent
+  }, onComment, onChompKeep) {
+    const {
+      indent,
+      indentStep,
+      stringify
+    } = ctx;
+    const inFlow = this.type === Type.FLOW_MAP || this.type === Type.FLOW_SEQ || ctx.inFlow;
+    if (inFlow) itemIndent += indentStep;
+    const allNullValues = isMap && this.hasAllNullValues();
+    ctx = Object.assign({}, ctx, {
+      allNullValues,
+      indent: itemIndent,
+      inFlow,
+      type: null
+    });
+    let chompKeep = false;
+    let hasItemWithNewLine = false;
+    const nodes = this.items.reduce((nodes, item, i) => {
+      let comment;
+      if (item) {
+        if (!chompKeep && item.spaceBefore) nodes.push({
+          type: 'comment',
+          str: ''
+        });
+        if (item.commentBefore) item.commentBefore.match(/^.*$/gm).forEach(line => {
+          nodes.push({
+            type: 'comment',
+            str: `#${line}`
+          });
+        });
+        if (item.comment) comment = item.comment;
+        if (inFlow && (!chompKeep && item.spaceBefore || item.commentBefore || item.comment || item.key && (item.key.commentBefore || item.key.comment) || item.value && (item.value.commentBefore || item.value.comment))) hasItemWithNewLine = true;
+      }
+      chompKeep = false;
+      let str = stringify(item, ctx, () => comment = null, () => chompKeep = true);
+      if (inFlow && !hasItemWithNewLine && str.includes('\n')) hasItemWithNewLine = true;
+      if (inFlow && i < this.items.length - 1) str += ',';
+      str = addComment(str, itemIndent, comment);
+      if (chompKeep && (comment || inFlow)) chompKeep = false;
+      nodes.push({
+        type: 'item',
+        str
+      });
+      return nodes;
+    }, []);
+    let str;
+    if (nodes.length === 0) {
+      str = flowChars.start + flowChars.end;
+    } else if (inFlow) {
+      const {
+        start,
+        end
+      } = flowChars;
+      const strings = nodes.map(n => n.str);
+      if (hasItemWithNewLine || strings.reduce((sum, str) => sum + str.length + 2, 2) > Collection.maxFlowStringSingleLineLength) {
+        str = start;
+        for (const s of strings) {
+          str += s ? `\n${indentStep}${indent}${s}` : '\n';
+        }
+        str += `\n${indent}${end}`;
+      } else {
+        str = `${start} ${strings.join(' ')} ${end}`;
+      }
+    } else {
+      const strings = nodes.map(blockItem);
+      str = strings.shift();
+      for (const s of strings) str += s ? `\n${indent}${s}` : '\n';
+    }
+    if (this.comment) {
+      str += '\n' + this.comment.replace(/^/gm, `${indent}#`);
+      if (onComment) onComment();
+    } else if (chompKeep && onChompKeep) onChompKeep();
+    return str;
+  }
+}
+_defineProperty(Collection, "maxFlowStringSingleLineLength", 60);
+
+function asItemIndex(key) {
+  let idx = key instanceof Scalar ? key.value : key;
+  if (idx && typeof idx === 'string') idx = Number(idx);
+  return Number.isInteger(idx) && idx >= 0 ? idx : null;
+}
+class YAMLSeq extends Collection {
+  add(value) {
+    this.items.push(value);
+  }
+  delete(key) {
+    const idx = asItemIndex(key);
+    if (typeof idx !== 'number') return false;
+    const del = this.items.splice(idx, 1);
+    return del.length > 0;
+  }
+  get(key, keepScalar) {
+    const idx = asItemIndex(key);
+    if (typeof idx !== 'number') return undefined;
+    const it = this.items[idx];
+    return !keepScalar && it instanceof Scalar ? it.value : it;
+  }
+  has(key) {
+    const idx = asItemIndex(key);
+    return typeof idx === 'number' && idx < this.items.length;
+  }
+  set(key, value) {
+    const idx = asItemIndex(key);
+    if (typeof idx !== 'number') throw new Error(`Expected a valid index, not ${key}.`);
+    this.items[idx] = value;
+  }
+  toJSON(_, ctx) {
+    const seq = [];
+    if (ctx && ctx.onCreate) ctx.onCreate(seq);
+    let i = 0;
+    for (const item of this.items) seq.push(toJSON(item, String(i++), ctx));
+    return seq;
+  }
+  toString(ctx, onComment, onChompKeep) {
+    if (!ctx) return JSON.stringify(this);
+    return super.toString(ctx, {
+      blockItem: n => n.type === 'comment' ? n.str : `- ${n.str}`,
+      flowChars: {
+        start: '[',
+        end: ']'
+      },
+      isMap: false,
+      itemIndent: (ctx.indent || '') + '  '
+    }, onComment, onChompKeep);
+  }
+}
+
+const stringifyKey = (key, jsKey, ctx) => {
+  if (jsKey === null) return '';
+  if (typeof jsKey !== 'object') return String(jsKey);
+  if (key instanceof Node && ctx && ctx.doc) return key.toString({
+    anchors: Object.create(null),
+    doc: ctx.doc,
+    indent: '',
+    indentStep: ctx.indentStep,
+    inFlow: true,
+    inStringifyKey: true,
+    stringify: ctx.stringify
+  });
+  return JSON.stringify(jsKey);
+};
+class Pair extends Node {
+  constructor(key, value = null) {
+    super();
+    this.key = key;
+    this.value = value;
+    this.type = Pair.Type.PAIR;
+  }
+  get commentBefore() {
+    return this.key instanceof Node ? this.key.commentBefore : undefined;
+  }
+  set commentBefore(cb) {
+    if (this.key == null) this.key = new Scalar(null);
+    if (this.key instanceof Node) this.key.commentBefore = cb;else {
+      const msg = 'Pair.commentBefore is an alias for Pair.key.commentBefore. To set it, the key must be a Node.';
+      throw new Error(msg);
+    }
+  }
+  addToJSMap(ctx, map) {
+    const key = toJSON(this.key, '', ctx);
+    if (map instanceof Map) {
+      const value = toJSON(this.value, key, ctx);
+      map.set(key, value);
+    } else if (map instanceof Set) {
+      map.add(key);
+    } else {
+      const stringKey = stringifyKey(this.key, key, ctx);
+      const value = toJSON(this.value, stringKey, ctx);
+      if (stringKey in map) Object.defineProperty(map, stringKey, {
+        value,
+        writable: true,
+        enumerable: true,
+        configurable: true
+      });else map[stringKey] = value;
+    }
+    return map;
+  }
+  toJSON(_, ctx) {
+    const pair = ctx && ctx.mapAsMap ? new Map() : {};
+    return this.addToJSMap(ctx, pair);
+  }
+  toString(ctx, onComment, onChompKeep) {
+    if (!ctx || !ctx.doc) return JSON.stringify(this);
+    const {
+      indent: indentSize,
+      indentSeq,
+      simpleKeys
+    } = ctx.doc.options;
+    let {
+      key,
+      value
+    } = this;
+    let keyComment = key instanceof Node && key.comment;
+    if (simpleKeys) {
+      if (keyComment) {
+        throw new Error('With simple keys, key nodes cannot have comments');
+      }
+      if (key instanceof Collection) {
+        const msg = 'With simple keys, collection cannot be used as a key value';
+        throw new Error(msg);
+      }
+    }
+    let explicitKey = !simpleKeys && (!key || keyComment || (key instanceof Node ? key instanceof Collection || key.type === Type.BLOCK_FOLDED || key.type === Type.BLOCK_LITERAL : typeof key === 'object'));
+    const {
+      doc,
+      indent,
+      indentStep,
+      stringify
+    } = ctx;
+    ctx = Object.assign({}, ctx, {
+      implicitKey: !explicitKey,
+      indent: indent + indentStep
+    });
+    let chompKeep = false;
+    let str = stringify(key, ctx, () => keyComment = null, () => chompKeep = true);
+    str = addComment(str, ctx.indent, keyComment);
+    if (!explicitKey && str.length > 1024) {
+      if (simpleKeys) throw new Error('With simple keys, single line scalar must not span more than 1024 characters');
+      explicitKey = true;
+    }
+    if (ctx.allNullValues && !simpleKeys) {
+      if (this.comment) {
+        str = addComment(str, ctx.indent, this.comment);
+        if (onComment) onComment();
+      } else if (chompKeep && !keyComment && onChompKeep) onChompKeep();
+      return ctx.inFlow && !explicitKey ? str : `? ${str}`;
+    }
+    str = explicitKey ? `? ${str}\n${indent}:` : `${str}:`;
+    if (this.comment) {
+      // expected (but not strictly required) to be a single-line comment
+      str = addComment(str, ctx.indent, this.comment);
+      if (onComment) onComment();
+    }
+    let vcb = '';
+    let valueComment = null;
+    if (value instanceof Node) {
+      if (value.spaceBefore) vcb = '\n';
+      if (value.commentBefore) {
+        const cs = value.commentBefore.replace(/^/gm, `${ctx.indent}#`);
+        vcb += `\n${cs}`;
+      }
+      valueComment = value.comment;
+    } else if (value && typeof value === 'object') {
+      value = doc.schema.createNode(value, true);
+    }
+    ctx.implicitKey = false;
+    if (!explicitKey && !this.comment && value instanceof Scalar) ctx.indentAtStart = str.length + 1;
+    chompKeep = false;
+    if (!indentSeq && indentSize >= 2 && !ctx.inFlow && !explicitKey && value instanceof YAMLSeq && value.type !== Type.FLOW_SEQ && !value.tag && !doc.anchors.getName(value)) {
+      // If indentSeq === false, consider '- ' as part of indentation where possible
+      ctx.indent = ctx.indent.substr(2);
+    }
+    const valueStr = stringify(value, ctx, () => valueComment = null, () => chompKeep = true);
+    let ws = ' ';
+    if (vcb || this.comment) {
+      ws = `${vcb}\n${ctx.indent}`;
+    } else if (!explicitKey && value instanceof Collection) {
+      const flow = valueStr[0] === '[' || valueStr[0] === '{';
+      if (!flow || valueStr.includes('\n')) ws = `\n${ctx.indent}`;
+    } else if (valueStr[0] === '\n') ws = '';
+    if (chompKeep && !valueComment && onChompKeep) onChompKeep();
+    return addComment(str + ws + valueStr, ctx.indent, valueComment);
+  }
+}
+_defineProperty(Pair, "Type", {
+  PAIR: 'PAIR',
+  MERGE_PAIR: 'MERGE_PAIR'
+});
+
+const getAliasCount = (node, anchors) => {
+  if (node instanceof Alias) {
+    const anchor = anchors.get(node.source);
+    return anchor.count * anchor.aliasCount;
+  } else if (node instanceof Collection) {
+    let count = 0;
+    for (const item of node.items) {
+      const c = getAliasCount(item, anchors);
+      if (c > count) count = c;
+    }
+    return count;
+  } else if (node instanceof Pair) {
+    const kc = getAliasCount(node.key, anchors);
+    const vc = getAliasCount(node.value, anchors);
+    return Math.max(kc, vc);
+  }
+  return 1;
+};
+class Alias extends Node {
+  static stringify({
+    range,
+    source
+  }, {
+    anchors,
+    doc,
+    implicitKey,
+    inStringifyKey
+  }) {
+    let anchor = Object.keys(anchors).find(a => anchors[a] === source);
+    if (!anchor && inStringifyKey) anchor = doc.anchors.getName(source) || doc.anchors.newName();
+    if (anchor) return `*${anchor}${implicitKey ? ' ' : ''}`;
+    const msg = doc.anchors.getName(source) ? 'Alias node must be after source node' : 'Source node not found for alias node';
+    throw new Error(`${msg} [${range}]`);
+  }
+  constructor(source) {
+    super();
+    this.source = source;
+    this.type = Type.ALIAS;
+  }
+  set tag(t) {
+    throw new Error('Alias nodes cannot have tags');
+  }
+  toJSON(arg, ctx) {
+    if (!ctx) return toJSON(this.source, arg, ctx);
+    const {
+      anchors,
+      maxAliasCount
+    } = ctx;
+    const anchor = anchors.get(this.source);
+    /* istanbul ignore if */
+    if (!anchor || anchor.res === undefined) {
+      const msg = 'This should not happen: Alias anchor was not resolved?';
+      if (this.cstNode) throw new YAMLReferenceError(this.cstNode, msg);else throw new ReferenceError(msg);
+    }
+    if (maxAliasCount >= 0) {
+      anchor.count += 1;
+      if (anchor.aliasCount === 0) anchor.aliasCount = getAliasCount(this.source, anchors);
+      if (anchor.count * anchor.aliasCount > maxAliasCount) {
+        const msg = 'Excessive alias count indicates a resource exhaustion attack';
+        if (this.cstNode) throw new YAMLReferenceError(this.cstNode, msg);else throw new ReferenceError(msg);
+      }
+    }
+    return anchor.res;
+  }
+
+  // Only called when stringifying an alias mapping key while constructing
+  // Object output.
+  toString(ctx) {
+    return Alias.stringify(this, ctx);
+  }
+}
+_defineProperty(Alias, "default", true);
+
+function findPair(items, key) {
+  const k = key instanceof Scalar ? key.value : key;
+  for (const it of items) {
+    if (it instanceof Pair) {
+      if (it.key === key || it.key === k) return it;
+      if (it.key && it.key.value === k) return it;
+    }
+  }
+  return undefined;
+}
+class YAMLMap extends Collection {
+  add(pair, overwrite) {
+    if (!pair) pair = new Pair(pair);else if (!(pair instanceof Pair)) pair = new Pair(pair.key || pair, pair.value);
+    const prev = findPair(this.items, pair.key);
+    const sortEntries = this.schema && this.schema.sortMapEntries;
+    if (prev) {
+      if (overwrite) prev.value = pair.value;else throw new Error(`Key ${pair.key} already set`);
+    } else if (sortEntries) {
+      const i = this.items.findIndex(item => sortEntries(pair, item) < 0);
+      if (i === -1) this.items.push(pair);else this.items.splice(i, 0, pair);
+    } else {
+      this.items.push(pair);
+    }
+  }
+  delete(key) {
+    const it = findPair(this.items, key);
+    if (!it) return false;
+    const del = this.items.splice(this.items.indexOf(it), 1);
+    return del.length > 0;
+  }
+  get(key, keepScalar) {
+    const it = findPair(this.items, key);
+    const node = it && it.value;
+    return !keepScalar && node instanceof Scalar ? node.value : node;
+  }
+  has(key) {
+    return !!findPair(this.items, key);
+  }
+  set(key, value) {
+    this.add(new Pair(key, value), true);
+  }
+
+  /**
+   * @param {*} arg ignored
+   * @param {*} ctx Conversion context, originally set in Document#toJSON()
+   * @param {Class} Type If set, forces the returned collection type
+   * @returns {*} Instance of Type, Map, or Object
+   */
+  toJSON(_, ctx, Type) {
+    const map = Type ? new Type() : ctx && ctx.mapAsMap ? new Map() : {};
+    if (ctx && ctx.onCreate) ctx.onCreate(map);
+    for (const item of this.items) item.addToJSMap(ctx, map);
+    return map;
+  }
+  toString(ctx, onComment, onChompKeep) {
+    if (!ctx) return JSON.stringify(this);
+    for (const item of this.items) {
+      if (!(item instanceof Pair)) throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`);
+    }
+    return super.toString(ctx, {
+      blockItem: n => n.str,
+      flowChars: {
+        start: '{',
+        end: '}'
+      },
+      isMap: true,
+      itemIndent: ctx.indent || ''
+    }, onComment, onChompKeep);
+  }
+}
+
+const MERGE_KEY = '<<';
+class Merge extends Pair {
+  constructor(pair) {
+    if (pair instanceof Pair) {
+      let seq = pair.value;
+      if (!(seq instanceof YAMLSeq)) {
+        seq = new YAMLSeq();
+        seq.items.push(pair.value);
+        seq.range = pair.value.range;
+      }
+      super(pair.key, seq);
+      this.range = pair.range;
+    } else {
+      super(new Scalar(MERGE_KEY), new YAMLSeq());
+    }
+    this.type = Pair.Type.MERGE_PAIR;
+  }
+
+  // If the value associated with a merge key is a single mapping node, each of
+  // its key/value pairs is inserted into the current mapping, unless the key
+  // already exists in it. If the value associated with the merge key is a
+  // sequence, then this sequence is expected to contain mapping nodes and each
+  // of these nodes is merged in turn according to its order in the sequence.
+  // Keys in mapping nodes earlier in the sequence override keys specified in
+  // later mapping nodes. -- http://yaml.org/type/merge.html
+  addToJSMap(ctx, map) {
+    for (const {
+      source
+    } of this.value.items) {
+      if (!(source instanceof YAMLMap)) throw new Error('Merge sources must be maps');
+      const srcMap = source.toJSON(null, ctx, Map);
+      for (const [key, value] of srcMap) {
+        if (map instanceof Map) {
+          if (!map.has(key)) map.set(key, value);
+        } else if (map instanceof Set) {
+          map.add(key);
+        } else if (!Object.prototype.hasOwnProperty.call(map, key)) {
+          Object.defineProperty(map, key, {
+            value,
+            writable: true,
+            enumerable: true,
+            configurable: true
+          });
+        }
+      }
+    }
+    return map;
+  }
+  toString(ctx, onComment) {
+    const seq = this.value;
+    if (seq.items.length > 1) return super.toString(ctx, onComment);
+    this.value = seq.items[0];
+    const str = super.toString(ctx, onComment);
+    this.value = seq;
+    return str;
+  }
+}
+
+const binaryOptions = {
+  defaultType: Type.BLOCK_LITERAL,
+  lineWidth: 76
+};
+const boolOptions = {
+  trueStr: 'true',
+  falseStr: 'false'
+};
+const intOptions = {
+  asBigInt: false
+};
+const nullOptions = {
+  nullStr: 'null'
+};
+const strOptions = {
+  defaultType: Type.PLAIN,
+  doubleQuoted: {
+    jsonEncoding: false,
+    minMultiLineLength: 40
+  },
+  fold: {
+    lineWidth: 80,
+    minContentWidth: 20
+  }
+};
+
+// falls back to string on no match
+function resolveScalar(str, tags, scalarFallback) {
+  for (const {
+    format,
+    test,
+    resolve
+  } of tags) {
+    if (test) {
+      const match = str.match(test);
+      if (match) {
+        let res = resolve.apply(null, match);
+        if (!(res instanceof Scalar)) res = new Scalar(res);
+        if (format) res.format = format;
+        return res;
+      }
+    }
+  }
+  if (scalarFallback) str = scalarFallback(str);
+  return new Scalar(str);
+}
+
+const FOLD_FLOW = 'flow';
+const FOLD_BLOCK = 'block';
+const FOLD_QUOTED = 'quoted';
+
+// presumes i+1 is at the start of a line
+// returns index of last newline in more-indented block
+const consumeMoreIndentedLines = (text, i) => {
+  let ch = text[i + 1];
+  while (ch === ' ' || ch === '\t') {
+    do {
+      ch = text[i += 1];
+    } while (ch && ch !== '\n');
+    ch = text[i + 1];
+  }
+  return i;
+};
+
+/**
+ * Tries to keep input at up to `lineWidth` characters, splitting only on spaces
+ * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are
+ * terminated with `\n` and started with `indent`.
+ *
+ * @param {string} text
+ * @param {string} indent
+ * @param {string} [mode='flow'] `'block'` prevents more-indented lines
+ *   from being folded; `'quoted'` allows for `\` escapes, including escaped
+ *   newlines
+ * @param {Object} options
+ * @param {number} [options.indentAtStart] Accounts for leading contents on
+ *   the first line, defaulting to `indent.length`
+ * @param {number} [options.lineWidth=80]
+ * @param {number} [options.minContentWidth=20] Allow highly indented lines to
+ *   stretch the line width or indent content from the start
+ * @param {function} options.onFold Called once if the text is folded
+ * @param {function} options.onFold Called once if any line of text exceeds
+ *   lineWidth characters
+ */
+function foldFlowLines(text, indent, mode, {
+  indentAtStart,
+  lineWidth = 80,
+  minContentWidth = 20,
+  onFold,
+  onOverflow
+}) {
+  if (!lineWidth || lineWidth < 0) return text;
+  const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length);
+  if (text.length <= endStep) return text;
+  const folds = [];
+  const escapedFolds = {};
+  let end = lineWidth - indent.length;
+  if (typeof indentAtStart === 'number') {
+    if (indentAtStart > lineWidth - Math.max(2, minContentWidth)) folds.push(0);else end = lineWidth - indentAtStart;
+  }
+  let split = undefined;
+  let prev = undefined;
+  let overflow = false;
+  let i = -1;
+  let escStart = -1;
+  let escEnd = -1;
+  if (mode === FOLD_BLOCK) {
+    i = consumeMoreIndentedLines(text, i);
+    if (i !== -1) end = i + endStep;
+  }
+  for (let ch; ch = text[i += 1];) {
+    if (mode === FOLD_QUOTED && ch === '\\') {
+      escStart = i;
+      switch (text[i + 1]) {
+        case 'x':
+          i += 3;
+          break;
+        case 'u':
+          i += 5;
+          break;
+        case 'U':
+          i += 9;
+          break;
+        default:
+          i += 1;
+      }
+      escEnd = i;
+    }
+    if (ch === '\n') {
+      if (mode === FOLD_BLOCK) i = consumeMoreIndentedLines(text, i);
+      end = i + endStep;
+      split = undefined;
+    } else {
+      if (ch === ' ' && prev && prev !== ' ' && prev !== '\n' && prev !== '\t') {
+        // space surrounded by non-space can be replaced with newline + indent
+        const next = text[i + 1];
+        if (next && next !== ' ' && next !== '\n' && next !== '\t') split = i;
+      }
+      if (i >= end) {
+        if (split) {
+          folds.push(split);
+          end = split + endStep;
+          split = undefined;
+        } else if (mode === FOLD_QUOTED) {
+          // white-space collected at end may stretch past lineWidth
+          while (prev === ' ' || prev === '\t') {
+            prev = ch;
+            ch = text[i += 1];
+            overflow = true;
+          }
+          // Account for newline escape, but don't break preceding escape
+          const j = i > escEnd + 1 ? i - 2 : escStart - 1;
+          // Bail out if lineWidth & minContentWidth are shorter than an escape string
+          if (escapedFolds[j]) return text;
+          folds.push(j);
+          escapedFolds[j] = true;
+          end = j + endStep;
+          split = undefined;
+        } else {
+          overflow = true;
+        }
+      }
+    }
+    prev = ch;
+  }
+  if (overflow && onOverflow) onOverflow();
+  if (folds.length === 0) return text;
+  if (onFold) onFold();
+  let res = text.slice(0, folds[0]);
+  for (let i = 0; i < folds.length; ++i) {
+    const fold = folds[i];
+    const end = folds[i + 1] || text.length;
+    if (fold === 0) res = `\n${indent}${text.slice(0, end)}`;else {
+      if (mode === FOLD_QUOTED && escapedFolds[fold]) res += `${text[fold]}\\`;
+      res += `\n${indent}${text.slice(fold + 1, end)}`;
+    }
+  }
+  return res;
+}
+
+const getFoldOptions = ({
+  indentAtStart
+}) => indentAtStart ? Object.assign({
+  indentAtStart
+}, strOptions.fold) : strOptions.fold;
+
+// Also checks for lines starting with %, as parsing the output as YAML 1.1 will
+// presume that's starting a new document.
+const containsDocumentMarker = str => /^(%|---|\.\.\.)/m.test(str);
+function lineLengthOverLimit(str, lineWidth, indentLength) {
+  if (!lineWidth || lineWidth < 0) return false;
+  const limit = lineWidth - indentLength;
+  const strLen = str.length;
+  if (strLen <= limit) return false;
+  for (let i = 0, start = 0; i < strLen; ++i) {
+    if (str[i] === '\n') {
+      if (i - start > limit) return true;
+      start = i + 1;
+      if (strLen - start <= limit) return false;
+    }
+  }
+  return true;
+}
+function doubleQuotedString(value, ctx) {
+  const {
+    implicitKey
+  } = ctx;
+  const {
+    jsonEncoding,
+    minMultiLineLength
+  } = strOptions.doubleQuoted;
+  const json = JSON.stringify(value);
+  if (jsonEncoding) return json;
+  const indent = ctx.indent || (containsDocumentMarker(value) ? '  ' : '');
+  let str = '';
+  let start = 0;
+  for (let i = 0, ch = json[i]; ch; ch = json[++i]) {
+    if (ch === ' ' && json[i + 1] === '\\' && json[i + 2] === 'n') {
+      // space before newline needs to be escaped to not be folded
+      str += json.slice(start, i) + '\\ ';
+      i += 1;
+      start = i;
+      ch = '\\';
+    }
+    if (ch === '\\') switch (json[i + 1]) {
+      case 'u':
+        {
+          str += json.slice(start, i);
+          const code = json.substr(i + 2, 4);
+          switch (code) {
+            case '0000':
+              str += '\\0';
+              break;
+            case '0007':
+              str += '\\a';
+              break;
+            case '000b':
+              str += '\\v';
+              break;
+            case '001b':
+              str += '\\e';
+              break;
+            case '0085':
+              str += '\\N';
+              break;
+            case '00a0':
+              str += '\\_';
+              break;
+            case '2028':
+              str += '\\L';
+              break;
+            case '2029':
+              str += '\\P';
+              break;
+            default:
+              if (code.substr(0, 2) === '00') str += '\\x' + code.substr(2);else str += json.substr(i, 6);
+          }
+          i += 5;
+          start = i + 1;
+        }
+        break;
+      case 'n':
+        if (implicitKey || json[i + 2] === '"' || json.length < minMultiLineLength) {
+          i += 1;
+        } else {
+          // folding will eat first newline
+          str += json.slice(start, i) + '\n\n';
+          while (json[i + 2] === '\\' && json[i + 3] === 'n' && json[i + 4] !== '"') {
+            str += '\n';
+            i += 2;
+          }
+          str += indent;
+          // space after newline needs to be escaped to not be folded
+          if (json[i + 2] === ' ') str += '\\';
+          i += 1;
+          start = i + 1;
+        }
+        break;
+      default:
+        i += 1;
+    }
+  }
+  str = start ? str + json.slice(start) : json;
+  return implicitKey ? str : foldFlowLines(str, indent, FOLD_QUOTED, getFoldOptions(ctx));
+}
+function singleQuotedString(value, ctx) {
+  if (ctx.implicitKey) {
+    if (/\n/.test(value)) return doubleQuotedString(value, ctx);
+  } else {
+    // single quoted string can't have leading or trailing whitespace around newline
+    if (/[ \t]\n|\n[ \t]/.test(value)) return doubleQuotedString(value, ctx);
+  }
+  const indent = ctx.indent || (containsDocumentMarker(value) ? '  ' : '');
+  const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'";
+  return ctx.implicitKey ? res : foldFlowLines(res, indent, FOLD_FLOW, getFoldOptions(ctx));
+}
+function blockString({
+  comment,
+  type,
+  value
+}, ctx, onComment, onChompKeep) {
+  // 1. Block can't end in whitespace unless the last line is non-empty.
+  // 2. Strings consisting of only whitespace are best rendered explicitly.
+  if (/\n[\t ]+$/.test(value) || /^\s*$/.test(value)) {
+    return doubleQuotedString(value, ctx);
+  }
+  const indent = ctx.indent || (ctx.forceBlockIndent || containsDocumentMarker(value) ? '  ' : '');
+  const indentSize = indent ? '2' : '1'; // root is at -1
+  const literal = type === Type.BLOCK_FOLDED ? false : type === Type.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, strOptions.fold.lineWidth, indent.length);
+  let header = literal ? '|' : '>';
+  if (!value) return header + '\n';
+  let wsStart = '';
+  let wsEnd = '';
+  value = value.replace(/[\n\t ]*$/, ws => {
+    const n = ws.indexOf('\n');
+    if (n === -1) {
+      header += '-'; // strip
+    } else if (value === ws || n !== ws.length - 1) {
+      header += '+'; // keep
+      if (onChompKeep) onChompKeep();
+    }
+    wsEnd = ws.replace(/\n$/, '');
+    return '';
+  }).replace(/^[\n ]*/, ws => {
+    if (ws.indexOf(' ') !== -1) header += indentSize;
+    const m = ws.match(/ +$/);
+    if (m) {
+      wsStart = ws.slice(0, -m[0].length);
+      return m[0];
+    } else {
+      wsStart = ws;
+      return '';
+    }
+  });
+  if (wsEnd) wsEnd = wsEnd.replace(/\n+(?!\n|$)/g, `$&${indent}`);
+  if (wsStart) wsStart = wsStart.replace(/\n+/g, `$&${indent}`);
+  if (comment) {
+    header += ' #' + comment.replace(/ ?[\r\n]+/g, ' ');
+    if (onComment) onComment();
+  }
+  if (!value) return `${header}${indentSize}\n${indent}${wsEnd}`;
+  if (literal) {
+    value = value.replace(/\n+/g, `$&${indent}`);
+    return `${header}\n${indent}${wsStart}${value}${wsEnd}`;
+  }
+  value = value.replace(/\n+/g, '\n$&').replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, '$1$2') // more-indented lines aren't folded
+  //         ^ ind.line  ^ empty     ^ capture next empty lines only at end of indent
+  .replace(/\n+/g, `$&${indent}`);
+  const body = foldFlowLines(`${wsStart}${value}${wsEnd}`, indent, FOLD_BLOCK, strOptions.fold);
+  return `${header}\n${indent}${body}`;
+}
+function plainString(item, ctx, onComment, onChompKeep) {
+  const {
+    comment,
+    type,
+    value
+  } = item;
+  const {
+    actualString,
+    implicitKey,
+    indent,
+    inFlow
+  } = ctx;
+  if (implicitKey && /[\n[\]{},]/.test(value) || inFlow && /[[\]{},]/.test(value)) {
+    return doubleQuotedString(value, ctx);
+  }
+  if (!value || /^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) {
+    // not allowed:
+    // - empty string, '-' or '?'
+    // - start with an indicator character (except [?:-]) or /[?-] /
+    // - '\n ', ': ' or ' \n' anywhere
+    // - '#' not preceded by a non-space char
+    // - end with ' ' or ':'
+    return implicitKey || inFlow || value.indexOf('\n') === -1 ? value.indexOf('"') !== -1 && value.indexOf("'") === -1 ? singleQuotedString(value, ctx) : doubleQuotedString(value, ctx) : blockString(item, ctx, onComment, onChompKeep);
+  }
+  if (!implicitKey && !inFlow && type !== Type.PLAIN && value.indexOf('\n') !== -1) {
+    // Where allowed & type not set explicitly, prefer block style for multiline strings
+    return blockString(item, ctx, onComment, onChompKeep);
+  }
+  if (indent === '' && containsDocumentMarker(value)) {
+    ctx.forceBlockIndent = true;
+    return blockString(item, ctx, onComment, onChompKeep);
+  }
+  const str = value.replace(/\n+/g, `$&\n${indent}`);
+  // Verify that output will be parsed as a string, as e.g. plain numbers and
+  // booleans get parsed with those types in v1.2 (e.g. '42', 'true' & '0.9e-3'),
+  // and others in v1.1.
+  if (actualString) {
+    const {
+      tags
+    } = ctx.doc.schema;
+    const resolved = resolveScalar(str, tags, tags.scalarFallback).value;
+    if (typeof resolved !== 'string') return doubleQuotedString(value, ctx);
+  }
+  const body = implicitKey ? str : foldFlowLines(str, indent, FOLD_FLOW, getFoldOptions(ctx));
+  if (comment && !inFlow && (body.indexOf('\n') !== -1 || comment.indexOf('\n') !== -1)) {
+    if (onComment) onComment();
+    return addCommentBefore(body, indent, comment);
+  }
+  return body;
+}
+function stringifyString(item, ctx, onComment, onChompKeep) {
+  const {
+    defaultType
+  } = strOptions;
+  const {
+    implicitKey,
+    inFlow
+  } = ctx;
+  let {
+    type,
+    value
+  } = item;
+  if (typeof value !== 'string') {
+    value = String(value);
+    item = Object.assign({}, item, {
+      value
+    });
+  }
+  const _stringify = _type => {
+    switch (_type) {
+      case Type.BLOCK_FOLDED:
+      case Type.BLOCK_LITERAL:
+        return blockString(item, ctx, onComment, onChompKeep);
+      case Type.QUOTE_DOUBLE:
+        return doubleQuotedString(value, ctx);
+      case Type.QUOTE_SINGLE:
+        return singleQuotedString(value, ctx);
+      case Type.PLAIN:
+        return plainString(item, ctx, onComment, onChompKeep);
+      default:
+        return null;
+    }
+  };
+  if (type !== Type.QUOTE_DOUBLE && /[\x00-\x08\x0b-\x1f\x7f-\x9f]/.test(value)) {
+    // force double quotes on control characters
+    type = Type.QUOTE_DOUBLE;
+  } else if ((implicitKey || inFlow) && (type === Type.BLOCK_FOLDED || type === Type.BLOCK_LITERAL)) {
+    // should not happen; blocks are not valid inside flow containers
+    type = Type.QUOTE_DOUBLE;
+  }
+  let res = _stringify(type);
+  if (res === null) {
+    res = _stringify(defaultType);
+    if (res === null) throw new Error(`Unsupported default string type ${defaultType}`);
+  }
+  return res;
+}
+
+function stringifyNumber({
+  format,
+  minFractionDigits,
+  tag,
+  value
+}) {
+  if (typeof value === 'bigint') return String(value);
+  if (!isFinite(value)) return isNaN(value) ? '.nan' : value < 0 ? '-.inf' : '.inf';
+  let n = JSON.stringify(value);
+  if (!format && minFractionDigits && (!tag || tag === 'tag:yaml.org,2002:float') && /^\d/.test(n)) {
+    let i = n.indexOf('.');
+    if (i < 0) {
+      i = n.length;
+      n += '.';
+    }
+    let d = minFractionDigits - (n.length - i - 1);
+    while (d-- > 0) n += '0';
+  }
+  return n;
+}
+
+function checkFlowCollectionEnd(errors, cst) {
+  let char, name;
+  switch (cst.type) {
+    case Type.FLOW_MAP:
+      char = '}';
+      name = 'flow map';
+      break;
+    case Type.FLOW_SEQ:
+      char = ']';
+      name = 'flow sequence';
+      break;
+    default:
+      errors.push(new YAMLSemanticError(cst, 'Not a flow collection!?'));
+      return;
+  }
+  let lastItem;
+  for (let i = cst.items.length - 1; i >= 0; --i) {
+    const item = cst.items[i];
+    if (!item || item.type !== Type.COMMENT) {
+      lastItem = item;
+      break;
+    }
+  }
+  if (lastItem && lastItem.char !== char) {
+    const msg = `Expected ${name} to end with ${char}`;
+    let err;
+    if (typeof lastItem.offset === 'number') {
+      err = new YAMLSemanticError(cst, msg);
+      err.offset = lastItem.offset + 1;
+    } else {
+      err = new YAMLSemanticError(lastItem, msg);
+      if (lastItem.range && lastItem.range.end) err.offset = lastItem.range.end - lastItem.range.start;
+    }
+    errors.push(err);
+  }
+}
+function checkFlowCommentSpace(errors, comment) {
+  const prev = comment.context.src[comment.range.start - 1];
+  if (prev !== '\n' && prev !== '\t' && prev !== ' ') {
+    const msg = 'Comments must be separated from other tokens by white space characters';
+    errors.push(new YAMLSemanticError(comment, msg));
+  }
+}
+function getLongKeyError(source, key) {
+  const sk = String(key);
+  const k = sk.substr(0, 8) + '...' + sk.substr(-8);
+  return new YAMLSemanticError(source, `The "${k}" key is too long`);
+}
+function resolveComments(collection, comments) {
+  for (const {
+    afterKey,
+    before,
+    comment
+  } of comments) {
+    let item = collection.items[before];
+    if (!item) {
+      if (comment !== undefined) {
+        if (collection.comment) collection.comment += '\n' + comment;else collection.comment = comment;
+      }
+    } else {
+      if (afterKey && item.value) item = item.value;
+      if (comment === undefined) {
+        if (afterKey || !item.commentBefore) item.spaceBefore = true;
+      } else {
+        if (item.commentBefore) item.commentBefore += '\n' + comment;else item.commentBefore = comment;
+      }
+    }
+  }
+}
+
+// on error, will return { str: string, errors: Error[] }
+function resolveString(doc, node) {
+  const res = node.strValue;
+  if (!res) return '';
+  if (typeof res === 'string') return res;
+  res.errors.forEach(error => {
+    if (!error.source) error.source = node;
+    doc.errors.push(error);
+  });
+  return res.str;
+}
+
+function resolveTagHandle(doc, node) {
+  const {
+    handle,
+    suffix
+  } = node.tag;
+  let prefix = doc.tagPrefixes.find(p => p.handle === handle);
+  if (!prefix) {
+    const dtp = doc.getDefaults().tagPrefixes;
+    if (dtp) prefix = dtp.find(p => p.handle === handle);
+    if (!prefix) throw new YAMLSemanticError(node, `The ${handle} tag handle is non-default and was not declared.`);
+  }
+  if (!suffix) throw new YAMLSemanticError(node, `The ${handle} tag has no suffix.`);
+  if (handle === '!' && (doc.version || doc.options.version) === '1.0') {
+    if (suffix[0] === '^') {
+      doc.warnings.push(new YAMLWarning(node, 'YAML 1.0 ^ tag expansion is not supported'));
+      return suffix;
+    }
+    if (/[:/]/.test(suffix)) {
+      // word/foo -> tag:word.yaml.org,2002:foo
+      const vocab = suffix.match(/^([a-z0-9-]+)\/(.*)/i);
+      return vocab ? `tag:${vocab[1]}.yaml.org,2002:${vocab[2]}` : `tag:${suffix}`;
+    }
+  }
+  return prefix.prefix + decodeURIComponent(suffix);
+}
+function resolveTagName(doc, node) {
+  const {
+    tag,
+    type
+  } = node;
+  let nonSpecific = false;
+  if (tag) {
+    const {
+      handle,
+      suffix,
+      verbatim
+    } = tag;
+    if (verbatim) {
+      if (verbatim !== '!' && verbatim !== '!!') return verbatim;
+      const msg = `Verbatim tags aren't resolved, so ${verbatim} is invalid.`;
+      doc.errors.push(new YAMLSemanticError(node, msg));
+    } else if (handle === '!' && !suffix) {
+      nonSpecific = true;
+    } else {
+      try {
+        return resolveTagHandle(doc, node);
+      } catch (error) {
+        doc.errors.push(error);
+      }
+    }
+  }
+  switch (type) {
+    case Type.BLOCK_FOLDED:
+    case Type.BLOCK_LITERAL:
+    case Type.QUOTE_DOUBLE:
+    case Type.QUOTE_SINGLE:
+      return defaultTags.STR;
+    case Type.FLOW_MAP:
+    case Type.MAP:
+      return defaultTags.MAP;
+    case Type.FLOW_SEQ:
+    case Type.SEQ:
+      return defaultTags.SEQ;
+    case Type.PLAIN:
+      return nonSpecific ? defaultTags.STR : null;
+    default:
+      return null;
+  }
+}
+
+function resolveByTagName(doc, node, tagName) {
+  const {
+    tags
+  } = doc.schema;
+  const matchWithTest = [];
+  for (const tag of tags) {
+    if (tag.tag === tagName) {
+      if (tag.test) matchWithTest.push(tag);else {
+        const res = tag.resolve(doc, node);
+        return res instanceof Collection ? res : new Scalar(res);
+      }
+    }
+  }
+  const str = resolveString(doc, node);
+  if (typeof str === 'string' && matchWithTest.length > 0) return resolveScalar(str, matchWithTest, tags.scalarFallback);
+  return null;
+}
+function getFallbackTagName({
+  type
+}) {
+  switch (type) {
+    case Type.FLOW_MAP:
+    case Type.MAP:
+      return defaultTags.MAP;
+    case Type.FLOW_SEQ:
+    case Type.SEQ:
+      return defaultTags.SEQ;
+    default:
+      return defaultTags.STR;
+  }
+}
+function resolveTag(doc, node, tagName) {
+  try {
+    const res = resolveByTagName(doc, node, tagName);
+    if (res) {
+      if (tagName && node.tag) res.tag = tagName;
+      return res;
+    }
+  } catch (error) {
+    if (error instanceof YAMLError) {
+      if (!error.source) error.source = node;
+      doc.errors.push(error);
+    } else {
+      const msg = error instanceof Error ? error.message : String(error);
+      doc.errors.push(new YAMLSemanticError(node, msg));
+    }
+    return null;
+  }
+  try {
+    const fallback = getFallbackTagName(node);
+    if (!fallback) throw new Error(`The tag ${tagName} is unavailable`);
+    const msg = `The tag ${tagName} is unavailable, falling back to ${fallback}`;
+    doc.warnings.push(new YAMLWarning(node, msg));
+    const res = resolveByTagName(doc, node, fallback);
+    res.tag = tagName;
+    return res;
+  } catch (error) {
+    const refError = new YAMLReferenceError(node, error.message);
+    refError.stack = error.stack;
+    doc.errors.push(refError);
+    return null;
+  }
+}
+
+const isCollectionItem = node => {
+  if (!node) return false;
+  const {
+    type
+  } = node;
+  return type === Type.MAP_KEY || type === Type.MAP_VALUE || type === Type.SEQ_ITEM;
+};
+function resolveNodeProps(errors, node) {
+  const comments = {
+    before: [],
+    after: []
+  };
+  let hasAnchor = false;
+  let hasTag = false;
+  const props = isCollectionItem(node.context.parent) ? node.context.parent.props.concat(node.props) : node.props;
+  for (const {
+    start,
+    end
+  } of props) {
+    switch (node.context.src[start]) {
+      case Char.COMMENT:
+        {
+          if (!node.commentHasRequiredWhitespace(start)) {
+            const msg = 'Comments must be separated from other tokens by white space characters';
+            errors.push(new YAMLSemanticError(node, msg));
+          }
+          const {
+            header,
+            valueRange
+          } = node;
+          const cc = valueRange && (start > valueRange.start || header && start > header.start) ? comments.after : comments.before;
+          cc.push(node.context.src.slice(start + 1, end));
+          break;
+        }
+
+      // Actual anchor & tag resolution is handled by schema, here we just complain
+      case Char.ANCHOR:
+        if (hasAnchor) {
+          const msg = 'A node can have at most one anchor';
+          errors.push(new YAMLSemanticError(node, msg));
+        }
+        hasAnchor = true;
+        break;
+      case Char.TAG:
+        if (hasTag) {
+          const msg = 'A node can have at most one tag';
+          errors.push(new YAMLSemanticError(node, msg));
+        }
+        hasTag = true;
+        break;
+    }
+  }
+  return {
+    comments,
+    hasAnchor,
+    hasTag
+  };
+}
+function resolveNodeValue(doc, node) {
+  const {
+    anchors,
+    errors,
+    schema
+  } = doc;
+  if (node.type === Type.ALIAS) {
+    const name = node.rawValue;
+    const src = anchors.getNode(name);
+    if (!src) {
+      const msg = `Aliased anchor not found: ${name}`;
+      errors.push(new YAMLReferenceError(node, msg));
+      return null;
+    }
+
+    // Lazy resolution for circular references
+    const res = new Alias(src);
+    anchors._cstAliases.push(res);
+    return res;
+  }
+  const tagName = resolveTagName(doc, node);
+  if (tagName) return resolveTag(doc, node, tagName);
+  if (node.type !== Type.PLAIN) {
+    const msg = `Failed to resolve ${node.type} node here`;
+    errors.push(new YAMLSyntaxError(node, msg));
+    return null;
+  }
+  try {
+    const str = resolveString(doc, node);
+    return resolveScalar(str, schema.tags, schema.tags.scalarFallback);
+  } catch (error) {
+    if (!error.source) error.source = node;
+    errors.push(error);
+    return null;
+  }
+}
+
+// sets node.resolved on success
+function resolveNode(doc, node) {
+  if (!node) return null;
+  if (node.error) doc.errors.push(node.error);
+  const {
+    comments,
+    hasAnchor,
+    hasTag
+  } = resolveNodeProps(doc.errors, node);
+  if (hasAnchor) {
+    const {
+      anchors
+    } = doc;
+    const name = node.anchor;
+    const prev = anchors.getNode(name);
+    // At this point, aliases for any preceding node with the same anchor
+    // name have already been resolved, so it may safely be renamed.
+    if (prev) anchors.map[anchors.newName(name)] = prev;
+    // During parsing, we need to store the CST node in anchors.map as
+    // anchors need to be available during resolution to allow for
+    // circular references.
+    anchors.map[name] = node;
+  }
+  if (node.type === Type.ALIAS && (hasAnchor || hasTag)) {
+    const msg = 'An alias node must not specify any properties';
+    doc.errors.push(new YAMLSemanticError(node, msg));
+  }
+  const res = resolveNodeValue(doc, node);
+  if (res) {
+    res.range = [node.range.start, node.range.end];
+    if (doc.options.keepCstNodes) res.cstNode = node;
+    if (doc.options.keepNodeTypes) res.type = node.type;
+    const cb = comments.before.join('\n');
+    if (cb) {
+      res.commentBefore = res.commentBefore ? `${res.commentBefore}\n${cb}` : cb;
+    }
+    const ca = comments.after.join('\n');
+    if (ca) res.comment = res.comment ? `${res.comment}\n${ca}` : ca;
+  }
+  return node.resolved = res;
+}
+
+function resolveMap(doc, cst) {
+  if (cst.type !== Type.MAP && cst.type !== Type.FLOW_MAP) {
+    const msg = `A ${cst.type} node cannot be resolved as a mapping`;
+    doc.errors.push(new YAMLSyntaxError(cst, msg));
+    return null;
+  }
+  const {
+    comments,
+    items
+  } = cst.type === Type.FLOW_MAP ? resolveFlowMapItems(doc, cst) : resolveBlockMapItems(doc, cst);
+  const map = new YAMLMap();
+  map.items = items;
+  resolveComments(map, comments);
+  let hasCollectionKey = false;
+  for (let i = 0; i < items.length; ++i) {
+    const {
+      key: iKey
+    } = items[i];
+    if (iKey instanceof Collection) hasCollectionKey = true;
+    if (doc.schema.merge && iKey && iKey.value === MERGE_KEY) {
+      items[i] = new Merge(items[i]);
+      const sources = items[i].value.items;
+      let error = null;
+      sources.some(node => {
+        if (node instanceof Alias) {
+          // During parsing, alias sources are CST nodes; to account for
+          // circular references their resolved values can't be used here.
+          const {
+            type
+          } = node.source;
+          if (type === Type.MAP || type === Type.FLOW_MAP) return false;
+          return error = 'Merge nodes aliases can only point to maps';
+        }
+        return error = 'Merge nodes can only have Alias nodes as values';
+      });
+      if (error) doc.errors.push(new YAMLSemanticError(cst, error));
+    } else {
+      for (let j = i + 1; j < items.length; ++j) {
+        const {
+          key: jKey
+        } = items[j];
+        if (iKey === jKey || iKey && jKey && Object.prototype.hasOwnProperty.call(iKey, 'value') && iKey.value === jKey.value) {
+          const msg = `Map keys must be unique; "${iKey}" is repeated`;
+          doc.errors.push(new YAMLSemanticError(cst, msg));
+          break;
+        }
+      }
+    }
+  }
+  if (hasCollectionKey && !doc.options.mapAsMap) {
+    const warn = 'Keys with collection values will be stringified as YAML due to JS Object restrictions. Use mapAsMap: true to avoid this.';
+    doc.warnings.push(new YAMLWarning(cst, warn));
+  }
+  cst.resolved = map;
+  return map;
+}
+const valueHasPairComment = ({
+  context: {
+    lineStart,
+    node,
+    src
+  },
+  props
+}) => {
+  if (props.length === 0) return false;
+  const {
+    start
+  } = props[0];
+  if (node && start > node.valueRange.start) return false;
+  if (src[start] !== Char.COMMENT) return false;
+  for (let i = lineStart; i < start; ++i) if (src[i] === '\n') return false;
+  return true;
+};
+function resolvePairComment(item, pair) {
+  if (!valueHasPairComment(item)) return;
+  const comment = item.getPropValue(0, Char.COMMENT, true);
+  let found = false;
+  const cb = pair.value.commentBefore;
+  if (cb && cb.startsWith(comment)) {
+    pair.value.commentBefore = cb.substr(comment.length + 1);
+    found = true;
+  } else {
+    const cc = pair.value.comment;
+    if (!item.node && cc && cc.startsWith(comment)) {
+      pair.value.comment = cc.substr(comment.length + 1);
+      found = true;
+    }
+  }
+  if (found) pair.comment = comment;
+}
+function resolveBlockMapItems(doc, cst) {
+  const comments = [];
+  const items = [];
+  let key = undefined;
+  let keyStart = null;
+  for (let i = 0; i < cst.items.length; ++i) {
+    const item = cst.items[i];
+    switch (item.type) {
+      case Type.BLANK_LINE:
+        comments.push({
+          afterKey: !!key,
+          before: items.length
+        });
+        break;
+      case Type.COMMENT:
+        comments.push({
+          afterKey: !!key,
+          before: items.length,
+          comment: item.comment
+        });
+        break;
+      case Type.MAP_KEY:
+        if (key !== undefined) items.push(new Pair(key));
+        if (item.error) doc.errors.push(item.error);
+        key = resolveNode(doc, item.node);
+        keyStart = null;
+        break;
+      case Type.MAP_VALUE:
+        {
+          if (key === undefined) key = null;
+          if (item.error) doc.errors.push(item.error);
+          if (!item.context.atLineStart && item.node && item.node.type === Type.MAP && !item.node.context.atLineStart) {
+            const msg = 'Nested mappings are not allowed in compact mappings';
+            doc.errors.push(new YAMLSemanticError(item.node, msg));
+          }
+          let valueNode = item.node;
+          if (!valueNode && item.props.length > 0) {
+            // Comments on an empty mapping value need to be preserved, so we
+            // need to construct a minimal empty node here to use instead of the
+            // missing `item.node`. -- eemeli/yaml#19
+            valueNode = new PlainValue(Type.PLAIN, []);
+            valueNode.context = {
+              parent: item,
+              src: item.context.src
+            };
+            const pos = item.range.start + 1;
+            valueNode.range = {
+              start: pos,
+              end: pos
+            };
+            valueNode.valueRange = {
+              start: pos,
+              end: pos
+            };
+            if (typeof item.range.origStart === 'number') {
+              const origPos = item.range.origStart + 1;
+              valueNode.range.origStart = valueNode.range.origEnd = origPos;
+              valueNode.valueRange.origStart = valueNode.valueRange.origEnd = origPos;
+            }
+          }
+          const pair = new Pair(key, resolveNode(doc, valueNode));
+          resolvePairComment(item, pair);
+          items.push(pair);
+          if (key && typeof keyStart === 'number') {
+            if (item.range.start > keyStart + 1024) doc.errors.push(getLongKeyError(cst, key));
+          }
+          key = undefined;
+          keyStart = null;
+        }
+        break;
+      default:
+        if (key !== undefined) items.push(new Pair(key));
+        key = resolveNode(doc, item);
+        keyStart = item.range.start;
+        if (item.error) doc.errors.push(item.error);
+        next: for (let j = i + 1;; ++j) {
+          const nextItem = cst.items[j];
+          switch (nextItem && nextItem.type) {
+            case Type.BLANK_LINE:
+            case Type.COMMENT:
+              continue next;
+            case Type.MAP_VALUE:
+              break next;
+            default:
+              {
+                const msg = 'Implicit map keys need to be followed by map values';
+                doc.errors.push(new YAMLSemanticError(item, msg));
+                break next;
+              }
+          }
+        }
+        if (item.valueRangeContainsNewline) {
+          const msg = 'Implicit map keys need to be on a single line';
+          doc.errors.push(new YAMLSemanticError(item, msg));
+        }
+    }
+  }
+  if (key !== undefined) items.push(new Pair(key));
+  return {
+    comments,
+    items
+  };
+}
+function resolveFlowMapItems(doc, cst) {
+  const comments = [];
+  const items = [];
+  let key = undefined;
+  let explicitKey = false;
+  let next = '{';
+  for (let i = 0; i < cst.items.length; ++i) {
+    const item = cst.items[i];
+    if (typeof item.char === 'string') {
+      const {
+        char,
+        offset
+      } = item;
+      if (char === '?' && key === undefined && !explicitKey) {
+        explicitKey = true;
+        next = ':';
+        continue;
+      }
+      if (char === ':') {
+        if (key === undefined) key = null;
+        if (next === ':') {
+          next = ',';
+          continue;
+        }
+      } else {
+        if (explicitKey) {
+          if (key === undefined && char !== ',') key = null;
+          explicitKey = false;
+        }
+        if (key !== undefined) {
+          items.push(new Pair(key));
+          key = undefined;
+          if (char === ',') {
+            next = ':';
+            continue;
+          }
+        }
+      }
+      if (char === '}') {
+        if (i === cst.items.length - 1) continue;
+      } else if (char === next) {
+        next = ':';
+        continue;
+      }
+      const msg = `Flow map contains an unexpected ${char}`;
+      const err = new YAMLSyntaxError(cst, msg);
+      err.offset = offset;
+      doc.errors.push(err);
+    } else if (item.type === Type.BLANK_LINE) {
+      comments.push({
+        afterKey: !!key,
+        before: items.length
+      });
+    } else if (item.type === Type.COMMENT) {
+      checkFlowCommentSpace(doc.errors, item);
+      comments.push({
+        afterKey: !!key,
+        before: items.length,
+        comment: item.comment
+      });
+    } else if (key === undefined) {
+      if (next === ',') doc.errors.push(new YAMLSemanticError(item, 'Separator , missing in flow map'));
+      key = resolveNode(doc, item);
+    } else {
+      if (next !== ',') doc.errors.push(new YAMLSemanticError(item, 'Indicator : missing in flow map entry'));
+      items.push(new Pair(key, resolveNode(doc, item)));
+      key = undefined;
+      explicitKey = false;
+    }
+  }
+  checkFlowCollectionEnd(doc.errors, cst);
+  if (key !== undefined) items.push(new Pair(key));
+  return {
+    comments,
+    items
+  };
+}
+
+function resolveSeq(doc, cst) {
+  if (cst.type !== Type.SEQ && cst.type !== Type.FLOW_SEQ) {
+    const msg = `A ${cst.type} node cannot be resolved as a sequence`;
+    doc.errors.push(new YAMLSyntaxError(cst, msg));
+    return null;
+  }
+  const {
+    comments,
+    items
+  } = cst.type === Type.FLOW_SEQ ? resolveFlowSeqItems(doc, cst) : resolveBlockSeqItems(doc, cst);
+  const seq = new YAMLSeq();
+  seq.items = items;
+  resolveComments(seq, comments);
+  if (!doc.options.mapAsMap && items.some(it => it instanceof Pair && it.key instanceof Collection)) {
+    const warn = 'Keys with collection values will be stringified as YAML due to JS Object restrictions. Use mapAsMap: true to avoid this.';
+    doc.warnings.push(new YAMLWarning(cst, warn));
+  }
+  cst.resolved = seq;
+  return seq;
+}
+function resolveBlockSeqItems(doc, cst) {
+  const comments = [];
+  const items = [];
+  for (let i = 0; i < cst.items.length; ++i) {
+    const item = cst.items[i];
+    switch (item.type) {
+      case Type.BLANK_LINE:
+        comments.push({
+          before: items.length
+        });
+        break;
+      case Type.COMMENT:
+        comments.push({
+          comment: item.comment,
+          before: items.length
+        });
+        break;
+      case Type.SEQ_ITEM:
+        if (item.error) doc.errors.push(item.error);
+        items.push(resolveNode(doc, item.node));
+        if (item.hasProps) {
+          const msg = 'Sequence items cannot have tags or anchors before the - indicator';
+          doc.errors.push(new YAMLSemanticError(item, msg));
+        }
+        break;
+      default:
+        if (item.error) doc.errors.push(item.error);
+        doc.errors.push(new YAMLSyntaxError(item, `Unexpected ${item.type} node in sequence`));
+    }
+  }
+  return {
+    comments,
+    items
+  };
+}
+function resolveFlowSeqItems(doc, cst) {
+  const comments = [];
+  const items = [];
+  let explicitKey = false;
+  let key = undefined;
+  let keyStart = null;
+  let next = '[';
+  let prevItem = null;
+  for (let i = 0; i < cst.items.length; ++i) {
+    const item = cst.items[i];
+    if (typeof item.char === 'string') {
+      const {
+        char,
+        offset
+      } = item;
+      if (char !== ':' && (explicitKey || key !== undefined)) {
+        if (explicitKey && key === undefined) key = next ? items.pop() : null;
+        items.push(new Pair(key));
+        explicitKey = false;
+        key = undefined;
+        keyStart = null;
+      }
+      if (char === next) {
+        next = null;
+      } else if (!next && char === '?') {
+        explicitKey = true;
+      } else if (next !== '[' && char === ':' && key === undefined) {
+        if (next === ',') {
+          key = items.pop();
+          if (key instanceof Pair) {
+            const msg = 'Chaining flow sequence pairs is invalid';
+            const err = new YAMLSemanticError(cst, msg);
+            err.offset = offset;
+            doc.errors.push(err);
+          }
+          if (!explicitKey && typeof keyStart === 'number') {
+            const keyEnd = item.range ? item.range.start : item.offset;
+            if (keyEnd > keyStart + 1024) doc.errors.push(getLongKeyError(cst, key));
+            const {
+              src
+            } = prevItem.context;
+            for (let i = keyStart; i < keyEnd; ++i) if (src[i] === '\n') {
+              const msg = 'Implicit keys of flow sequence pairs need to be on a single line';
+              doc.errors.push(new YAMLSemanticError(prevItem, msg));
+              break;
+            }
+          }
+        } else {
+          key = null;
+        }
+        keyStart = null;
+        explicitKey = false;
+        next = null;
+      } else if (next === '[' || char !== ']' || i < cst.items.length - 1) {
+        const msg = `Flow sequence contains an unexpected ${char}`;
+        const err = new YAMLSyntaxError(cst, msg);
+        err.offset = offset;
+        doc.errors.push(err);
+      }
+    } else if (item.type === Type.BLANK_LINE) {
+      comments.push({
+        before: items.length
+      });
+    } else if (item.type === Type.COMMENT) {
+      checkFlowCommentSpace(doc.errors, item);
+      comments.push({
+        comment: item.comment,
+        before: items.length
+      });
+    } else {
+      if (next) {
+        const msg = `Expected a ${next} in flow sequence`;
+        doc.errors.push(new YAMLSemanticError(item, msg));
+      }
+      const value = resolveNode(doc, item);
+      if (key === undefined) {
+        items.push(value);
+        prevItem = item;
+      } else {
+        items.push(new Pair(key, value));
+        key = undefined;
+      }
+      keyStart = item.range.start;
+      next = ',';
+    }
+  }
+  checkFlowCollectionEnd(doc.errors, cst);
+  if (key !== undefined) items.push(new Pair(key));
+  return {
+    comments,
+    items
+  };
+}
+
+export { Alias as A, Collection as C, Merge as M, Node as N, Pair as P, Scalar as S, YAMLSeq as Y, boolOptions as a, binaryOptions as b, stringifyString as c, YAMLMap as d, isEmptyPath as e, addComment as f, resolveMap as g, resolveSeq as h, intOptions as i, resolveString as j, stringifyNumber as k, findPair as l, nullOptions as n, resolveNode as r, strOptions as s, toJSON as t };
Index: frontend/node_modules/yaml/browser/dist/types.js
===================================================================
--- frontend/node_modules/yaml/browser/dist/types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/dist/types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,4 @@
+export { A as Alias, C as Collection, M as Merge, N as Node, P as Pair, S as Scalar, d as YAMLMap, Y as YAMLSeq, b as binaryOptions, a as boolOptions, i as intOptions, n as nullOptions, s as strOptions } from './resolveSeq-67caf78a.js';
+export { S as Schema } from './Schema-9530c078.js';
+import './PlainValue-183afbad.js';
+import './warnings-5e4358fe.js';
Index: frontend/node_modules/yaml/browser/dist/util.js
===================================================================
--- frontend/node_modules/yaml/browser/dist/util.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/dist/util.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2 @@
+export { l as findPair, g as parseMap, h as parseSeq, k as stringifyNumber, c as stringifyString, t as toJSON } from './resolveSeq-67caf78a.js';
+export { T as Type, c as YAMLError, f as YAMLReferenceError, b as YAMLSemanticError, Y as YAMLSyntaxError, a as YAMLWarning } from './PlainValue-183afbad.js';
Index: frontend/node_modules/yaml/browser/dist/warnings-5e4358fe.js
===================================================================
--- frontend/node_modules/yaml/browser/dist/warnings-5e4358fe.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/dist/warnings-5e4358fe.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,348 @@
+import { f as YAMLReferenceError, T as Type, b as YAMLSemanticError, _ as _defineProperty } from './PlainValue-183afbad.js';
+import { j as resolveString, b as binaryOptions, c as stringifyString, h as resolveSeq, P as Pair, d as YAMLMap, Y as YAMLSeq, t as toJSON, S as Scalar, l as findPair, g as resolveMap, k as stringifyNumber } from './resolveSeq-67caf78a.js';
+
+/* global atob, btoa, Buffer */
+const binary = {
+  identify: value => value instanceof Uint8Array,
+  // Buffer inherits from Uint8Array
+  default: false,
+  tag: 'tag:yaml.org,2002:binary',
+  /**
+   * Returns a Buffer in node and an Uint8Array in browsers
+   *
+   * To use the resulting buffer as an image, you'll want to do something like:
+   *
+   *   const blob = new Blob([buffer], { type: 'image/jpeg' })
+   *   document.querySelector('#photo').src = URL.createObjectURL(blob)
+   */
+  resolve: (doc, node) => {
+    const src = resolveString(doc, node);
+    if (typeof Buffer === 'function') {
+      return Buffer.from(src, 'base64');
+    } else if (typeof atob === 'function') {
+      // On IE 11, atob() can't handle newlines
+      const str = atob(src.replace(/[\n\r]/g, ''));
+      const buffer = new Uint8Array(str.length);
+      for (let i = 0; i < str.length; ++i) buffer[i] = str.charCodeAt(i);
+      return buffer;
+    } else {
+      const msg = 'This environment does not support reading binary tags; either Buffer or atob is required';
+      doc.errors.push(new YAMLReferenceError(node, msg));
+      return null;
+    }
+  },
+  options: binaryOptions,
+  stringify: ({
+    comment,
+    type,
+    value
+  }, ctx, onComment, onChompKeep) => {
+    let src;
+    if (typeof Buffer === 'function') {
+      src = value instanceof Buffer ? value.toString('base64') : Buffer.from(value.buffer).toString('base64');
+    } else if (typeof btoa === 'function') {
+      let s = '';
+      for (let i = 0; i < value.length; ++i) s += String.fromCharCode(value[i]);
+      src = btoa(s);
+    } else {
+      throw new Error('This environment does not support writing binary tags; either Buffer or btoa is required');
+    }
+    if (!type) type = binaryOptions.defaultType;
+    if (type === Type.QUOTE_DOUBLE) {
+      value = src;
+    } else {
+      const {
+        lineWidth
+      } = binaryOptions;
+      const n = Math.ceil(src.length / lineWidth);
+      const lines = new Array(n);
+      for (let i = 0, o = 0; i < n; ++i, o += lineWidth) {
+        lines[i] = src.substr(o, lineWidth);
+      }
+      value = lines.join(type === Type.BLOCK_LITERAL ? '\n' : ' ');
+    }
+    return stringifyString({
+      comment,
+      type,
+      value
+    }, ctx, onComment, onChompKeep);
+  }
+};
+
+function parsePairs(doc, cst) {
+  const seq = resolveSeq(doc, cst);
+  for (let i = 0; i < seq.items.length; ++i) {
+    let item = seq.items[i];
+    if (item instanceof Pair) continue;else if (item instanceof YAMLMap) {
+      if (item.items.length > 1) {
+        const msg = 'Each pair must have its own sequence indicator';
+        throw new YAMLSemanticError(cst, msg);
+      }
+      const pair = item.items[0] || new Pair();
+      if (item.commentBefore) pair.commentBefore = pair.commentBefore ? `${item.commentBefore}\n${pair.commentBefore}` : item.commentBefore;
+      if (item.comment) pair.comment = pair.comment ? `${item.comment}\n${pair.comment}` : item.comment;
+      item = pair;
+    }
+    seq.items[i] = item instanceof Pair ? item : new Pair(item);
+  }
+  return seq;
+}
+function createPairs(schema, iterable, ctx) {
+  const pairs = new YAMLSeq(schema);
+  pairs.tag = 'tag:yaml.org,2002:pairs';
+  for (const it of iterable) {
+    let key, value;
+    if (Array.isArray(it)) {
+      if (it.length === 2) {
+        key = it[0];
+        value = it[1];
+      } else throw new TypeError(`Expected [key, value] tuple: ${it}`);
+    } else if (it && it instanceof Object) {
+      const keys = Object.keys(it);
+      if (keys.length === 1) {
+        key = keys[0];
+        value = it[key];
+      } else throw new TypeError(`Expected { key: value } tuple: ${it}`);
+    } else {
+      key = it;
+    }
+    const pair = schema.createPair(key, value, ctx);
+    pairs.items.push(pair);
+  }
+  return pairs;
+}
+const pairs = {
+  default: false,
+  tag: 'tag:yaml.org,2002:pairs',
+  resolve: parsePairs,
+  createNode: createPairs
+};
+
+class YAMLOMap extends YAMLSeq {
+  constructor() {
+    super();
+    _defineProperty(this, "add", YAMLMap.prototype.add.bind(this));
+    _defineProperty(this, "delete", YAMLMap.prototype.delete.bind(this));
+    _defineProperty(this, "get", YAMLMap.prototype.get.bind(this));
+    _defineProperty(this, "has", YAMLMap.prototype.has.bind(this));
+    _defineProperty(this, "set", YAMLMap.prototype.set.bind(this));
+    this.tag = YAMLOMap.tag;
+  }
+  toJSON(_, ctx) {
+    const map = new Map();
+    if (ctx && ctx.onCreate) ctx.onCreate(map);
+    for (const pair of this.items) {
+      let key, value;
+      if (pair instanceof Pair) {
+        key = toJSON(pair.key, '', ctx);
+        value = toJSON(pair.value, key, ctx);
+      } else {
+        key = toJSON(pair, '', ctx);
+      }
+      if (map.has(key)) throw new Error('Ordered maps must not include duplicate keys');
+      map.set(key, value);
+    }
+    return map;
+  }
+}
+_defineProperty(YAMLOMap, "tag", 'tag:yaml.org,2002:omap');
+function parseOMap(doc, cst) {
+  const pairs = parsePairs(doc, cst);
+  const seenKeys = [];
+  for (const {
+    key
+  } of pairs.items) {
+    if (key instanceof Scalar) {
+      if (seenKeys.includes(key.value)) {
+        const msg = 'Ordered maps must not include duplicate keys';
+        throw new YAMLSemanticError(cst, msg);
+      } else {
+        seenKeys.push(key.value);
+      }
+    }
+  }
+  return Object.assign(new YAMLOMap(), pairs);
+}
+function createOMap(schema, iterable, ctx) {
+  const pairs = createPairs(schema, iterable, ctx);
+  const omap = new YAMLOMap();
+  omap.items = pairs.items;
+  return omap;
+}
+const omap = {
+  identify: value => value instanceof Map,
+  nodeClass: YAMLOMap,
+  default: false,
+  tag: 'tag:yaml.org,2002:omap',
+  resolve: parseOMap,
+  createNode: createOMap
+};
+
+class YAMLSet extends YAMLMap {
+  constructor() {
+    super();
+    this.tag = YAMLSet.tag;
+  }
+  add(key) {
+    const pair = key instanceof Pair ? key : new Pair(key);
+    const prev = findPair(this.items, pair.key);
+    if (!prev) this.items.push(pair);
+  }
+  get(key, keepPair) {
+    const pair = findPair(this.items, key);
+    return !keepPair && pair instanceof Pair ? pair.key instanceof Scalar ? pair.key.value : pair.key : pair;
+  }
+  set(key, value) {
+    if (typeof value !== 'boolean') throw new Error(`Expected boolean value for set(key, value) in a YAML set, not ${typeof value}`);
+    const prev = findPair(this.items, key);
+    if (prev && !value) {
+      this.items.splice(this.items.indexOf(prev), 1);
+    } else if (!prev && value) {
+      this.items.push(new Pair(key));
+    }
+  }
+  toJSON(_, ctx) {
+    return super.toJSON(_, ctx, Set);
+  }
+  toString(ctx, onComment, onChompKeep) {
+    if (!ctx) return JSON.stringify(this);
+    if (this.hasAllNullValues()) return super.toString(ctx, onComment, onChompKeep);else throw new Error('Set items must all have null values');
+  }
+}
+_defineProperty(YAMLSet, "tag", 'tag:yaml.org,2002:set');
+function parseSet(doc, cst) {
+  const map = resolveMap(doc, cst);
+  if (!map.hasAllNullValues()) throw new YAMLSemanticError(cst, 'Set items must all have null values');
+  return Object.assign(new YAMLSet(), map);
+}
+function createSet(schema, iterable, ctx) {
+  const set = new YAMLSet();
+  for (const value of iterable) set.items.push(schema.createPair(value, null, ctx));
+  return set;
+}
+const set = {
+  identify: value => value instanceof Set,
+  nodeClass: YAMLSet,
+  default: false,
+  tag: 'tag:yaml.org,2002:set',
+  resolve: parseSet,
+  createNode: createSet
+};
+
+const parseSexagesimal = (sign, parts) => {
+  const n = parts.split(':').reduce((n, p) => n * 60 + Number(p), 0);
+  return sign === '-' ? -n : n;
+};
+
+// hhhh:mm:ss.sss
+const stringifySexagesimal = ({
+  value
+}) => {
+  if (isNaN(value) || !isFinite(value)) return stringifyNumber(value);
+  let sign = '';
+  if (value < 0) {
+    sign = '-';
+    value = Math.abs(value);
+  }
+  const parts = [value % 60]; // seconds, including ms
+  if (value < 60) {
+    parts.unshift(0); // at least one : is required
+  } else {
+    value = Math.round((value - parts[0]) / 60);
+    parts.unshift(value % 60); // minutes
+    if (value >= 60) {
+      value = Math.round((value - parts[0]) / 60);
+      parts.unshift(value); // hours
+    }
+  }
+  return sign + parts.map(n => n < 10 ? '0' + String(n) : String(n)).join(':').replace(/000000\d*$/, '') // % 60 may introduce error
+;
+};
+const intTime = {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'TIME',
+  test: /^([-+]?)([0-9][0-9_]*(?::[0-5]?[0-9])+)$/,
+  resolve: (str, sign, parts) => parseSexagesimal(sign, parts.replace(/_/g, '')),
+  stringify: stringifySexagesimal
+};
+const floatTime = {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  format: 'TIME',
+  test: /^([-+]?)([0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*)$/,
+  resolve: (str, sign, parts) => parseSexagesimal(sign, parts.replace(/_/g, '')),
+  stringify: stringifySexagesimal
+};
+const timestamp = {
+  identify: value => value instanceof Date,
+  default: true,
+  tag: 'tag:yaml.org,2002:timestamp',
+  // If the time zone is omitted, the timestamp is assumed to be specified in UTC. The time part
+  // may be omitted altogether, resulting in a date format. In such a case, the time part is
+  // assumed to be 00:00:00Z (start of day, UTC).
+  test: RegExp('^(?:' + '([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})' +
+  // YYYY-Mm-Dd
+  '(?:(?:t|T|[ \\t]+)' +
+  // t | T | whitespace
+  '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)' +
+  // Hh:Mm:Ss(.ss)?
+  '(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?' +
+  // Z | +5 | -03:30
+  ')?' + ')$'),
+  resolve: (str, year, month, day, hour, minute, second, millisec, tz) => {
+    if (millisec) millisec = (millisec + '00').substr(1, 3);
+    let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec || 0);
+    if (tz && tz !== 'Z') {
+      let d = parseSexagesimal(tz[0], tz.slice(1));
+      if (Math.abs(d) < 30) d *= 60;
+      date -= 60000 * d;
+    }
+    return new Date(date);
+  },
+  stringify: ({
+    value
+  }) => value.toISOString().replace(/((T00:00)?:00)?\.000Z$/, '')
+};
+
+/* global console, process, YAML_SILENCE_DEPRECATION_WARNINGS, YAML_SILENCE_WARNINGS */
+
+function shouldWarn(deprecation) {
+  const env = typeof process !== 'undefined' && process.env || {};
+  if (deprecation) {
+    if (typeof YAML_SILENCE_DEPRECATION_WARNINGS !== 'undefined') return !YAML_SILENCE_DEPRECATION_WARNINGS;
+    return !env.YAML_SILENCE_DEPRECATION_WARNINGS;
+  }
+  if (typeof YAML_SILENCE_WARNINGS !== 'undefined') return !YAML_SILENCE_WARNINGS;
+  return !env.YAML_SILENCE_WARNINGS;
+}
+function warn(warning, type) {
+  if (shouldWarn(false)) {
+    const emit = typeof process !== 'undefined' && process.emitWarning;
+    // This will throw in Jest if `warning` is an Error instance due to
+    // https://github.com/facebook/jest/issues/2549
+    if (emit) emit(warning, type);else {
+      // eslint-disable-next-line no-console
+      console.warn(type ? `${type}: ${warning}` : warning);
+    }
+  }
+}
+function warnFileDeprecation(filename) {
+  if (shouldWarn(true)) {
+    const path = filename.replace(/.*yaml[/\\]/i, '').replace(/\.js$/, '').replace(/\\/g, '/');
+    warn(`The endpoint 'yaml/${path}' will be removed in a future release.`, 'DeprecationWarning');
+  }
+}
+const warned = {};
+function warnOptionDeprecation(name, alternative) {
+  if (!warned[name] && shouldWarn(true)) {
+    warned[name] = true;
+    let msg = `The option '${name}' will be removed in a future release`;
+    msg += alternative ? `, use '${alternative}' instead.` : '.';
+    warn(msg, 'DeprecationWarning');
+  }
+}
+
+export { warnOptionDeprecation as a, binary as b, warnFileDeprecation as c, floatTime as f, intTime as i, omap as o, pairs as p, set as s, timestamp as t, warn as w };
Index: frontend/node_modules/yaml/browser/index.js
===================================================================
--- frontend/node_modules/yaml/browser/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+module.exports = require('./dist').YAML
Index: frontend/node_modules/yaml/browser/map.js
===================================================================
--- frontend/node_modules/yaml/browser/map.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/map.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2 @@
+module.exports = require('./dist/types').YAMLMap
+require('./dist/legacy-exports').warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/browser/pair.js
===================================================================
--- frontend/node_modules/yaml/browser/pair.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/pair.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2 @@
+module.exports = require('./dist/types').Pair
+require('./dist/legacy-exports').warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/browser/parse-cst.js
===================================================================
--- frontend/node_modules/yaml/browser/parse-cst.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/parse-cst.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+module.exports = require('./dist/parse-cst').parse
Index: frontend/node_modules/yaml/browser/scalar.js
===================================================================
--- frontend/node_modules/yaml/browser/scalar.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/scalar.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2 @@
+module.exports = require('./dist/types').Scalar
+require('./dist/legacy-exports').warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/browser/schema.js
===================================================================
--- frontend/node_modules/yaml/browser/schema.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/schema.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,9 @@
+const types = require('./dist/types')
+const util = require('./dist/util')
+
+module.exports = types.Schema
+module.exports.nullOptions = types.nullOptions
+module.exports.strOptions = types.strOptions
+module.exports.stringify = util.stringifyString
+
+require('./dist/legacy-exports').warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/browser/seq.js
===================================================================
--- frontend/node_modules/yaml/browser/seq.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/seq.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2 @@
+module.exports = require('./dist/types').YAMLSeq
+require('./dist/legacy-exports').warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/browser/types.js
===================================================================
--- frontend/node_modules/yaml/browser/types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+module.exports = require('./dist/types')
Index: frontend/node_modules/yaml/browser/types/binary.js
===================================================================
--- frontend/node_modules/yaml/browser/types/binary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/types/binary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,8 @@
+'use strict'
+Object.defineProperty(exports, '__esModule', { value: true })
+
+const legacy = require('../dist/legacy-exports')
+exports.binary = legacy.binary
+exports.default = [exports.binary]
+
+legacy.warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/browser/types/omap.js
===================================================================
--- frontend/node_modules/yaml/browser/types/omap.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/types/omap.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+const legacy = require('../dist/legacy-exports')
+module.exports = legacy.omap
+legacy.warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/browser/types/pairs.js
===================================================================
--- frontend/node_modules/yaml/browser/types/pairs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/types/pairs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+const legacy = require('../dist/legacy-exports')
+module.exports = legacy.pairs
+legacy.warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/browser/types/set.js
===================================================================
--- frontend/node_modules/yaml/browser/types/set.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/types/set.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+const legacy = require('../dist/legacy-exports')
+module.exports = legacy.set
+legacy.warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/browser/types/timestamp.js
===================================================================
--- frontend/node_modules/yaml/browser/types/timestamp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/types/timestamp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,10 @@
+'use strict'
+Object.defineProperty(exports, '__esModule', { value: true })
+
+const legacy = require('../dist/legacy-exports')
+exports.default = [legacy.intTime, legacy.floatTime, legacy.timestamp]
+exports.floatTime = legacy.floatTime
+exports.intTime = legacy.intTime
+exports.timestamp = legacy.timestamp
+
+legacy.warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/browser/util.js
===================================================================
--- frontend/node_modules/yaml/browser/util.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/browser/util.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+module.exports = require('./dist/util')
Index: frontend/node_modules/yaml/dist/Document-a8d0fbf9.js
===================================================================
--- frontend/node_modules/yaml/dist/Document-a8d0fbf9.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/dist/Document-a8d0fbf9.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,637 @@
+'use strict';
+
+var PlainValue = require('./PlainValue-516d5bc2.js');
+var resolveSeq = require('./resolveSeq-95613e94.js');
+var Schema = require('./Schema-bcc6c2d7.js');
+
+const defaultOptions = {
+  anchorPrefix: 'a',
+  customTags: null,
+  indent: 2,
+  indentSeq: true,
+  keepCstNodes: false,
+  keepNodeTypes: true,
+  keepBlobsInJSON: true,
+  mapAsMap: false,
+  maxAliasCount: 100,
+  prettyErrors: false,
+  // TODO Set true in v2
+  simpleKeys: false,
+  version: '1.2'
+};
+const scalarOptions = {
+  get binary() {
+    return resolveSeq.binaryOptions;
+  },
+  set binary(opt) {
+    Object.assign(resolveSeq.binaryOptions, opt);
+  },
+  get bool() {
+    return resolveSeq.boolOptions;
+  },
+  set bool(opt) {
+    Object.assign(resolveSeq.boolOptions, opt);
+  },
+  get int() {
+    return resolveSeq.intOptions;
+  },
+  set int(opt) {
+    Object.assign(resolveSeq.intOptions, opt);
+  },
+  get null() {
+    return resolveSeq.nullOptions;
+  },
+  set null(opt) {
+    Object.assign(resolveSeq.nullOptions, opt);
+  },
+  get str() {
+    return resolveSeq.strOptions;
+  },
+  set str(opt) {
+    Object.assign(resolveSeq.strOptions, opt);
+  }
+};
+const documentOptions = {
+  '1.0': {
+    schema: 'yaml-1.1',
+    merge: true,
+    tagPrefixes: [{
+      handle: '!',
+      prefix: PlainValue.defaultTagPrefix
+    }, {
+      handle: '!!',
+      prefix: 'tag:private.yaml.org,2002:'
+    }]
+  },
+  1.1: {
+    schema: 'yaml-1.1',
+    merge: true,
+    tagPrefixes: [{
+      handle: '!',
+      prefix: '!'
+    }, {
+      handle: '!!',
+      prefix: PlainValue.defaultTagPrefix
+    }]
+  },
+  1.2: {
+    schema: 'core',
+    merge: false,
+    tagPrefixes: [{
+      handle: '!',
+      prefix: '!'
+    }, {
+      handle: '!!',
+      prefix: PlainValue.defaultTagPrefix
+    }]
+  }
+};
+
+function stringifyTag(doc, tag) {
+  if ((doc.version || doc.options.version) === '1.0') {
+    const priv = tag.match(/^tag:private\.yaml\.org,2002:([^:/]+)$/);
+    if (priv) return '!' + priv[1];
+    const vocab = tag.match(/^tag:([a-zA-Z0-9-]+)\.yaml\.org,2002:(.*)/);
+    return vocab ? `!${vocab[1]}/${vocab[2]}` : `!${tag.replace(/^tag:/, '')}`;
+  }
+  let p = doc.tagPrefixes.find(p => tag.indexOf(p.prefix) === 0);
+  if (!p) {
+    const dtp = doc.getDefaults().tagPrefixes;
+    p = dtp && dtp.find(p => tag.indexOf(p.prefix) === 0);
+  }
+  if (!p) return tag[0] === '!' ? tag : `!<${tag}>`;
+  const suffix = tag.substr(p.prefix.length).replace(/[!,[\]{}]/g, ch => ({
+    '!': '%21',
+    ',': '%2C',
+    '[': '%5B',
+    ']': '%5D',
+    '{': '%7B',
+    '}': '%7D'
+  })[ch]);
+  return p.handle + suffix;
+}
+
+function getTagObject(tags, item) {
+  if (item instanceof resolveSeq.Alias) return resolveSeq.Alias;
+  if (item.tag) {
+    const match = tags.filter(t => t.tag === item.tag);
+    if (match.length > 0) return match.find(t => t.format === item.format) || match[0];
+  }
+  let tagObj, obj;
+  if (item instanceof resolveSeq.Scalar) {
+    obj = item.value;
+    // TODO: deprecate/remove class check
+    const match = tags.filter(t => t.identify && t.identify(obj) || t.class && obj instanceof t.class);
+    tagObj = match.find(t => t.format === item.format) || match.find(t => !t.format);
+  } else {
+    obj = item;
+    tagObj = tags.find(t => t.nodeClass && obj instanceof t.nodeClass);
+  }
+  if (!tagObj) {
+    const name = obj && obj.constructor ? obj.constructor.name : typeof obj;
+    throw new Error(`Tag not resolved for ${name} value`);
+  }
+  return tagObj;
+}
+
+// needs to be called before value stringifier to allow for circular anchor refs
+function stringifyProps(node, tagObj, {
+  anchors,
+  doc
+}) {
+  const props = [];
+  const anchor = doc.anchors.getName(node);
+  if (anchor) {
+    anchors[anchor] = node;
+    props.push(`&${anchor}`);
+  }
+  if (node.tag) {
+    props.push(stringifyTag(doc, node.tag));
+  } else if (!tagObj.default) {
+    props.push(stringifyTag(doc, tagObj.tag));
+  }
+  return props.join(' ');
+}
+function stringify(item, ctx, onComment, onChompKeep) {
+  const {
+    anchors,
+    schema
+  } = ctx.doc;
+  let tagObj;
+  if (!(item instanceof resolveSeq.Node)) {
+    const createCtx = {
+      aliasNodes: [],
+      onTagObj: o => tagObj = o,
+      prevObjects: new Map()
+    };
+    item = schema.createNode(item, true, null, createCtx);
+    for (const alias of createCtx.aliasNodes) {
+      alias.source = alias.source.node;
+      let name = anchors.getName(alias.source);
+      if (!name) {
+        name = anchors.newName();
+        anchors.map[name] = alias.source;
+      }
+    }
+  }
+  if (item instanceof resolveSeq.Pair) return item.toString(ctx, onComment, onChompKeep);
+  if (!tagObj) tagObj = getTagObject(schema.tags, item);
+  const props = stringifyProps(item, tagObj, ctx);
+  if (props.length > 0) ctx.indentAtStart = (ctx.indentAtStart || 0) + props.length + 1;
+  const str = typeof tagObj.stringify === 'function' ? tagObj.stringify(item, ctx, onComment, onChompKeep) : item instanceof resolveSeq.Scalar ? resolveSeq.stringifyString(item, ctx, onComment, onChompKeep) : item.toString(ctx, onComment, onChompKeep);
+  if (!props) return str;
+  return item instanceof resolveSeq.Scalar || str[0] === '{' || str[0] === '[' ? `${props} ${str}` : `${props}\n${ctx.indent}${str}`;
+}
+
+class Anchors {
+  static validAnchorNode(node) {
+    return node instanceof resolveSeq.Scalar || node instanceof resolveSeq.YAMLSeq || node instanceof resolveSeq.YAMLMap;
+  }
+  constructor(prefix) {
+    PlainValue._defineProperty(this, "map", Object.create(null));
+    this.prefix = prefix;
+  }
+  createAlias(node, name) {
+    this.setAnchor(node, name);
+    return new resolveSeq.Alias(node);
+  }
+  createMergePair(...sources) {
+    const merge = new resolveSeq.Merge();
+    merge.value.items = sources.map(s => {
+      if (s instanceof resolveSeq.Alias) {
+        if (s.source instanceof resolveSeq.YAMLMap) return s;
+      } else if (s instanceof resolveSeq.YAMLMap) {
+        return this.createAlias(s);
+      }
+      throw new Error('Merge sources must be Map nodes or their Aliases');
+    });
+    return merge;
+  }
+  getName(node) {
+    const {
+      map
+    } = this;
+    return Object.keys(map).find(a => map[a] === node);
+  }
+  getNames() {
+    return Object.keys(this.map);
+  }
+  getNode(name) {
+    return this.map[name];
+  }
+  newName(prefix) {
+    if (!prefix) prefix = this.prefix;
+    const names = Object.keys(this.map);
+    for (let i = 1; true; ++i) {
+      const name = `${prefix}${i}`;
+      if (!names.includes(name)) return name;
+    }
+  }
+
+  // During parsing, map & aliases contain CST nodes
+  resolveNodes() {
+    const {
+      map,
+      _cstAliases
+    } = this;
+    Object.keys(map).forEach(a => {
+      map[a] = map[a].resolved;
+    });
+    _cstAliases.forEach(a => {
+      a.source = a.source.resolved;
+    });
+    delete this._cstAliases;
+  }
+  setAnchor(node, name) {
+    if (node != null && !Anchors.validAnchorNode(node)) {
+      throw new Error('Anchors may only be set for Scalar, Seq and Map nodes');
+    }
+    if (name && /[\x00-\x19\s,[\]{}]/.test(name)) {
+      throw new Error('Anchor names must not contain whitespace or control characters');
+    }
+    const {
+      map
+    } = this;
+    const prev = node && Object.keys(map).find(a => map[a] === node);
+    if (prev) {
+      if (!name) {
+        return prev;
+      } else if (prev !== name) {
+        delete map[prev];
+        map[name] = node;
+      }
+    } else {
+      if (!name) {
+        if (!node) return null;
+        name = this.newName();
+      }
+      map[name] = node;
+    }
+    return name;
+  }
+}
+
+const visit = (node, tags) => {
+  if (node && typeof node === 'object') {
+    const {
+      tag
+    } = node;
+    if (node instanceof resolveSeq.Collection) {
+      if (tag) tags[tag] = true;
+      node.items.forEach(n => visit(n, tags));
+    } else if (node instanceof resolveSeq.Pair) {
+      visit(node.key, tags);
+      visit(node.value, tags);
+    } else if (node instanceof resolveSeq.Scalar) {
+      if (tag) tags[tag] = true;
+    }
+  }
+  return tags;
+};
+const listTagNames = node => Object.keys(visit(node, {}));
+
+function parseContents(doc, contents) {
+  const comments = {
+    before: [],
+    after: []
+  };
+  let body = undefined;
+  let spaceBefore = false;
+  for (const node of contents) {
+    if (node.valueRange) {
+      if (body !== undefined) {
+        const msg = 'Document contains trailing content not separated by a ... or --- line';
+        doc.errors.push(new PlainValue.YAMLSyntaxError(node, msg));
+        break;
+      }
+      const res = resolveSeq.resolveNode(doc, node);
+      if (spaceBefore) {
+        res.spaceBefore = true;
+        spaceBefore = false;
+      }
+      body = res;
+    } else if (node.comment !== null) {
+      const cc = body === undefined ? comments.before : comments.after;
+      cc.push(node.comment);
+    } else if (node.type === PlainValue.Type.BLANK_LINE) {
+      spaceBefore = true;
+      if (body === undefined && comments.before.length > 0 && !doc.commentBefore) {
+        // space-separated comments at start are parsed as document comments
+        doc.commentBefore = comments.before.join('\n');
+        comments.before = [];
+      }
+    }
+  }
+  doc.contents = body || null;
+  if (!body) {
+    doc.comment = comments.before.concat(comments.after).join('\n') || null;
+  } else {
+    const cb = comments.before.join('\n');
+    if (cb) {
+      const cbNode = body instanceof resolveSeq.Collection && body.items[0] ? body.items[0] : body;
+      cbNode.commentBefore = cbNode.commentBefore ? `${cb}\n${cbNode.commentBefore}` : cb;
+    }
+    doc.comment = comments.after.join('\n') || null;
+  }
+}
+
+function resolveTagDirective({
+  tagPrefixes
+}, directive) {
+  const [handle, prefix] = directive.parameters;
+  if (!handle || !prefix) {
+    const msg = 'Insufficient parameters given for %TAG directive';
+    throw new PlainValue.YAMLSemanticError(directive, msg);
+  }
+  if (tagPrefixes.some(p => p.handle === handle)) {
+    const msg = 'The %TAG directive must only be given at most once per handle in the same document.';
+    throw new PlainValue.YAMLSemanticError(directive, msg);
+  }
+  return {
+    handle,
+    prefix
+  };
+}
+function resolveYamlDirective(doc, directive) {
+  let [version] = directive.parameters;
+  if (directive.name === 'YAML:1.0') version = '1.0';
+  if (!version) {
+    const msg = 'Insufficient parameters given for %YAML directive';
+    throw new PlainValue.YAMLSemanticError(directive, msg);
+  }
+  if (!documentOptions[version]) {
+    const v0 = doc.version || doc.options.version;
+    const msg = `Document will be parsed as YAML ${v0} rather than YAML ${version}`;
+    doc.warnings.push(new PlainValue.YAMLWarning(directive, msg));
+  }
+  return version;
+}
+function parseDirectives(doc, directives, prevDoc) {
+  const directiveComments = [];
+  let hasDirectives = false;
+  for (const directive of directives) {
+    const {
+      comment,
+      name
+    } = directive;
+    switch (name) {
+      case 'TAG':
+        try {
+          doc.tagPrefixes.push(resolveTagDirective(doc, directive));
+        } catch (error) {
+          doc.errors.push(error);
+        }
+        hasDirectives = true;
+        break;
+      case 'YAML':
+      case 'YAML:1.0':
+        if (doc.version) {
+          const msg = 'The %YAML directive must only be given at most once per document.';
+          doc.errors.push(new PlainValue.YAMLSemanticError(directive, msg));
+        }
+        try {
+          doc.version = resolveYamlDirective(doc, directive);
+        } catch (error) {
+          doc.errors.push(error);
+        }
+        hasDirectives = true;
+        break;
+      default:
+        if (name) {
+          const msg = `YAML only supports %TAG and %YAML directives, and not %${name}`;
+          doc.warnings.push(new PlainValue.YAMLWarning(directive, msg));
+        }
+    }
+    if (comment) directiveComments.push(comment);
+  }
+  if (prevDoc && !hasDirectives && '1.1' === (doc.version || prevDoc.version || doc.options.version)) {
+    const copyTagPrefix = ({
+      handle,
+      prefix
+    }) => ({
+      handle,
+      prefix
+    });
+    doc.tagPrefixes = prevDoc.tagPrefixes.map(copyTagPrefix);
+    doc.version = prevDoc.version;
+  }
+  doc.commentBefore = directiveComments.join('\n') || null;
+}
+
+function assertCollection(contents) {
+  if (contents instanceof resolveSeq.Collection) return true;
+  throw new Error('Expected a YAML collection as document contents');
+}
+class Document {
+  constructor(options) {
+    this.anchors = new Anchors(options.anchorPrefix);
+    this.commentBefore = null;
+    this.comment = null;
+    this.contents = null;
+    this.directivesEndMarker = null;
+    this.errors = [];
+    this.options = options;
+    this.schema = null;
+    this.tagPrefixes = [];
+    this.version = null;
+    this.warnings = [];
+  }
+  add(value) {
+    assertCollection(this.contents);
+    return this.contents.add(value);
+  }
+  addIn(path, value) {
+    assertCollection(this.contents);
+    this.contents.addIn(path, value);
+  }
+  delete(key) {
+    assertCollection(this.contents);
+    return this.contents.delete(key);
+  }
+  deleteIn(path) {
+    if (resolveSeq.isEmptyPath(path)) {
+      if (this.contents == null) return false;
+      this.contents = null;
+      return true;
+    }
+    assertCollection(this.contents);
+    return this.contents.deleteIn(path);
+  }
+  getDefaults() {
+    return Document.defaults[this.version] || Document.defaults[this.options.version] || {};
+  }
+  get(key, keepScalar) {
+    return this.contents instanceof resolveSeq.Collection ? this.contents.get(key, keepScalar) : undefined;
+  }
+  getIn(path, keepScalar) {
+    if (resolveSeq.isEmptyPath(path)) return !keepScalar && this.contents instanceof resolveSeq.Scalar ? this.contents.value : this.contents;
+    return this.contents instanceof resolveSeq.Collection ? this.contents.getIn(path, keepScalar) : undefined;
+  }
+  has(key) {
+    return this.contents instanceof resolveSeq.Collection ? this.contents.has(key) : false;
+  }
+  hasIn(path) {
+    if (resolveSeq.isEmptyPath(path)) return this.contents !== undefined;
+    return this.contents instanceof resolveSeq.Collection ? this.contents.hasIn(path) : false;
+  }
+  set(key, value) {
+    assertCollection(this.contents);
+    this.contents.set(key, value);
+  }
+  setIn(path, value) {
+    if (resolveSeq.isEmptyPath(path)) this.contents = value;else {
+      assertCollection(this.contents);
+      this.contents.setIn(path, value);
+    }
+  }
+  setSchema(id, customTags) {
+    if (!id && !customTags && this.schema) return;
+    if (typeof id === 'number') id = id.toFixed(1);
+    if (id === '1.0' || id === '1.1' || id === '1.2') {
+      if (this.version) this.version = id;else this.options.version = id;
+      delete this.options.schema;
+    } else if (id && typeof id === 'string') {
+      this.options.schema = id;
+    }
+    if (Array.isArray(customTags)) this.options.customTags = customTags;
+    const opt = Object.assign({}, this.getDefaults(), this.options);
+    this.schema = new Schema.Schema(opt);
+  }
+  parse(node, prevDoc) {
+    if (this.options.keepCstNodes) this.cstNode = node;
+    if (this.options.keepNodeTypes) this.type = 'DOCUMENT';
+    const {
+      directives = [],
+      contents = [],
+      directivesEndMarker,
+      error,
+      valueRange
+    } = node;
+    if (error) {
+      if (!error.source) error.source = this;
+      this.errors.push(error);
+    }
+    parseDirectives(this, directives, prevDoc);
+    if (directivesEndMarker) this.directivesEndMarker = true;
+    this.range = valueRange ? [valueRange.start, valueRange.end] : null;
+    this.setSchema();
+    this.anchors._cstAliases = [];
+    parseContents(this, contents);
+    this.anchors.resolveNodes();
+    if (this.options.prettyErrors) {
+      for (const error of this.errors) if (error instanceof PlainValue.YAMLError) error.makePretty();
+      for (const warn of this.warnings) if (warn instanceof PlainValue.YAMLError) warn.makePretty();
+    }
+    return this;
+  }
+  listNonDefaultTags() {
+    return listTagNames(this.contents).filter(t => t.indexOf(Schema.Schema.defaultPrefix) !== 0);
+  }
+  setTagPrefix(handle, prefix) {
+    if (handle[0] !== '!' || handle[handle.length - 1] !== '!') throw new Error('Handle must start and end with !');
+    if (prefix) {
+      const prev = this.tagPrefixes.find(p => p.handle === handle);
+      if (prev) prev.prefix = prefix;else this.tagPrefixes.push({
+        handle,
+        prefix
+      });
+    } else {
+      this.tagPrefixes = this.tagPrefixes.filter(p => p.handle !== handle);
+    }
+  }
+  toJSON(arg, onAnchor) {
+    const {
+      keepBlobsInJSON,
+      mapAsMap,
+      maxAliasCount
+    } = this.options;
+    const keep = keepBlobsInJSON && (typeof arg !== 'string' || !(this.contents instanceof resolveSeq.Scalar));
+    const ctx = {
+      doc: this,
+      indentStep: '  ',
+      keep,
+      mapAsMap: keep && !!mapAsMap,
+      maxAliasCount,
+      stringify // Requiring directly in Pair would create circular dependencies
+    };
+    const anchorNames = Object.keys(this.anchors.map);
+    if (anchorNames.length > 0) ctx.anchors = new Map(anchorNames.map(name => [this.anchors.map[name], {
+      alias: [],
+      aliasCount: 0,
+      count: 1
+    }]));
+    const res = resolveSeq.toJSON(this.contents, arg, ctx);
+    if (typeof onAnchor === 'function' && ctx.anchors) for (const {
+      count,
+      res
+    } of ctx.anchors.values()) onAnchor(res, count);
+    return res;
+  }
+  toString() {
+    if (this.errors.length > 0) throw new Error('Document with errors cannot be stringified');
+    const indentSize = this.options.indent;
+    if (!Number.isInteger(indentSize) || indentSize <= 0) {
+      const s = JSON.stringify(indentSize);
+      throw new Error(`"indent" option must be a positive integer, not ${s}`);
+    }
+    this.setSchema();
+    const lines = [];
+    let hasDirectives = false;
+    if (this.version) {
+      let vd = '%YAML 1.2';
+      if (this.schema.name === 'yaml-1.1') {
+        if (this.version === '1.0') vd = '%YAML:1.0';else if (this.version === '1.1') vd = '%YAML 1.1';
+      }
+      lines.push(vd);
+      hasDirectives = true;
+    }
+    const tagNames = this.listNonDefaultTags();
+    this.tagPrefixes.forEach(({
+      handle,
+      prefix
+    }) => {
+      if (tagNames.some(t => t.indexOf(prefix) === 0)) {
+        lines.push(`%TAG ${handle} ${prefix}`);
+        hasDirectives = true;
+      }
+    });
+    if (hasDirectives || this.directivesEndMarker) lines.push('---');
+    if (this.commentBefore) {
+      if (hasDirectives || !this.directivesEndMarker) lines.unshift('');
+      lines.unshift(this.commentBefore.replace(/^/gm, '#'));
+    }
+    const ctx = {
+      anchors: Object.create(null),
+      doc: this,
+      indent: '',
+      indentStep: ' '.repeat(indentSize),
+      stringify // Requiring directly in nodes would create circular dependencies
+    };
+    let chompKeep = false;
+    let contentComment = null;
+    if (this.contents) {
+      if (this.contents instanceof resolveSeq.Node) {
+        if (this.contents.spaceBefore && (hasDirectives || this.directivesEndMarker)) lines.push('');
+        if (this.contents.commentBefore) lines.push(this.contents.commentBefore.replace(/^/gm, '#'));
+        // top-level block scalars need to be indented if followed by a comment
+        ctx.forceBlockIndent = !!this.comment;
+        contentComment = this.contents.comment;
+      }
+      const onChompKeep = contentComment ? null : () => chompKeep = true;
+      const body = stringify(this.contents, ctx, () => contentComment = null, onChompKeep);
+      lines.push(resolveSeq.addComment(body, '', contentComment));
+    } else if (this.contents !== undefined) {
+      lines.push(stringify(this.contents, ctx));
+    }
+    if (this.comment) {
+      if ((!chompKeep || contentComment) && lines[lines.length - 1] !== '') lines.push('');
+      lines.push(this.comment.replace(/^/gm, '#'));
+    }
+    return lines.join('\n') + '\n';
+  }
+}
+PlainValue._defineProperty(Document, "defaults", documentOptions);
+
+exports.Document = Document;
+exports.defaultOptions = defaultOptions;
+exports.scalarOptions = scalarOptions;
Index: frontend/node_modules/yaml/dist/PlainValue-516d5bc2.js
===================================================================
--- frontend/node_modules/yaml/dist/PlainValue-516d5bc2.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/dist/PlainValue-516d5bc2.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,765 @@
+'use strict';
+
+const Char = {
+  ANCHOR: '&',
+  COMMENT: '#',
+  TAG: '!',
+  DIRECTIVES_END: '-',
+  DOCUMENT_END: '.'
+};
+const Type = {
+  ALIAS: 'ALIAS',
+  BLANK_LINE: 'BLANK_LINE',
+  BLOCK_FOLDED: 'BLOCK_FOLDED',
+  BLOCK_LITERAL: 'BLOCK_LITERAL',
+  COMMENT: 'COMMENT',
+  DIRECTIVE: 'DIRECTIVE',
+  DOCUMENT: 'DOCUMENT',
+  FLOW_MAP: 'FLOW_MAP',
+  FLOW_SEQ: 'FLOW_SEQ',
+  MAP: 'MAP',
+  MAP_KEY: 'MAP_KEY',
+  MAP_VALUE: 'MAP_VALUE',
+  PLAIN: 'PLAIN',
+  QUOTE_DOUBLE: 'QUOTE_DOUBLE',
+  QUOTE_SINGLE: 'QUOTE_SINGLE',
+  SEQ: 'SEQ',
+  SEQ_ITEM: 'SEQ_ITEM'
+};
+const defaultTagPrefix = 'tag:yaml.org,2002:';
+const defaultTags = {
+  MAP: 'tag:yaml.org,2002:map',
+  SEQ: 'tag:yaml.org,2002:seq',
+  STR: 'tag:yaml.org,2002:str'
+};
+
+function findLineStarts(src) {
+  const ls = [0];
+  let offset = src.indexOf('\n');
+  while (offset !== -1) {
+    offset += 1;
+    ls.push(offset);
+    offset = src.indexOf('\n', offset);
+  }
+  return ls;
+}
+function getSrcInfo(cst) {
+  let lineStarts, src;
+  if (typeof cst === 'string') {
+    lineStarts = findLineStarts(cst);
+    src = cst;
+  } else {
+    if (Array.isArray(cst)) cst = cst[0];
+    if (cst && cst.context) {
+      if (!cst.lineStarts) cst.lineStarts = findLineStarts(cst.context.src);
+      lineStarts = cst.lineStarts;
+      src = cst.context.src;
+    }
+  }
+  return {
+    lineStarts,
+    src
+  };
+}
+
+/**
+ * @typedef {Object} LinePos - One-indexed position in the source
+ * @property {number} line
+ * @property {number} col
+ */
+
+/**
+ * Determine the line/col position matching a character offset.
+ *
+ * Accepts a source string or a CST document as the second parameter. With
+ * the latter, starting indices for lines are cached in the document as
+ * `lineStarts: number[]`.
+ *
+ * Returns a one-indexed `{ line, col }` location if found, or
+ * `undefined` otherwise.
+ *
+ * @param {number} offset
+ * @param {string|Document|Document[]} cst
+ * @returns {?LinePos}
+ */
+function getLinePos(offset, cst) {
+  if (typeof offset !== 'number' || offset < 0) return null;
+  const {
+    lineStarts,
+    src
+  } = getSrcInfo(cst);
+  if (!lineStarts || !src || offset > src.length) return null;
+  for (let i = 0; i < lineStarts.length; ++i) {
+    const start = lineStarts[i];
+    if (offset < start) {
+      return {
+        line: i,
+        col: offset - lineStarts[i - 1] + 1
+      };
+    }
+    if (offset === start) return {
+      line: i + 1,
+      col: 1
+    };
+  }
+  const line = lineStarts.length;
+  return {
+    line,
+    col: offset - lineStarts[line - 1] + 1
+  };
+}
+
+/**
+ * Get a specified line from the source.
+ *
+ * Accepts a source string or a CST document as the second parameter. With
+ * the latter, starting indices for lines are cached in the document as
+ * `lineStarts: number[]`.
+ *
+ * Returns the line as a string if found, or `null` otherwise.
+ *
+ * @param {number} line One-indexed line number
+ * @param {string|Document|Document[]} cst
+ * @returns {?string}
+ */
+function getLine(line, cst) {
+  const {
+    lineStarts,
+    src
+  } = getSrcInfo(cst);
+  if (!lineStarts || !(line >= 1) || line > lineStarts.length) return null;
+  const start = lineStarts[line - 1];
+  let end = lineStarts[line]; // undefined for last line; that's ok for slice()
+  while (end && end > start && src[end - 1] === '\n') --end;
+  return src.slice(start, end);
+}
+
+/**
+ * Pretty-print the starting line from the source indicated by the range `pos`
+ *
+ * Trims output to `maxWidth` chars while keeping the starting column visible,
+ * using `…` at either end to indicate dropped characters.
+ *
+ * Returns a two-line string (or `null`) with `\n` as separator; the second line
+ * will hold appropriately indented `^` marks indicating the column range.
+ *
+ * @param {Object} pos
+ * @param {LinePos} pos.start
+ * @param {LinePos} [pos.end]
+ * @param {string|Document|Document[]*} cst
+ * @param {number} [maxWidth=80]
+ * @returns {?string}
+ */
+function getPrettyContext({
+  start,
+  end
+}, cst, maxWidth = 80) {
+  let src = getLine(start.line, cst);
+  if (!src) return null;
+  let {
+    col
+  } = start;
+  if (src.length > maxWidth) {
+    if (col <= maxWidth - 10) {
+      src = src.substr(0, maxWidth - 1) + '…';
+    } else {
+      const halfWidth = Math.round(maxWidth / 2);
+      if (src.length > col + halfWidth) src = src.substr(0, col + halfWidth - 1) + '…';
+      col -= src.length - maxWidth;
+      src = '…' + src.substr(1 - maxWidth);
+    }
+  }
+  let errLen = 1;
+  let errEnd = '';
+  if (end) {
+    if (end.line === start.line && col + (end.col - start.col) <= maxWidth + 1) {
+      errLen = end.col - start.col;
+    } else {
+      errLen = Math.min(src.length + 1, maxWidth) - col;
+      errEnd = '…';
+    }
+  }
+  const offset = col > 1 ? ' '.repeat(col - 1) : '';
+  const err = '^'.repeat(errLen);
+  return `${src}\n${offset}${err}${errEnd}`;
+}
+
+class Range {
+  static copy(orig) {
+    return new Range(orig.start, orig.end);
+  }
+  constructor(start, end) {
+    this.start = start;
+    this.end = end || start;
+  }
+  isEmpty() {
+    return typeof this.start !== 'number' || !this.end || this.end <= this.start;
+  }
+
+  /**
+   * Set `origStart` and `origEnd` to point to the original source range for
+   * this node, which may differ due to dropped CR characters.
+   *
+   * @param {number[]} cr - Positions of dropped CR characters
+   * @param {number} offset - Starting index of `cr` from the last call
+   * @returns {number} - The next offset, matching the one found for `origStart`
+   */
+  setOrigRange(cr, offset) {
+    const {
+      start,
+      end
+    } = this;
+    if (cr.length === 0 || end <= cr[0]) {
+      this.origStart = start;
+      this.origEnd = end;
+      return offset;
+    }
+    let i = offset;
+    while (i < cr.length) {
+      if (cr[i] > start) break;else ++i;
+    }
+    this.origStart = start + i;
+    const nextOffset = i;
+    while (i < cr.length) {
+      // if end was at \n, it should now be at \r
+      if (cr[i] >= end) break;else ++i;
+    }
+    this.origEnd = end + i;
+    return nextOffset;
+  }
+}
+
+/** Root class of all nodes */
+class Node {
+  static addStringTerminator(src, offset, str) {
+    if (str[str.length - 1] === '\n') return str;
+    const next = Node.endOfWhiteSpace(src, offset);
+    return next >= src.length || src[next] === '\n' ? str + '\n' : str;
+  }
+
+  // ^(---|...)
+  static atDocumentBoundary(src, offset, sep) {
+    const ch0 = src[offset];
+    if (!ch0) return true;
+    const prev = src[offset - 1];
+    if (prev && prev !== '\n') return false;
+    if (sep) {
+      if (ch0 !== sep) return false;
+    } else {
+      if (ch0 !== Char.DIRECTIVES_END && ch0 !== Char.DOCUMENT_END) return false;
+    }
+    const ch1 = src[offset + 1];
+    const ch2 = src[offset + 2];
+    if (ch1 !== ch0 || ch2 !== ch0) return false;
+    const ch3 = src[offset + 3];
+    return !ch3 || ch3 === '\n' || ch3 === '\t' || ch3 === ' ';
+  }
+  static endOfIdentifier(src, offset) {
+    let ch = src[offset];
+    const isVerbatim = ch === '<';
+    const notOk = isVerbatim ? ['\n', '\t', ' ', '>'] : ['\n', '\t', ' ', '[', ']', '{', '}', ','];
+    while (ch && notOk.indexOf(ch) === -1) ch = src[offset += 1];
+    if (isVerbatim && ch === '>') offset += 1;
+    return offset;
+  }
+  static endOfIndent(src, offset) {
+    let ch = src[offset];
+    while (ch === ' ') ch = src[offset += 1];
+    return offset;
+  }
+  static endOfLine(src, offset) {
+    let ch = src[offset];
+    while (ch && ch !== '\n') ch = src[offset += 1];
+    return offset;
+  }
+  static endOfWhiteSpace(src, offset) {
+    let ch = src[offset];
+    while (ch === '\t' || ch === ' ') ch = src[offset += 1];
+    return offset;
+  }
+  static startOfLine(src, offset) {
+    let ch = src[offset - 1];
+    if (ch === '\n') return offset;
+    while (ch && ch !== '\n') ch = src[offset -= 1];
+    return offset + 1;
+  }
+
+  /**
+   * End of indentation, or null if the line's indent level is not more
+   * than `indent`
+   *
+   * @param {string} src
+   * @param {number} indent
+   * @param {number} lineStart
+   * @returns {?number}
+   */
+  static endOfBlockIndent(src, indent, lineStart) {
+    const inEnd = Node.endOfIndent(src, lineStart);
+    if (inEnd > lineStart + indent) {
+      return inEnd;
+    } else {
+      const wsEnd = Node.endOfWhiteSpace(src, inEnd);
+      const ch = src[wsEnd];
+      if (!ch || ch === '\n') return wsEnd;
+    }
+    return null;
+  }
+  static atBlank(src, offset, endAsBlank) {
+    const ch = src[offset];
+    return ch === '\n' || ch === '\t' || ch === ' ' || endAsBlank && !ch;
+  }
+  static nextNodeIsIndented(ch, indentDiff, indicatorAsIndent) {
+    if (!ch || indentDiff < 0) return false;
+    if (indentDiff > 0) return true;
+    return indicatorAsIndent && ch === '-';
+  }
+
+  // should be at line or string end, or at next non-whitespace char
+  static normalizeOffset(src, offset) {
+    const ch = src[offset];
+    return !ch ? offset : ch !== '\n' && src[offset - 1] === '\n' ? offset - 1 : Node.endOfWhiteSpace(src, offset);
+  }
+
+  // fold single newline into space, multiple newlines to N - 1 newlines
+  // presumes src[offset] === '\n'
+  static foldNewline(src, offset, indent) {
+    let inCount = 0;
+    let error = false;
+    let fold = '';
+    let ch = src[offset + 1];
+    while (ch === ' ' || ch === '\t' || ch === '\n') {
+      switch (ch) {
+        case '\n':
+          inCount = 0;
+          offset += 1;
+          fold += '\n';
+          break;
+        case '\t':
+          if (inCount <= indent) error = true;
+          offset = Node.endOfWhiteSpace(src, offset + 2) - 1;
+          break;
+        case ' ':
+          inCount += 1;
+          offset += 1;
+          break;
+      }
+      ch = src[offset + 1];
+    }
+    if (!fold) fold = ' ';
+    if (ch && inCount <= indent) error = true;
+    return {
+      fold,
+      offset,
+      error
+    };
+  }
+  constructor(type, props, context) {
+    Object.defineProperty(this, 'context', {
+      value: context || null,
+      writable: true
+    });
+    this.error = null;
+    this.range = null;
+    this.valueRange = null;
+    this.props = props || [];
+    this.type = type;
+    this.value = null;
+  }
+  getPropValue(idx, key, skipKey) {
+    if (!this.context) return null;
+    const {
+      src
+    } = this.context;
+    const prop = this.props[idx];
+    return prop && src[prop.start] === key ? src.slice(prop.start + (skipKey ? 1 : 0), prop.end) : null;
+  }
+  get anchor() {
+    for (let i = 0; i < this.props.length; ++i) {
+      const anchor = this.getPropValue(i, Char.ANCHOR, true);
+      if (anchor != null) return anchor;
+    }
+    return null;
+  }
+  get comment() {
+    const comments = [];
+    for (let i = 0; i < this.props.length; ++i) {
+      const comment = this.getPropValue(i, Char.COMMENT, true);
+      if (comment != null) comments.push(comment);
+    }
+    return comments.length > 0 ? comments.join('\n') : null;
+  }
+  commentHasRequiredWhitespace(start) {
+    const {
+      src
+    } = this.context;
+    if (this.header && start === this.header.end) return false;
+    if (!this.valueRange) return false;
+    const {
+      end
+    } = this.valueRange;
+    return start !== end || Node.atBlank(src, end - 1);
+  }
+  get hasComment() {
+    if (this.context) {
+      const {
+        src
+      } = this.context;
+      for (let i = 0; i < this.props.length; ++i) {
+        if (src[this.props[i].start] === Char.COMMENT) return true;
+      }
+    }
+    return false;
+  }
+  get hasProps() {
+    if (this.context) {
+      const {
+        src
+      } = this.context;
+      for (let i = 0; i < this.props.length; ++i) {
+        if (src[this.props[i].start] !== Char.COMMENT) return true;
+      }
+    }
+    return false;
+  }
+  get includesTrailingLines() {
+    return false;
+  }
+  get jsonLike() {
+    const jsonLikeTypes = [Type.FLOW_MAP, Type.FLOW_SEQ, Type.QUOTE_DOUBLE, Type.QUOTE_SINGLE];
+    return jsonLikeTypes.indexOf(this.type) !== -1;
+  }
+  get rangeAsLinePos() {
+    if (!this.range || !this.context) return undefined;
+    const start = getLinePos(this.range.start, this.context.root);
+    if (!start) return undefined;
+    const end = getLinePos(this.range.end, this.context.root);
+    return {
+      start,
+      end
+    };
+  }
+  get rawValue() {
+    if (!this.valueRange || !this.context) return null;
+    const {
+      start,
+      end
+    } = this.valueRange;
+    return this.context.src.slice(start, end);
+  }
+  get tag() {
+    for (let i = 0; i < this.props.length; ++i) {
+      const tag = this.getPropValue(i, Char.TAG, false);
+      if (tag != null) {
+        if (tag[1] === '<') {
+          return {
+            verbatim: tag.slice(2, -1)
+          };
+        } else {
+          // eslint-disable-next-line no-unused-vars
+          const [_, handle, suffix] = tag.match(/^(.*!)([^!]*)$/);
+          return {
+            handle,
+            suffix
+          };
+        }
+      }
+    }
+    return null;
+  }
+  get valueRangeContainsNewline() {
+    if (!this.valueRange || !this.context) return false;
+    const {
+      start,
+      end
+    } = this.valueRange;
+    const {
+      src
+    } = this.context;
+    for (let i = start; i < end; ++i) {
+      if (src[i] === '\n') return true;
+    }
+    return false;
+  }
+  parseComment(start) {
+    const {
+      src
+    } = this.context;
+    if (src[start] === Char.COMMENT) {
+      const end = Node.endOfLine(src, start + 1);
+      const commentRange = new Range(start, end);
+      this.props.push(commentRange);
+      return end;
+    }
+    return start;
+  }
+
+  /**
+   * Populates the `origStart` and `origEnd` values of all ranges for this
+   * node. Extended by child classes to handle descendant nodes.
+   *
+   * @param {number[]} cr - Positions of dropped CR characters
+   * @param {number} offset - Starting index of `cr` from the last call
+   * @returns {number} - The next offset, matching the one found for `origStart`
+   */
+  setOrigRanges(cr, offset) {
+    if (this.range) offset = this.range.setOrigRange(cr, offset);
+    if (this.valueRange) this.valueRange.setOrigRange(cr, offset);
+    this.props.forEach(prop => prop.setOrigRange(cr, offset));
+    return offset;
+  }
+  toString() {
+    const {
+      context: {
+        src
+      },
+      range,
+      value
+    } = this;
+    if (value != null) return value;
+    const str = src.slice(range.start, range.end);
+    return Node.addStringTerminator(src, range.end, str);
+  }
+}
+
+class YAMLError extends Error {
+  constructor(name, source, message) {
+    if (!message || !(source instanceof Node)) throw new Error(`Invalid arguments for new ${name}`);
+    super();
+    this.name = name;
+    this.message = message;
+    this.source = source;
+  }
+  makePretty() {
+    if (!this.source) return;
+    this.nodeType = this.source.type;
+    const cst = this.source.context && this.source.context.root;
+    if (typeof this.offset === 'number') {
+      this.range = new Range(this.offset, this.offset + 1);
+      const start = cst && getLinePos(this.offset, cst);
+      if (start) {
+        const end = {
+          line: start.line,
+          col: start.col + 1
+        };
+        this.linePos = {
+          start,
+          end
+        };
+      }
+      delete this.offset;
+    } else {
+      this.range = this.source.range;
+      this.linePos = this.source.rangeAsLinePos;
+    }
+    if (this.linePos) {
+      const {
+        line,
+        col
+      } = this.linePos.start;
+      this.message += ` at line ${line}, column ${col}`;
+      const ctx = cst && getPrettyContext(this.linePos, cst);
+      if (ctx) this.message += `:\n\n${ctx}\n`;
+    }
+    delete this.source;
+  }
+}
+class YAMLReferenceError extends YAMLError {
+  constructor(source, message) {
+    super('YAMLReferenceError', source, message);
+  }
+}
+class YAMLSemanticError extends YAMLError {
+  constructor(source, message) {
+    super('YAMLSemanticError', source, message);
+  }
+}
+class YAMLSyntaxError extends YAMLError {
+  constructor(source, message) {
+    super('YAMLSyntaxError', source, message);
+  }
+}
+class YAMLWarning extends YAMLError {
+  constructor(source, message) {
+    super('YAMLWarning', source, message);
+  }
+}
+
+function _defineProperty(e, r, t) {
+  return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
+    value: t,
+    enumerable: !0,
+    configurable: !0,
+    writable: !0
+  }) : e[r] = t, e;
+}
+function _toPrimitive(t, r) {
+  if ("object" != typeof t || !t) return t;
+  var e = t[Symbol.toPrimitive];
+  if (void 0 !== e) {
+    var i = e.call(t, r || "default");
+    if ("object" != typeof i) return i;
+    throw new TypeError("@@toPrimitive must return a primitive value.");
+  }
+  return ("string" === r ? String : Number)(t);
+}
+function _toPropertyKey(t) {
+  var i = _toPrimitive(t, "string");
+  return "symbol" == typeof i ? i : i + "";
+}
+
+class PlainValue extends Node {
+  static endOfLine(src, start, inFlow) {
+    let ch = src[start];
+    let offset = start;
+    while (ch && ch !== '\n') {
+      if (inFlow && (ch === '[' || ch === ']' || ch === '{' || ch === '}' || ch === ',')) break;
+      const next = src[offset + 1];
+      if (ch === ':' && (!next || next === '\n' || next === '\t' || next === ' ' || inFlow && next === ',')) break;
+      if ((ch === ' ' || ch === '\t') && next === '#') break;
+      offset += 1;
+      ch = next;
+    }
+    return offset;
+  }
+  get strValue() {
+    if (!this.valueRange || !this.context) return null;
+    let {
+      start,
+      end
+    } = this.valueRange;
+    const {
+      src
+    } = this.context;
+    let ch = src[end - 1];
+    while (start < end && (ch === '\n' || ch === '\t' || ch === ' ')) ch = src[--end - 1];
+    let str = '';
+    for (let i = start; i < end; ++i) {
+      const ch = src[i];
+      if (ch === '\n') {
+        const {
+          fold,
+          offset
+        } = Node.foldNewline(src, i, -1);
+        str += fold;
+        i = offset;
+      } else if (ch === ' ' || ch === '\t') {
+        // trim trailing whitespace
+        const wsStart = i;
+        let next = src[i + 1];
+        while (i < end && (next === ' ' || next === '\t')) {
+          i += 1;
+          next = src[i + 1];
+        }
+        if (next !== '\n') str += i > wsStart ? src.slice(wsStart, i + 1) : ch;
+      } else {
+        str += ch;
+      }
+    }
+    const ch0 = src[start];
+    switch (ch0) {
+      case '\t':
+        {
+          const msg = 'Plain value cannot start with a tab character';
+          const errors = [new YAMLSemanticError(this, msg)];
+          return {
+            errors,
+            str
+          };
+        }
+      case '@':
+      case '`':
+        {
+          const msg = `Plain value cannot start with reserved character ${ch0}`;
+          const errors = [new YAMLSemanticError(this, msg)];
+          return {
+            errors,
+            str
+          };
+        }
+      default:
+        return str;
+    }
+  }
+  parseBlockValue(start) {
+    const {
+      indent,
+      inFlow,
+      src
+    } = this.context;
+    let offset = start;
+    let valueEnd = start;
+    for (let ch = src[offset]; ch === '\n'; ch = src[offset]) {
+      if (Node.atDocumentBoundary(src, offset + 1)) break;
+      const end = Node.endOfBlockIndent(src, indent, offset + 1);
+      if (end === null || src[end] === '#') break;
+      if (src[end] === '\n') {
+        offset = end;
+      } else {
+        valueEnd = PlainValue.endOfLine(src, end, inFlow);
+        offset = valueEnd;
+      }
+    }
+    if (this.valueRange.isEmpty()) this.valueRange.start = start;
+    this.valueRange.end = valueEnd;
+    return valueEnd;
+  }
+
+  /**
+   * Parses a plain value from the source
+   *
+   * Accepted forms are:
+   * ```
+   * #comment
+   *
+   * first line
+   *
+   * first line #comment
+   *
+   * first line
+   * block
+   * lines
+   *
+   * #comment
+   * block
+   * lines
+   * ```
+   * where block lines are empty or have an indent level greater than `indent`.
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this scalar, may be `\n`
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      inFlow,
+      src
+    } = context;
+    let offset = start;
+    const ch = src[offset];
+    if (ch && ch !== '#' && ch !== '\n') {
+      offset = PlainValue.endOfLine(src, start, inFlow);
+    }
+    this.valueRange = new Range(start, offset);
+    offset = Node.endOfWhiteSpace(src, offset);
+    offset = this.parseComment(offset);
+    if (!this.hasComment || this.valueRange.isEmpty()) {
+      offset = this.parseBlockValue(offset);
+    }
+    return offset;
+  }
+}
+
+exports.Char = Char;
+exports.Node = Node;
+exports.PlainValue = PlainValue;
+exports.Range = Range;
+exports.Type = Type;
+exports.YAMLError = YAMLError;
+exports.YAMLReferenceError = YAMLReferenceError;
+exports.YAMLSemanticError = YAMLSemanticError;
+exports.YAMLSyntaxError = YAMLSyntaxError;
+exports.YAMLWarning = YAMLWarning;
+exports._defineProperty = _defineProperty;
+exports.defaultTagPrefix = defaultTagPrefix;
+exports.defaultTags = defaultTags;
Index: frontend/node_modules/yaml/dist/Schema-bcc6c2d7.js
===================================================================
--- frontend/node_modules/yaml/dist/Schema-bcc6c2d7.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/dist/Schema-bcc6c2d7.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,469 @@
+'use strict';
+
+var PlainValue = require('./PlainValue-516d5bc2.js');
+var resolveSeq = require('./resolveSeq-95613e94.js');
+var warnings = require('./warnings-793925ce.js');
+
+function createMap(schema, obj, ctx) {
+  const map = new resolveSeq.YAMLMap(schema);
+  if (obj instanceof Map) {
+    for (const [key, value] of obj) map.items.push(schema.createPair(key, value, ctx));
+  } else if (obj && typeof obj === 'object') {
+    for (const key of Object.keys(obj)) map.items.push(schema.createPair(key, obj[key], ctx));
+  }
+  if (typeof schema.sortMapEntries === 'function') {
+    map.items.sort(schema.sortMapEntries);
+  }
+  return map;
+}
+const map = {
+  createNode: createMap,
+  default: true,
+  nodeClass: resolveSeq.YAMLMap,
+  tag: 'tag:yaml.org,2002:map',
+  resolve: resolveSeq.resolveMap
+};
+
+function createSeq(schema, obj, ctx) {
+  const seq = new resolveSeq.YAMLSeq(schema);
+  if (obj && obj[Symbol.iterator]) {
+    for (const it of obj) {
+      const v = schema.createNode(it, ctx.wrapScalars, null, ctx);
+      seq.items.push(v);
+    }
+  }
+  return seq;
+}
+const seq = {
+  createNode: createSeq,
+  default: true,
+  nodeClass: resolveSeq.YAMLSeq,
+  tag: 'tag:yaml.org,2002:seq',
+  resolve: resolveSeq.resolveSeq
+};
+
+const string = {
+  identify: value => typeof value === 'string',
+  default: true,
+  tag: 'tag:yaml.org,2002:str',
+  resolve: resolveSeq.resolveString,
+  stringify(item, ctx, onComment, onChompKeep) {
+    ctx = Object.assign({
+      actualString: true
+    }, ctx);
+    return resolveSeq.stringifyString(item, ctx, onComment, onChompKeep);
+  },
+  options: resolveSeq.strOptions
+};
+
+const failsafe = [map, seq, string];
+
+/* global BigInt */
+const intIdentify$2 = value => typeof value === 'bigint' || Number.isInteger(value);
+const intResolve$1 = (src, part, radix) => resolveSeq.intOptions.asBigInt ? BigInt(src) : parseInt(part, radix);
+function intStringify$1(node, radix, prefix) {
+  const {
+    value
+  } = node;
+  if (intIdentify$2(value) && value >= 0) return prefix + value.toString(radix);
+  return resolveSeq.stringifyNumber(node);
+}
+const nullObj = {
+  identify: value => value == null,
+  createNode: (schema, value, ctx) => ctx.wrapScalars ? new resolveSeq.Scalar(null) : null,
+  default: true,
+  tag: 'tag:yaml.org,2002:null',
+  test: /^(?:~|[Nn]ull|NULL)?$/,
+  resolve: () => null,
+  options: resolveSeq.nullOptions,
+  stringify: () => resolveSeq.nullOptions.nullStr
+};
+const boolObj = {
+  identify: value => typeof value === 'boolean',
+  default: true,
+  tag: 'tag:yaml.org,2002:bool',
+  test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/,
+  resolve: str => str[0] === 't' || str[0] === 'T',
+  options: resolveSeq.boolOptions,
+  stringify: ({
+    value
+  }) => value ? resolveSeq.boolOptions.trueStr : resolveSeq.boolOptions.falseStr
+};
+const octObj = {
+  identify: value => intIdentify$2(value) && value >= 0,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'OCT',
+  test: /^0o([0-7]+)$/,
+  resolve: (str, oct) => intResolve$1(str, oct, 8),
+  options: resolveSeq.intOptions,
+  stringify: node => intStringify$1(node, 8, '0o')
+};
+const intObj = {
+  identify: intIdentify$2,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  test: /^[-+]?[0-9]+$/,
+  resolve: str => intResolve$1(str, str, 10),
+  options: resolveSeq.intOptions,
+  stringify: resolveSeq.stringifyNumber
+};
+const hexObj = {
+  identify: value => intIdentify$2(value) && value >= 0,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'HEX',
+  test: /^0x([0-9a-fA-F]+)$/,
+  resolve: (str, hex) => intResolve$1(str, hex, 16),
+  options: resolveSeq.intOptions,
+  stringify: node => intStringify$1(node, 16, '0x')
+};
+const nanObj = {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  test: /^(?:[-+]?\.inf|(\.nan))$/i,
+  resolve: (str, nan) => nan ? NaN : str[0] === '-' ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+  stringify: resolveSeq.stringifyNumber
+};
+const expObj = {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  format: 'EXP',
+  test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/,
+  resolve: str => parseFloat(str),
+  stringify: ({
+    value
+  }) => Number(value).toExponential()
+};
+const floatObj = {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  test: /^[-+]?(?:\.([0-9]+)|[0-9]+\.([0-9]*))$/,
+  resolve(str, frac1, frac2) {
+    const frac = frac1 || frac2;
+    const node = new resolveSeq.Scalar(parseFloat(str));
+    if (frac && frac[frac.length - 1] === '0') node.minFractionDigits = frac.length;
+    return node;
+  },
+  stringify: resolveSeq.stringifyNumber
+};
+const core = failsafe.concat([nullObj, boolObj, octObj, intObj, hexObj, nanObj, expObj, floatObj]);
+
+/* global BigInt */
+const intIdentify$1 = value => typeof value === 'bigint' || Number.isInteger(value);
+const stringifyJSON = ({
+  value
+}) => JSON.stringify(value);
+const json = [map, seq, {
+  identify: value => typeof value === 'string',
+  default: true,
+  tag: 'tag:yaml.org,2002:str',
+  resolve: resolveSeq.resolveString,
+  stringify: stringifyJSON
+}, {
+  identify: value => value == null,
+  createNode: (schema, value, ctx) => ctx.wrapScalars ? new resolveSeq.Scalar(null) : null,
+  default: true,
+  tag: 'tag:yaml.org,2002:null',
+  test: /^null$/,
+  resolve: () => null,
+  stringify: stringifyJSON
+}, {
+  identify: value => typeof value === 'boolean',
+  default: true,
+  tag: 'tag:yaml.org,2002:bool',
+  test: /^true|false$/,
+  resolve: str => str === 'true',
+  stringify: stringifyJSON
+}, {
+  identify: intIdentify$1,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  test: /^-?(?:0|[1-9][0-9]*)$/,
+  resolve: str => resolveSeq.intOptions.asBigInt ? BigInt(str) : parseInt(str, 10),
+  stringify: ({
+    value
+  }) => intIdentify$1(value) ? value.toString() : JSON.stringify(value)
+}, {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  test: /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][-+]?[0-9]+)?$/,
+  resolve: str => parseFloat(str),
+  stringify: stringifyJSON
+}];
+json.scalarFallback = str => {
+  throw new SyntaxError(`Unresolved plain scalar ${JSON.stringify(str)}`);
+};
+
+/* global BigInt */
+const boolStringify = ({
+  value
+}) => value ? resolveSeq.boolOptions.trueStr : resolveSeq.boolOptions.falseStr;
+const intIdentify = value => typeof value === 'bigint' || Number.isInteger(value);
+function intResolve(sign, src, radix) {
+  let str = src.replace(/_/g, '');
+  if (resolveSeq.intOptions.asBigInt) {
+    switch (radix) {
+      case 2:
+        str = `0b${str}`;
+        break;
+      case 8:
+        str = `0o${str}`;
+        break;
+      case 16:
+        str = `0x${str}`;
+        break;
+    }
+    const n = BigInt(str);
+    return sign === '-' ? BigInt(-1) * n : n;
+  }
+  const n = parseInt(str, radix);
+  return sign === '-' ? -1 * n : n;
+}
+function intStringify(node, radix, prefix) {
+  const {
+    value
+  } = node;
+  if (intIdentify(value)) {
+    const str = value.toString(radix);
+    return value < 0 ? '-' + prefix + str.substr(1) : prefix + str;
+  }
+  return resolveSeq.stringifyNumber(node);
+}
+const yaml11 = failsafe.concat([{
+  identify: value => value == null,
+  createNode: (schema, value, ctx) => ctx.wrapScalars ? new resolveSeq.Scalar(null) : null,
+  default: true,
+  tag: 'tag:yaml.org,2002:null',
+  test: /^(?:~|[Nn]ull|NULL)?$/,
+  resolve: () => null,
+  options: resolveSeq.nullOptions,
+  stringify: () => resolveSeq.nullOptions.nullStr
+}, {
+  identify: value => typeof value === 'boolean',
+  default: true,
+  tag: 'tag:yaml.org,2002:bool',
+  test: /^(?:Y|y|[Yy]es|YES|[Tt]rue|TRUE|[Oo]n|ON)$/,
+  resolve: () => true,
+  options: resolveSeq.boolOptions,
+  stringify: boolStringify
+}, {
+  identify: value => typeof value === 'boolean',
+  default: true,
+  tag: 'tag:yaml.org,2002:bool',
+  test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/i,
+  resolve: () => false,
+  options: resolveSeq.boolOptions,
+  stringify: boolStringify
+}, {
+  identify: intIdentify,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'BIN',
+  test: /^([-+]?)0b([0-1_]+)$/,
+  resolve: (str, sign, bin) => intResolve(sign, bin, 2),
+  stringify: node => intStringify(node, 2, '0b')
+}, {
+  identify: intIdentify,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'OCT',
+  test: /^([-+]?)0([0-7_]+)$/,
+  resolve: (str, sign, oct) => intResolve(sign, oct, 8),
+  stringify: node => intStringify(node, 8, '0')
+}, {
+  identify: intIdentify,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  test: /^([-+]?)([0-9][0-9_]*)$/,
+  resolve: (str, sign, abs) => intResolve(sign, abs, 10),
+  stringify: resolveSeq.stringifyNumber
+}, {
+  identify: intIdentify,
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'HEX',
+  test: /^([-+]?)0x([0-9a-fA-F_]+)$/,
+  resolve: (str, sign, hex) => intResolve(sign, hex, 16),
+  stringify: node => intStringify(node, 16, '0x')
+}, {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  test: /^(?:[-+]?\.inf|(\.nan))$/i,
+  resolve: (str, nan) => nan ? NaN : str[0] === '-' ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+  stringify: resolveSeq.stringifyNumber
+}, {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  format: 'EXP',
+  test: /^[-+]?([0-9][0-9_]*)?(\.[0-9_]*)?[eE][-+]?[0-9]+$/,
+  resolve: str => parseFloat(str.replace(/_/g, '')),
+  stringify: ({
+    value
+  }) => Number(value).toExponential()
+}, {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  test: /^[-+]?(?:[0-9][0-9_]*)?\.([0-9_]*)$/,
+  resolve(str, frac) {
+    const node = new resolveSeq.Scalar(parseFloat(str.replace(/_/g, '')));
+    if (frac) {
+      const f = frac.replace(/_/g, '');
+      if (f[f.length - 1] === '0') node.minFractionDigits = f.length;
+    }
+    return node;
+  },
+  stringify: resolveSeq.stringifyNumber
+}], warnings.binary, warnings.omap, warnings.pairs, warnings.set, warnings.intTime, warnings.floatTime, warnings.timestamp);
+
+const schemas = {
+  core,
+  failsafe,
+  json,
+  yaml11
+};
+const tags = {
+  binary: warnings.binary,
+  bool: boolObj,
+  float: floatObj,
+  floatExp: expObj,
+  floatNaN: nanObj,
+  floatTime: warnings.floatTime,
+  int: intObj,
+  intHex: hexObj,
+  intOct: octObj,
+  intTime: warnings.intTime,
+  map,
+  null: nullObj,
+  omap: warnings.omap,
+  pairs: warnings.pairs,
+  seq,
+  set: warnings.set,
+  timestamp: warnings.timestamp
+};
+
+function findTagObject(value, tagName, tags) {
+  if (tagName) {
+    const match = tags.filter(t => t.tag === tagName);
+    const tagObj = match.find(t => !t.format) || match[0];
+    if (!tagObj) throw new Error(`Tag ${tagName} not found`);
+    return tagObj;
+  }
+
+  // TODO: deprecate/remove class check
+  return tags.find(t => (t.identify && t.identify(value) || t.class && value instanceof t.class) && !t.format);
+}
+function createNode(value, tagName, ctx) {
+  if (value instanceof resolveSeq.Node) return value;
+  const {
+    defaultPrefix,
+    onTagObj,
+    prevObjects,
+    schema,
+    wrapScalars
+  } = ctx;
+  if (tagName && tagName.startsWith('!!')) tagName = defaultPrefix + tagName.slice(2);
+  let tagObj = findTagObject(value, tagName, schema.tags);
+  if (!tagObj) {
+    if (typeof value.toJSON === 'function') value = value.toJSON();
+    if (!value || typeof value !== 'object') return wrapScalars ? new resolveSeq.Scalar(value) : value;
+    tagObj = value instanceof Map ? map : value[Symbol.iterator] ? seq : map;
+  }
+  if (onTagObj) {
+    onTagObj(tagObj);
+    delete ctx.onTagObj;
+  }
+
+  // Detect duplicate references to the same object & use Alias nodes for all
+  // after first. The `obj` wrapper allows for circular references to resolve.
+  const obj = {
+    value: undefined,
+    node: undefined
+  };
+  if (value && typeof value === 'object' && prevObjects) {
+    const prev = prevObjects.get(value);
+    if (prev) {
+      const alias = new resolveSeq.Alias(prev); // leaves source dirty; must be cleaned by caller
+      ctx.aliasNodes.push(alias); // defined along with prevObjects
+      return alias;
+    }
+    obj.value = value;
+    prevObjects.set(value, obj);
+  }
+  obj.node = tagObj.createNode ? tagObj.createNode(ctx.schema, value, ctx) : wrapScalars ? new resolveSeq.Scalar(value) : value;
+  if (tagName && obj.node instanceof resolveSeq.Node) obj.node.tag = tagName;
+  return obj.node;
+}
+
+function getSchemaTags(schemas, knownTags, customTags, schemaId) {
+  let tags = schemas[schemaId.replace(/\W/g, '')]; // 'yaml-1.1' -> 'yaml11'
+  if (!tags) {
+    const keys = Object.keys(schemas).map(key => JSON.stringify(key)).join(', ');
+    throw new Error(`Unknown schema "${schemaId}"; use one of ${keys}`);
+  }
+  if (Array.isArray(customTags)) {
+    for (const tag of customTags) tags = tags.concat(tag);
+  } else if (typeof customTags === 'function') {
+    tags = customTags(tags.slice());
+  }
+  for (let i = 0; i < tags.length; ++i) {
+    const tag = tags[i];
+    if (typeof tag === 'string') {
+      const tagObj = knownTags[tag];
+      if (!tagObj) {
+        const keys = Object.keys(knownTags).map(key => JSON.stringify(key)).join(', ');
+        throw new Error(`Unknown custom tag "${tag}"; use one of ${keys}`);
+      }
+      tags[i] = tagObj;
+    }
+  }
+  return tags;
+}
+
+const sortMapEntriesByKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
+class Schema {
+  // TODO: remove in v2
+
+  constructor({
+    customTags,
+    merge,
+    schema,
+    sortMapEntries,
+    tags: deprecatedCustomTags
+  }) {
+    this.merge = !!merge;
+    this.name = schema;
+    this.sortMapEntries = sortMapEntries === true ? sortMapEntriesByKey : sortMapEntries || null;
+    if (!customTags && deprecatedCustomTags) warnings.warnOptionDeprecation('tags', 'customTags');
+    this.tags = getSchemaTags(schemas, tags, customTags || deprecatedCustomTags, schema);
+  }
+  createNode(value, wrapScalars, tagName, ctx) {
+    const baseCtx = {
+      defaultPrefix: Schema.defaultPrefix,
+      schema: this,
+      wrapScalars
+    };
+    const createCtx = ctx ? Object.assign(ctx, baseCtx) : baseCtx;
+    return createNode(value, tagName, createCtx);
+  }
+  createPair(key, value, ctx) {
+    if (!ctx) ctx = {
+      wrapScalars: true
+    };
+    const k = this.createNode(key, ctx.wrapScalars, null, ctx);
+    const v = this.createNode(value, ctx.wrapScalars, null, ctx);
+    return new resolveSeq.Pair(k, v);
+  }
+}
+PlainValue._defineProperty(Schema, "defaultPrefix", PlainValue.defaultTagPrefix);
+// TODO: remove in v2
+PlainValue._defineProperty(Schema, "defaultTags", PlainValue.defaultTags);
+
+exports.Schema = Schema;
Index: frontend/node_modules/yaml/dist/index.js
===================================================================
--- frontend/node_modules/yaml/dist/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/dist/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,67 @@
+'use strict';
+
+var parseCst = require('./parse-cst.js');
+var Document$1 = require('./Document-a8d0fbf9.js');
+var Schema = require('./Schema-bcc6c2d7.js');
+var PlainValue = require('./PlainValue-516d5bc2.js');
+var warnings = require('./warnings-793925ce.js');
+require('./resolveSeq-95613e94.js');
+
+function createNode(value, wrapScalars = true, tag) {
+  if (tag === undefined && typeof wrapScalars === 'string') {
+    tag = wrapScalars;
+    wrapScalars = true;
+  }
+  const options = Object.assign({}, Document$1.Document.defaults[Document$1.defaultOptions.version], Document$1.defaultOptions);
+  const schema = new Schema.Schema(options);
+  return schema.createNode(value, wrapScalars, tag);
+}
+class Document extends Document$1.Document {
+  constructor(options) {
+    super(Object.assign({}, Document$1.defaultOptions, options));
+  }
+}
+function parseAllDocuments(src, options) {
+  const stream = [];
+  let prev;
+  for (const cstDoc of parseCst.parse(src)) {
+    const doc = new Document(options);
+    doc.parse(cstDoc, prev);
+    stream.push(doc);
+    prev = doc;
+  }
+  return stream;
+}
+function parseDocument(src, options) {
+  const cst = parseCst.parse(src);
+  const doc = new Document(options).parse(cst[0]);
+  if (cst.length > 1) {
+    const errMsg = 'Source contains multiple documents; please use YAML.parseAllDocuments()';
+    doc.errors.unshift(new PlainValue.YAMLSemanticError(cst[1], errMsg));
+  }
+  return doc;
+}
+function parse(src, options) {
+  const doc = parseDocument(src, options);
+  doc.warnings.forEach(warning => warnings.warn(warning));
+  if (doc.errors.length > 0) throw doc.errors[0];
+  return doc.toJSON();
+}
+function stringify(value, options) {
+  const doc = new Document(options);
+  doc.contents = value;
+  return String(doc);
+}
+const YAML = {
+  createNode,
+  defaultOptions: Document$1.defaultOptions,
+  Document,
+  parse,
+  parseAllDocuments,
+  parseCST: parseCst.parse,
+  parseDocument,
+  scalarOptions: Document$1.scalarOptions,
+  stringify
+};
+
+exports.YAML = YAML;
Index: frontend/node_modules/yaml/dist/legacy-exports.js
===================================================================
--- frontend/node_modules/yaml/dist/legacy-exports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/dist/legacy-exports.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,16 @@
+'use strict';
+
+var warnings = require('./warnings-793925ce.js');
+require('./PlainValue-516d5bc2.js');
+require('./resolveSeq-95613e94.js');
+
+
+
+exports.binary = warnings.binary;
+exports.floatTime = warnings.floatTime;
+exports.intTime = warnings.intTime;
+exports.omap = warnings.omap;
+exports.pairs = warnings.pairs;
+exports.set = warnings.set;
+exports.timestamp = warnings.timestamp;
+exports.warnFileDeprecation = warnings.warnFileDeprecation;
Index: frontend/node_modules/yaml/dist/parse-cst.js
===================================================================
--- frontend/node_modules/yaml/dist/parse-cst.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/dist/parse-cst.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1507 @@
+'use strict';
+
+var PlainValue = require('./PlainValue-516d5bc2.js');
+
+class BlankLine extends PlainValue.Node {
+  constructor() {
+    super(PlainValue.Type.BLANK_LINE);
+  }
+
+  /* istanbul ignore next */
+  get includesTrailingLines() {
+    // This is never called from anywhere, but if it were,
+    // this is the value it should return.
+    return true;
+  }
+
+  /**
+   * Parses a blank line from the source
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first \n character
+   * @returns {number} - Index of the character after this
+   */
+  parse(context, start) {
+    this.context = context;
+    this.range = new PlainValue.Range(start, start + 1);
+    return start + 1;
+  }
+}
+
+class CollectionItem extends PlainValue.Node {
+  constructor(type, props) {
+    super(type, props);
+    this.node = null;
+  }
+  get includesTrailingLines() {
+    return !!this.node && this.node.includesTrailingLines;
+  }
+
+  /**
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      parseNode,
+      src
+    } = context;
+    let {
+      atLineStart,
+      lineStart
+    } = context;
+    if (!atLineStart && this.type === PlainValue.Type.SEQ_ITEM) this.error = new PlainValue.YAMLSemanticError(this, 'Sequence items must not have preceding content on the same line');
+    const indent = atLineStart ? start - lineStart : context.indent;
+    let offset = PlainValue.Node.endOfWhiteSpace(src, start + 1);
+    let ch = src[offset];
+    const inlineComment = ch === '#';
+    const comments = [];
+    let blankLine = null;
+    while (ch === '\n' || ch === '#') {
+      if (ch === '#') {
+        const end = PlainValue.Node.endOfLine(src, offset + 1);
+        comments.push(new PlainValue.Range(offset, end));
+        offset = end;
+      } else {
+        atLineStart = true;
+        lineStart = offset + 1;
+        const wsEnd = PlainValue.Node.endOfWhiteSpace(src, lineStart);
+        if (src[wsEnd] === '\n' && comments.length === 0) {
+          blankLine = new BlankLine();
+          lineStart = blankLine.parse({
+            src
+          }, lineStart);
+        }
+        offset = PlainValue.Node.endOfIndent(src, lineStart);
+      }
+      ch = src[offset];
+    }
+    if (PlainValue.Node.nextNodeIsIndented(ch, offset - (lineStart + indent), this.type !== PlainValue.Type.SEQ_ITEM)) {
+      this.node = parseNode({
+        atLineStart,
+        inCollection: false,
+        indent,
+        lineStart,
+        parent: this
+      }, offset);
+    } else if (ch && lineStart > start + 1) {
+      offset = lineStart - 1;
+    }
+    if (this.node) {
+      if (blankLine) {
+        // Only blank lines preceding non-empty nodes are captured. Note that
+        // this means that collection item range start indices do not always
+        // increase monotonically. -- eemeli/yaml#126
+        const items = context.parent.items || context.parent.contents;
+        if (items) items.push(blankLine);
+      }
+      if (comments.length) Array.prototype.push.apply(this.props, comments);
+      offset = this.node.range.end;
+    } else {
+      if (inlineComment) {
+        const c = comments[0];
+        this.props.push(c);
+        offset = c.end;
+      } else {
+        offset = PlainValue.Node.endOfLine(src, start + 1);
+      }
+    }
+    const end = this.node ? this.node.valueRange.end : offset;
+    this.valueRange = new PlainValue.Range(start, end);
+    return offset;
+  }
+  setOrigRanges(cr, offset) {
+    offset = super.setOrigRanges(cr, offset);
+    return this.node ? this.node.setOrigRanges(cr, offset) : offset;
+  }
+  toString() {
+    const {
+      context: {
+        src
+      },
+      node,
+      range,
+      value
+    } = this;
+    if (value != null) return value;
+    const str = node ? src.slice(range.start, node.range.start) + String(node) : src.slice(range.start, range.end);
+    return PlainValue.Node.addStringTerminator(src, range.end, str);
+  }
+}
+
+class Comment extends PlainValue.Node {
+  constructor() {
+    super(PlainValue.Type.COMMENT);
+  }
+
+  /**
+   * Parses a comment line from the source
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this scalar
+   */
+  parse(context, start) {
+    this.context = context;
+    const offset = this.parseComment(start);
+    this.range = new PlainValue.Range(start, offset);
+    return offset;
+  }
+}
+
+function grabCollectionEndComments(node) {
+  let cnode = node;
+  while (cnode instanceof CollectionItem) cnode = cnode.node;
+  if (!(cnode instanceof Collection)) return null;
+  const len = cnode.items.length;
+  let ci = -1;
+  for (let i = len - 1; i >= 0; --i) {
+    const n = cnode.items[i];
+    if (n.type === PlainValue.Type.COMMENT) {
+      // Keep sufficiently indented comments with preceding node
+      const {
+        indent,
+        lineStart
+      } = n.context;
+      if (indent > 0 && n.range.start >= lineStart + indent) break;
+      ci = i;
+    } else if (n.type === PlainValue.Type.BLANK_LINE) ci = i;else break;
+  }
+  if (ci === -1) return null;
+  const ca = cnode.items.splice(ci, len - ci);
+  const prevEnd = ca[0].range.start;
+  while (true) {
+    cnode.range.end = prevEnd;
+    if (cnode.valueRange && cnode.valueRange.end > prevEnd) cnode.valueRange.end = prevEnd;
+    if (cnode === node) break;
+    cnode = cnode.context.parent;
+  }
+  return ca;
+}
+class Collection extends PlainValue.Node {
+  static nextContentHasIndent(src, offset, indent) {
+    const lineStart = PlainValue.Node.endOfLine(src, offset) + 1;
+    offset = PlainValue.Node.endOfWhiteSpace(src, lineStart);
+    const ch = src[offset];
+    if (!ch) return false;
+    if (offset >= lineStart + indent) return true;
+    if (ch !== '#' && ch !== '\n') return false;
+    return Collection.nextContentHasIndent(src, offset, indent);
+  }
+  constructor(firstItem) {
+    super(firstItem.type === PlainValue.Type.SEQ_ITEM ? PlainValue.Type.SEQ : PlainValue.Type.MAP);
+    for (let i = firstItem.props.length - 1; i >= 0; --i) {
+      if (firstItem.props[i].start < firstItem.context.lineStart) {
+        // props on previous line are assumed by the collection
+        this.props = firstItem.props.slice(0, i + 1);
+        firstItem.props = firstItem.props.slice(i + 1);
+        const itemRange = firstItem.props[0] || firstItem.valueRange;
+        firstItem.range.start = itemRange.start;
+        break;
+      }
+    }
+    this.items = [firstItem];
+    const ec = grabCollectionEndComments(firstItem);
+    if (ec) Array.prototype.push.apply(this.items, ec);
+  }
+  get includesTrailingLines() {
+    return this.items.length > 0;
+  }
+
+  /**
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      parseNode,
+      src
+    } = context;
+    // It's easier to recalculate lineStart here rather than tracking down the
+    // last context from which to read it -- eemeli/yaml#2
+    let lineStart = PlainValue.Node.startOfLine(src, start);
+    const firstItem = this.items[0];
+    // First-item context needs to be correct for later comment handling
+    // -- eemeli/yaml#17
+    firstItem.context.parent = this;
+    this.valueRange = PlainValue.Range.copy(firstItem.valueRange);
+    const indent = firstItem.range.start - firstItem.context.lineStart;
+    let offset = start;
+    offset = PlainValue.Node.normalizeOffset(src, offset);
+    let ch = src[offset];
+    let atLineStart = PlainValue.Node.endOfWhiteSpace(src, lineStart) === offset;
+    let prevIncludesTrailingLines = false;
+    while (ch) {
+      while (ch === '\n' || ch === '#') {
+        if (atLineStart && ch === '\n' && !prevIncludesTrailingLines) {
+          const blankLine = new BlankLine();
+          offset = blankLine.parse({
+            src
+          }, offset);
+          this.valueRange.end = offset;
+          if (offset >= src.length) {
+            ch = null;
+            break;
+          }
+          this.items.push(blankLine);
+          offset -= 1; // blankLine.parse() consumes terminal newline
+        } else if (ch === '#') {
+          if (offset < lineStart + indent && !Collection.nextContentHasIndent(src, offset, indent)) {
+            return offset;
+          }
+          const comment = new Comment();
+          offset = comment.parse({
+            indent,
+            lineStart,
+            src
+          }, offset);
+          this.items.push(comment);
+          this.valueRange.end = offset;
+          if (offset >= src.length) {
+            ch = null;
+            break;
+          }
+        }
+        lineStart = offset + 1;
+        offset = PlainValue.Node.endOfIndent(src, lineStart);
+        if (PlainValue.Node.atBlank(src, offset)) {
+          const wsEnd = PlainValue.Node.endOfWhiteSpace(src, offset);
+          const next = src[wsEnd];
+          if (!next || next === '\n' || next === '#') {
+            offset = wsEnd;
+          }
+        }
+        ch = src[offset];
+        atLineStart = true;
+      }
+      if (!ch) {
+        break;
+      }
+      if (offset !== lineStart + indent && (atLineStart || ch !== ':')) {
+        if (offset < lineStart + indent) {
+          if (lineStart > start) offset = lineStart;
+          break;
+        } else if (!this.error) {
+          const msg = 'All collection items must start at the same column';
+          this.error = new PlainValue.YAMLSyntaxError(this, msg);
+        }
+      }
+      if (firstItem.type === PlainValue.Type.SEQ_ITEM) {
+        if (ch !== '-') {
+          if (lineStart > start) offset = lineStart;
+          break;
+        }
+      } else if (ch === '-' && !this.error) {
+        // map key may start with -, as long as it's followed by a non-whitespace char
+        const next = src[offset + 1];
+        if (!next || next === '\n' || next === '\t' || next === ' ') {
+          const msg = 'A collection cannot be both a mapping and a sequence';
+          this.error = new PlainValue.YAMLSyntaxError(this, msg);
+        }
+      }
+      const node = parseNode({
+        atLineStart,
+        inCollection: true,
+        indent,
+        lineStart,
+        parent: this
+      }, offset);
+      if (!node) return offset; // at next document start
+      this.items.push(node);
+      this.valueRange.end = node.valueRange.end;
+      offset = PlainValue.Node.normalizeOffset(src, node.range.end);
+      ch = src[offset];
+      atLineStart = false;
+      prevIncludesTrailingLines = node.includesTrailingLines;
+      // Need to reset lineStart and atLineStart here if preceding node's range
+      // has advanced to check the current line's indentation level
+      // -- eemeli/yaml#10 & eemeli/yaml#38
+      if (ch) {
+        let ls = offset - 1;
+        let prev = src[ls];
+        while (prev === ' ' || prev === '\t') prev = src[--ls];
+        if (prev === '\n') {
+          lineStart = ls + 1;
+          atLineStart = true;
+        }
+      }
+      const ec = grabCollectionEndComments(node);
+      if (ec) Array.prototype.push.apply(this.items, ec);
+    }
+    return offset;
+  }
+  setOrigRanges(cr, offset) {
+    offset = super.setOrigRanges(cr, offset);
+    this.items.forEach(node => {
+      offset = node.setOrigRanges(cr, offset);
+    });
+    return offset;
+  }
+  toString() {
+    const {
+      context: {
+        src
+      },
+      items,
+      range,
+      value
+    } = this;
+    if (value != null) return value;
+    let str = src.slice(range.start, items[0].range.start) + String(items[0]);
+    for (let i = 1; i < items.length; ++i) {
+      const item = items[i];
+      const {
+        atLineStart,
+        indent
+      } = item.context;
+      if (atLineStart) for (let i = 0; i < indent; ++i) str += ' ';
+      str += String(item);
+    }
+    return PlainValue.Node.addStringTerminator(src, range.end, str);
+  }
+}
+
+class Directive extends PlainValue.Node {
+  constructor() {
+    super(PlainValue.Type.DIRECTIVE);
+    this.name = null;
+  }
+  get parameters() {
+    const raw = this.rawValue;
+    return raw ? raw.trim().split(/[ \t]+/) : [];
+  }
+  parseName(start) {
+    const {
+      src
+    } = this.context;
+    let offset = start;
+    let ch = src[offset];
+    while (ch && ch !== '\n' && ch !== '\t' && ch !== ' ') ch = src[offset += 1];
+    this.name = src.slice(start, offset);
+    return offset;
+  }
+  parseParameters(start) {
+    const {
+      src
+    } = this.context;
+    let offset = start;
+    let ch = src[offset];
+    while (ch && ch !== '\n' && ch !== '#') ch = src[offset += 1];
+    this.valueRange = new PlainValue.Range(start, offset);
+    return offset;
+  }
+  parse(context, start) {
+    this.context = context;
+    let offset = this.parseName(start + 1);
+    offset = this.parseParameters(offset);
+    offset = this.parseComment(offset);
+    this.range = new PlainValue.Range(start, offset);
+    return offset;
+  }
+}
+
+class Document extends PlainValue.Node {
+  static startCommentOrEndBlankLine(src, start) {
+    const offset = PlainValue.Node.endOfWhiteSpace(src, start);
+    const ch = src[offset];
+    return ch === '#' || ch === '\n' ? offset : start;
+  }
+  constructor() {
+    super(PlainValue.Type.DOCUMENT);
+    this.directives = null;
+    this.contents = null;
+    this.directivesEndMarker = null;
+    this.documentEndMarker = null;
+  }
+  parseDirectives(start) {
+    const {
+      src
+    } = this.context;
+    this.directives = [];
+    let atLineStart = true;
+    let hasDirectives = false;
+    let offset = start;
+    while (!PlainValue.Node.atDocumentBoundary(src, offset, PlainValue.Char.DIRECTIVES_END)) {
+      offset = Document.startCommentOrEndBlankLine(src, offset);
+      switch (src[offset]) {
+        case '\n':
+          if (atLineStart) {
+            const blankLine = new BlankLine();
+            offset = blankLine.parse({
+              src
+            }, offset);
+            if (offset < src.length) {
+              this.directives.push(blankLine);
+            }
+          } else {
+            offset += 1;
+            atLineStart = true;
+          }
+          break;
+        case '#':
+          {
+            const comment = new Comment();
+            offset = comment.parse({
+              src
+            }, offset);
+            this.directives.push(comment);
+            atLineStart = false;
+          }
+          break;
+        case '%':
+          {
+            const directive = new Directive();
+            offset = directive.parse({
+              parent: this,
+              src
+            }, offset);
+            this.directives.push(directive);
+            hasDirectives = true;
+            atLineStart = false;
+          }
+          break;
+        default:
+          if (hasDirectives) {
+            this.error = new PlainValue.YAMLSemanticError(this, 'Missing directives-end indicator line');
+          } else if (this.directives.length > 0) {
+            this.contents = this.directives;
+            this.directives = [];
+          }
+          return offset;
+      }
+    }
+    if (src[offset]) {
+      this.directivesEndMarker = new PlainValue.Range(offset, offset + 3);
+      return offset + 3;
+    }
+    if (hasDirectives) {
+      this.error = new PlainValue.YAMLSemanticError(this, 'Missing directives-end indicator line');
+    } else if (this.directives.length > 0) {
+      this.contents = this.directives;
+      this.directives = [];
+    }
+    return offset;
+  }
+  parseContents(start) {
+    const {
+      parseNode,
+      src
+    } = this.context;
+    if (!this.contents) this.contents = [];
+    let lineStart = start;
+    while (src[lineStart - 1] === '-') lineStart -= 1;
+    let offset = PlainValue.Node.endOfWhiteSpace(src, start);
+    let atLineStart = lineStart === start;
+    this.valueRange = new PlainValue.Range(offset);
+    while (!PlainValue.Node.atDocumentBoundary(src, offset, PlainValue.Char.DOCUMENT_END)) {
+      switch (src[offset]) {
+        case '\n':
+          if (atLineStart) {
+            const blankLine = new BlankLine();
+            offset = blankLine.parse({
+              src
+            }, offset);
+            if (offset < src.length) {
+              this.contents.push(blankLine);
+            }
+          } else {
+            offset += 1;
+            atLineStart = true;
+          }
+          lineStart = offset;
+          break;
+        case '#':
+          {
+            const comment = new Comment();
+            offset = comment.parse({
+              src
+            }, offset);
+            this.contents.push(comment);
+            atLineStart = false;
+          }
+          break;
+        default:
+          {
+            const iEnd = PlainValue.Node.endOfIndent(src, offset);
+            const context = {
+              atLineStart,
+              indent: -1,
+              inFlow: false,
+              inCollection: false,
+              lineStart,
+              parent: this
+            };
+            const node = parseNode(context, iEnd);
+            if (!node) return this.valueRange.end = iEnd; // at next document start
+            this.contents.push(node);
+            offset = node.range.end;
+            atLineStart = false;
+            const ec = grabCollectionEndComments(node);
+            if (ec) Array.prototype.push.apply(this.contents, ec);
+          }
+      }
+      offset = Document.startCommentOrEndBlankLine(src, offset);
+    }
+    this.valueRange.end = offset;
+    if (src[offset]) {
+      this.documentEndMarker = new PlainValue.Range(offset, offset + 3);
+      offset += 3;
+      if (src[offset]) {
+        offset = PlainValue.Node.endOfWhiteSpace(src, offset);
+        if (src[offset] === '#') {
+          const comment = new Comment();
+          offset = comment.parse({
+            src
+          }, offset);
+          this.contents.push(comment);
+        }
+        switch (src[offset]) {
+          case '\n':
+            offset += 1;
+            break;
+          case undefined:
+            break;
+          default:
+            this.error = new PlainValue.YAMLSyntaxError(this, 'Document end marker line cannot have a non-comment suffix');
+        }
+      }
+    }
+    return offset;
+  }
+
+  /**
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this
+   */
+  parse(context, start) {
+    context.root = this;
+    this.context = context;
+    const {
+      src
+    } = context;
+    let offset = src.charCodeAt(start) === 0xfeff ? start + 1 : start; // skip BOM
+    offset = this.parseDirectives(offset);
+    offset = this.parseContents(offset);
+    return offset;
+  }
+  setOrigRanges(cr, offset) {
+    offset = super.setOrigRanges(cr, offset);
+    this.directives.forEach(node => {
+      offset = node.setOrigRanges(cr, offset);
+    });
+    if (this.directivesEndMarker) offset = this.directivesEndMarker.setOrigRange(cr, offset);
+    this.contents.forEach(node => {
+      offset = node.setOrigRanges(cr, offset);
+    });
+    if (this.documentEndMarker) offset = this.documentEndMarker.setOrigRange(cr, offset);
+    return offset;
+  }
+  toString() {
+    const {
+      contents,
+      directives,
+      value
+    } = this;
+    if (value != null) return value;
+    let str = directives.join('');
+    if (contents.length > 0) {
+      if (directives.length > 0 || contents[0].type === PlainValue.Type.COMMENT) str += '---\n';
+      str += contents.join('');
+    }
+    if (str[str.length - 1] !== '\n') str += '\n';
+    return str;
+  }
+}
+
+class Alias extends PlainValue.Node {
+  /**
+   * Parses an *alias from the source
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this scalar
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      src
+    } = context;
+    let offset = PlainValue.Node.endOfIdentifier(src, start + 1);
+    this.valueRange = new PlainValue.Range(start + 1, offset);
+    offset = PlainValue.Node.endOfWhiteSpace(src, offset);
+    offset = this.parseComment(offset);
+    return offset;
+  }
+}
+
+const Chomp = {
+  CLIP: 'CLIP',
+  KEEP: 'KEEP',
+  STRIP: 'STRIP'
+};
+class BlockValue extends PlainValue.Node {
+  constructor(type, props) {
+    super(type, props);
+    this.blockIndent = null;
+    this.chomping = Chomp.CLIP;
+    this.header = null;
+  }
+  get includesTrailingLines() {
+    return this.chomping === Chomp.KEEP;
+  }
+  get strValue() {
+    if (!this.valueRange || !this.context) return null;
+    let {
+      start,
+      end
+    } = this.valueRange;
+    const {
+      indent,
+      src
+    } = this.context;
+    if (this.valueRange.isEmpty()) return '';
+    let lastNewLine = null;
+    let ch = src[end - 1];
+    while (ch === '\n' || ch === '\t' || ch === ' ') {
+      end -= 1;
+      if (end <= start) {
+        if (this.chomping === Chomp.KEEP) break;else return ''; // probably never happens
+      }
+      if (ch === '\n') lastNewLine = end;
+      ch = src[end - 1];
+    }
+    let keepStart = end + 1;
+    if (lastNewLine) {
+      if (this.chomping === Chomp.KEEP) {
+        keepStart = lastNewLine;
+        end = this.valueRange.end;
+      } else {
+        end = lastNewLine;
+      }
+    }
+    const bi = indent + this.blockIndent;
+    const folded = this.type === PlainValue.Type.BLOCK_FOLDED;
+    let atStart = true;
+    let str = '';
+    let sep = '';
+    let prevMoreIndented = false;
+    for (let i = start; i < end; ++i) {
+      for (let j = 0; j < bi; ++j) {
+        if (src[i] !== ' ') break;
+        i += 1;
+      }
+      const ch = src[i];
+      if (ch === '\n') {
+        if (sep === '\n') str += '\n';else sep = '\n';
+      } else {
+        const lineEnd = PlainValue.Node.endOfLine(src, i);
+        const line = src.slice(i, lineEnd);
+        i = lineEnd;
+        if (folded && (ch === ' ' || ch === '\t') && i < keepStart) {
+          if (sep === ' ') sep = '\n';else if (!prevMoreIndented && !atStart && sep === '\n') sep = '\n\n';
+          str += sep + line; //+ ((lineEnd < end && src[lineEnd]) || '')
+          sep = lineEnd < end && src[lineEnd] || '';
+          prevMoreIndented = true;
+        } else {
+          str += sep + line;
+          sep = folded && i < keepStart ? ' ' : '\n';
+          prevMoreIndented = false;
+        }
+        if (atStart && line !== '') atStart = false;
+      }
+    }
+    return this.chomping === Chomp.STRIP ? str : str + '\n';
+  }
+  parseBlockHeader(start) {
+    const {
+      src
+    } = this.context;
+    let offset = start + 1;
+    let bi = '';
+    while (true) {
+      const ch = src[offset];
+      switch (ch) {
+        case '-':
+          this.chomping = Chomp.STRIP;
+          break;
+        case '+':
+          this.chomping = Chomp.KEEP;
+          break;
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+          bi += ch;
+          break;
+        default:
+          this.blockIndent = Number(bi) || null;
+          this.header = new PlainValue.Range(start, offset);
+          return offset;
+      }
+      offset += 1;
+    }
+  }
+  parseBlockValue(start) {
+    const {
+      indent,
+      src
+    } = this.context;
+    const explicit = !!this.blockIndent;
+    let offset = start;
+    let valueEnd = start;
+    let minBlockIndent = 1;
+    for (let ch = src[offset]; ch === '\n'; ch = src[offset]) {
+      offset += 1;
+      if (PlainValue.Node.atDocumentBoundary(src, offset)) break;
+      const end = PlainValue.Node.endOfBlockIndent(src, indent, offset); // should not include tab?
+      if (end === null) break;
+      const ch = src[end];
+      const lineIndent = end - (offset + indent);
+      if (!this.blockIndent) {
+        // no explicit block indent, none yet detected
+        if (src[end] !== '\n') {
+          // first line with non-whitespace content
+          if (lineIndent < minBlockIndent) {
+            const msg = 'Block scalars with more-indented leading empty lines must use an explicit indentation indicator';
+            this.error = new PlainValue.YAMLSemanticError(this, msg);
+          }
+          this.blockIndent = lineIndent;
+        } else if (lineIndent > minBlockIndent) {
+          // empty line with more whitespace
+          minBlockIndent = lineIndent;
+        }
+      } else if (ch && ch !== '\n' && lineIndent < this.blockIndent) {
+        if (src[end] === '#') break;
+        if (!this.error) {
+          const src = explicit ? 'explicit indentation indicator' : 'first line';
+          const msg = `Block scalars must not be less indented than their ${src}`;
+          this.error = new PlainValue.YAMLSemanticError(this, msg);
+        }
+      }
+      if (src[end] === '\n') {
+        offset = end;
+      } else {
+        offset = valueEnd = PlainValue.Node.endOfLine(src, end);
+      }
+    }
+    if (this.chomping !== Chomp.KEEP) {
+      offset = src[valueEnd] ? valueEnd + 1 : valueEnd;
+    }
+    this.valueRange = new PlainValue.Range(start + 1, offset);
+    return offset;
+  }
+
+  /**
+   * Parses a block value from the source
+   *
+   * Accepted forms are:
+   * ```
+   * BS
+   * block
+   * lines
+   *
+   * BS #comment
+   * block
+   * lines
+   * ```
+   * where the block style BS matches the regexp `[|>][-+1-9]*` and block lines
+   * are empty or have an indent level greater than `indent`.
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this block
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      src
+    } = context;
+    let offset = this.parseBlockHeader(start);
+    offset = PlainValue.Node.endOfWhiteSpace(src, offset);
+    offset = this.parseComment(offset);
+    offset = this.parseBlockValue(offset);
+    return offset;
+  }
+  setOrigRanges(cr, offset) {
+    offset = super.setOrigRanges(cr, offset);
+    return this.header ? this.header.setOrigRange(cr, offset) : offset;
+  }
+}
+
+class FlowCollection extends PlainValue.Node {
+  constructor(type, props) {
+    super(type, props);
+    this.items = null;
+  }
+  prevNodeIsJsonLike(idx = this.items.length) {
+    const node = this.items[idx - 1];
+    return !!node && (node.jsonLike || node.type === PlainValue.Type.COMMENT && this.prevNodeIsJsonLike(idx - 1));
+  }
+
+  /**
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      parseNode,
+      src
+    } = context;
+    let {
+      indent,
+      lineStart
+    } = context;
+    let char = src[start]; // { or [
+    this.items = [{
+      char,
+      offset: start
+    }];
+    let offset = PlainValue.Node.endOfWhiteSpace(src, start + 1);
+    char = src[offset];
+    while (char && char !== ']' && char !== '}') {
+      switch (char) {
+        case '\n':
+          {
+            lineStart = offset + 1;
+            const wsEnd = PlainValue.Node.endOfWhiteSpace(src, lineStart);
+            if (src[wsEnd] === '\n') {
+              const blankLine = new BlankLine();
+              lineStart = blankLine.parse({
+                src
+              }, lineStart);
+              this.items.push(blankLine);
+            }
+            offset = PlainValue.Node.endOfIndent(src, lineStart);
+            if (offset <= lineStart + indent) {
+              char = src[offset];
+              if (offset < lineStart + indent || char !== ']' && char !== '}') {
+                const msg = 'Insufficient indentation in flow collection';
+                this.error = new PlainValue.YAMLSemanticError(this, msg);
+              }
+            }
+          }
+          break;
+        case ',':
+          {
+            this.items.push({
+              char,
+              offset
+            });
+            offset += 1;
+          }
+          break;
+        case '#':
+          {
+            const comment = new Comment();
+            offset = comment.parse({
+              src
+            }, offset);
+            this.items.push(comment);
+          }
+          break;
+        case '?':
+        case ':':
+          {
+            const next = src[offset + 1];
+            if (next === '\n' || next === '\t' || next === ' ' || next === ',' ||
+            // in-flow : after JSON-like key does not need to be followed by whitespace
+            char === ':' && this.prevNodeIsJsonLike()) {
+              this.items.push({
+                char,
+                offset
+              });
+              offset += 1;
+              break;
+            }
+          }
+        // fallthrough
+        default:
+          {
+            const node = parseNode({
+              atLineStart: false,
+              inCollection: false,
+              inFlow: true,
+              indent: -1,
+              lineStart,
+              parent: this
+            }, offset);
+            if (!node) {
+              // at next document start
+              this.valueRange = new PlainValue.Range(start, offset);
+              return offset;
+            }
+            this.items.push(node);
+            offset = PlainValue.Node.normalizeOffset(src, node.range.end);
+          }
+      }
+      offset = PlainValue.Node.endOfWhiteSpace(src, offset);
+      char = src[offset];
+    }
+    this.valueRange = new PlainValue.Range(start, offset + 1);
+    if (char) {
+      this.items.push({
+        char,
+        offset
+      });
+      offset = PlainValue.Node.endOfWhiteSpace(src, offset + 1);
+      offset = this.parseComment(offset);
+    }
+    return offset;
+  }
+  setOrigRanges(cr, offset) {
+    offset = super.setOrigRanges(cr, offset);
+    this.items.forEach(node => {
+      if (node instanceof PlainValue.Node) {
+        offset = node.setOrigRanges(cr, offset);
+      } else if (cr.length === 0) {
+        node.origOffset = node.offset;
+      } else {
+        let i = offset;
+        while (i < cr.length) {
+          if (cr[i] > node.offset) break;else ++i;
+        }
+        node.origOffset = node.offset + i;
+        offset = i;
+      }
+    });
+    return offset;
+  }
+  toString() {
+    const {
+      context: {
+        src
+      },
+      items,
+      range,
+      value
+    } = this;
+    if (value != null) return value;
+    const nodes = items.filter(item => item instanceof PlainValue.Node);
+    let str = '';
+    let prevEnd = range.start;
+    nodes.forEach(node => {
+      const prefix = src.slice(prevEnd, node.range.start);
+      prevEnd = node.range.end;
+      str += prefix + String(node);
+      if (str[str.length - 1] === '\n' && src[prevEnd - 1] !== '\n' && src[prevEnd] === '\n') {
+        // Comment range does not include the terminal newline, but its
+        // stringified value does. Without this fix, newlines at comment ends
+        // get duplicated.
+        prevEnd += 1;
+      }
+    });
+    str += src.slice(prevEnd, range.end);
+    return PlainValue.Node.addStringTerminator(src, range.end, str);
+  }
+}
+
+class QuoteDouble extends PlainValue.Node {
+  static endOfQuote(src, offset) {
+    let ch = src[offset];
+    while (ch && ch !== '"') {
+      offset += ch === '\\' ? 2 : 1;
+      ch = src[offset];
+    }
+    return offset + 1;
+  }
+
+  /**
+   * @returns {string | { str: string, errors: YAMLSyntaxError[] }}
+   */
+  get strValue() {
+    if (!this.valueRange || !this.context) return null;
+    const errors = [];
+    const {
+      start,
+      end
+    } = this.valueRange;
+    const {
+      indent,
+      src
+    } = this.context;
+    if (src[end - 1] !== '"') errors.push(new PlainValue.YAMLSyntaxError(this, 'Missing closing "quote'));
+    // Using String#replace is too painful with escaped newlines preceded by
+    // escaped backslashes; also, this should be faster.
+    let str = '';
+    for (let i = start + 1; i < end - 1; ++i) {
+      const ch = src[i];
+      if (ch === '\n') {
+        if (PlainValue.Node.atDocumentBoundary(src, i + 1)) errors.push(new PlainValue.YAMLSemanticError(this, 'Document boundary indicators are not allowed within string values'));
+        const {
+          fold,
+          offset,
+          error
+        } = PlainValue.Node.foldNewline(src, i, indent);
+        str += fold;
+        i = offset;
+        if (error) errors.push(new PlainValue.YAMLSemanticError(this, 'Multi-line double-quoted string needs to be sufficiently indented'));
+      } else if (ch === '\\') {
+        i += 1;
+        switch (src[i]) {
+          case '0':
+            str += '\0';
+            break;
+          // null character
+          case 'a':
+            str += '\x07';
+            break;
+          // bell character
+          case 'b':
+            str += '\b';
+            break;
+          // backspace
+          case 'e':
+            str += '\x1b';
+            break;
+          // escape character
+          case 'f':
+            str += '\f';
+            break;
+          // form feed
+          case 'n':
+            str += '\n';
+            break;
+          // line feed
+          case 'r':
+            str += '\r';
+            break;
+          // carriage return
+          case 't':
+            str += '\t';
+            break;
+          // horizontal tab
+          case 'v':
+            str += '\v';
+            break;
+          // vertical tab
+          case 'N':
+            str += '\u0085';
+            break;
+          // Unicode next line
+          case '_':
+            str += '\u00a0';
+            break;
+          // Unicode non-breaking space
+          case 'L':
+            str += '\u2028';
+            break;
+          // Unicode line separator
+          case 'P':
+            str += '\u2029';
+            break;
+          // Unicode paragraph separator
+          case ' ':
+            str += ' ';
+            break;
+          case '"':
+            str += '"';
+            break;
+          case '/':
+            str += '/';
+            break;
+          case '\\':
+            str += '\\';
+            break;
+          case '\t':
+            str += '\t';
+            break;
+          case 'x':
+            str += this.parseCharCode(i + 1, 2, errors);
+            i += 2;
+            break;
+          case 'u':
+            str += this.parseCharCode(i + 1, 4, errors);
+            i += 4;
+            break;
+          case 'U':
+            str += this.parseCharCode(i + 1, 8, errors);
+            i += 8;
+            break;
+          case '\n':
+            // skip escaped newlines, but still trim the following line
+            while (src[i + 1] === ' ' || src[i + 1] === '\t') i += 1;
+            break;
+          default:
+            errors.push(new PlainValue.YAMLSyntaxError(this, `Invalid escape sequence ${src.substr(i - 1, 2)}`));
+            str += '\\' + src[i];
+        }
+      } else if (ch === ' ' || ch === '\t') {
+        // trim trailing whitespace
+        const wsStart = i;
+        let next = src[i + 1];
+        while (next === ' ' || next === '\t') {
+          i += 1;
+          next = src[i + 1];
+        }
+        if (next !== '\n') str += i > wsStart ? src.slice(wsStart, i + 1) : ch;
+      } else {
+        str += ch;
+      }
+    }
+    return errors.length > 0 ? {
+      errors,
+      str
+    } : str;
+  }
+  parseCharCode(offset, length, errors) {
+    const {
+      src
+    } = this.context;
+    const cc = src.substr(offset, length);
+    const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
+    const code = ok ? parseInt(cc, 16) : NaN;
+    if (isNaN(code)) {
+      errors.push(new PlainValue.YAMLSyntaxError(this, `Invalid escape sequence ${src.substr(offset - 2, length + 2)}`));
+      return src.substr(offset - 2, length + 2);
+    }
+    return String.fromCodePoint(code);
+  }
+
+  /**
+   * Parses a "double quoted" value from the source
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this scalar
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      src
+    } = context;
+    let offset = QuoteDouble.endOfQuote(src, start + 1);
+    this.valueRange = new PlainValue.Range(start, offset);
+    offset = PlainValue.Node.endOfWhiteSpace(src, offset);
+    offset = this.parseComment(offset);
+    return offset;
+  }
+}
+
+class QuoteSingle extends PlainValue.Node {
+  static endOfQuote(src, offset) {
+    let ch = src[offset];
+    while (ch) {
+      if (ch === "'") {
+        if (src[offset + 1] !== "'") break;
+        ch = src[offset += 2];
+      } else {
+        ch = src[offset += 1];
+      }
+    }
+    return offset + 1;
+  }
+
+  /**
+   * @returns {string | { str: string, errors: YAMLSyntaxError[] }}
+   */
+  get strValue() {
+    if (!this.valueRange || !this.context) return null;
+    const errors = [];
+    const {
+      start,
+      end
+    } = this.valueRange;
+    const {
+      indent,
+      src
+    } = this.context;
+    if (src[end - 1] !== "'") errors.push(new PlainValue.YAMLSyntaxError(this, "Missing closing 'quote"));
+    let str = '';
+    for (let i = start + 1; i < end - 1; ++i) {
+      const ch = src[i];
+      if (ch === '\n') {
+        if (PlainValue.Node.atDocumentBoundary(src, i + 1)) errors.push(new PlainValue.YAMLSemanticError(this, 'Document boundary indicators are not allowed within string values'));
+        const {
+          fold,
+          offset,
+          error
+        } = PlainValue.Node.foldNewline(src, i, indent);
+        str += fold;
+        i = offset;
+        if (error) errors.push(new PlainValue.YAMLSemanticError(this, 'Multi-line single-quoted string needs to be sufficiently indented'));
+      } else if (ch === "'") {
+        str += ch;
+        i += 1;
+        if (src[i] !== "'") errors.push(new PlainValue.YAMLSyntaxError(this, 'Unescaped single quote? This should not happen.'));
+      } else if (ch === ' ' || ch === '\t') {
+        // trim trailing whitespace
+        const wsStart = i;
+        let next = src[i + 1];
+        while (next === ' ' || next === '\t') {
+          i += 1;
+          next = src[i + 1];
+        }
+        if (next !== '\n') str += i > wsStart ? src.slice(wsStart, i + 1) : ch;
+      } else {
+        str += ch;
+      }
+    }
+    return errors.length > 0 ? {
+      errors,
+      str
+    } : str;
+  }
+
+  /**
+   * Parses a 'single quoted' value from the source
+   *
+   * @param {ParseContext} context
+   * @param {number} start - Index of first character
+   * @returns {number} - Index of the character after this scalar
+   */
+  parse(context, start) {
+    this.context = context;
+    const {
+      src
+    } = context;
+    let offset = QuoteSingle.endOfQuote(src, start + 1);
+    this.valueRange = new PlainValue.Range(start, offset);
+    offset = PlainValue.Node.endOfWhiteSpace(src, offset);
+    offset = this.parseComment(offset);
+    return offset;
+  }
+}
+
+function createNewNode(type, props) {
+  switch (type) {
+    case PlainValue.Type.ALIAS:
+      return new Alias(type, props);
+    case PlainValue.Type.BLOCK_FOLDED:
+    case PlainValue.Type.BLOCK_LITERAL:
+      return new BlockValue(type, props);
+    case PlainValue.Type.FLOW_MAP:
+    case PlainValue.Type.FLOW_SEQ:
+      return new FlowCollection(type, props);
+    case PlainValue.Type.MAP_KEY:
+    case PlainValue.Type.MAP_VALUE:
+    case PlainValue.Type.SEQ_ITEM:
+      return new CollectionItem(type, props);
+    case PlainValue.Type.COMMENT:
+    case PlainValue.Type.PLAIN:
+      return new PlainValue.PlainValue(type, props);
+    case PlainValue.Type.QUOTE_DOUBLE:
+      return new QuoteDouble(type, props);
+    case PlainValue.Type.QUOTE_SINGLE:
+      return new QuoteSingle(type, props);
+    /* istanbul ignore next */
+    default:
+      return null;
+    // should never happen
+  }
+}
+
+/**
+ * @param {boolean} atLineStart - Node starts at beginning of line
+ * @param {boolean} inFlow - true if currently in a flow context
+ * @param {boolean} inCollection - true if currently in a collection context
+ * @param {number} indent - Current level of indentation
+ * @param {number} lineStart - Start of the current line
+ * @param {Node} parent - The parent of the node
+ * @param {string} src - Source of the YAML document
+ */
+class ParseContext {
+  static parseType(src, offset, inFlow) {
+    switch (src[offset]) {
+      case '*':
+        return PlainValue.Type.ALIAS;
+      case '>':
+        return PlainValue.Type.BLOCK_FOLDED;
+      case '|':
+        return PlainValue.Type.BLOCK_LITERAL;
+      case '{':
+        return PlainValue.Type.FLOW_MAP;
+      case '[':
+        return PlainValue.Type.FLOW_SEQ;
+      case '?':
+        return !inFlow && PlainValue.Node.atBlank(src, offset + 1, true) ? PlainValue.Type.MAP_KEY : PlainValue.Type.PLAIN;
+      case ':':
+        return !inFlow && PlainValue.Node.atBlank(src, offset + 1, true) ? PlainValue.Type.MAP_VALUE : PlainValue.Type.PLAIN;
+      case '-':
+        return !inFlow && PlainValue.Node.atBlank(src, offset + 1, true) ? PlainValue.Type.SEQ_ITEM : PlainValue.Type.PLAIN;
+      case '"':
+        return PlainValue.Type.QUOTE_DOUBLE;
+      case "'":
+        return PlainValue.Type.QUOTE_SINGLE;
+      default:
+        return PlainValue.Type.PLAIN;
+    }
+  }
+  constructor(orig = {}, {
+    atLineStart,
+    inCollection,
+    inFlow,
+    indent,
+    lineStart,
+    parent
+  } = {}) {
+    /**
+     * Parses a node from the source
+     * @param {ParseContext} overlay
+     * @param {number} start - Index of first non-whitespace character for the node
+     * @returns {?Node} - null if at a document boundary
+     */
+    PlainValue._defineProperty(this, "parseNode", (overlay, start) => {
+      if (PlainValue.Node.atDocumentBoundary(this.src, start)) return null;
+      const context = new ParseContext(this, overlay);
+      const {
+        props,
+        type,
+        valueStart
+      } = context.parseProps(start);
+      const node = createNewNode(type, props);
+      let offset = start;
+      try {
+        offset = node.parse(context, valueStart);
+      } catch (error) {
+        const msg = error instanceof Error ? error.message : String(error);
+        if (!node.error) node.error = new PlainValue.YAMLSyntaxError(node, msg);
+      }
+      node.range = new PlainValue.Range(start, offset);
+      /* istanbul ignore if */
+      if (offset <= start) {
+        // This should never happen, but if it does, let's make sure to at least
+        // step one character forward to avoid a busy loop.
+        if (!node.error) node.error = new Error(`Node#parse consumed no characters`);
+        node.error.parseEnd = offset;
+        node.error.source = node;
+        node.range.end = start + 1;
+      }
+      if (context.nodeStartsCollection(node)) {
+        if (!node.error && !context.atLineStart && context.parent.type === PlainValue.Type.DOCUMENT) {
+          node.error = new PlainValue.YAMLSyntaxError(node, 'Block collection must not have preceding content here (e.g. directives-end indicator)');
+        }
+        const collection = new Collection(node);
+        offset = collection.parse(new ParseContext(context), offset);
+        collection.range = new PlainValue.Range(start, offset);
+        return collection;
+      }
+      return node;
+    });
+    this.atLineStart = atLineStart != null ? atLineStart : orig.atLineStart || false;
+    this.inCollection = inCollection != null ? inCollection : orig.inCollection || false;
+    this.inFlow = inFlow != null ? inFlow : orig.inFlow || false;
+    this.indent = indent != null ? indent : orig.indent;
+    this.lineStart = lineStart != null ? lineStart : orig.lineStart;
+    this.parent = parent != null ? parent : orig.parent || {};
+    this.root = orig.root;
+    this.src = orig.src;
+  }
+  nodeStartsCollection(node) {
+    const {
+      inCollection,
+      inFlow,
+      src
+    } = this;
+    if (inCollection || inFlow) return false;
+    if (node instanceof CollectionItem) return true;
+    // check for implicit key
+    let offset = node.range.end;
+    if (src[offset] === '\n' || src[offset - 1] === '\n') return false;
+    offset = PlainValue.Node.endOfWhiteSpace(src, offset);
+    return src[offset] === ':';
+  }
+
+  // Anchor and tag are before type, which determines the node implementation
+  // class; hence this intermediate step.
+  parseProps(offset) {
+    const {
+      inFlow,
+      parent,
+      src
+    } = this;
+    const props = [];
+    let lineHasProps = false;
+    offset = this.atLineStart ? PlainValue.Node.endOfIndent(src, offset) : PlainValue.Node.endOfWhiteSpace(src, offset);
+    let ch = src[offset];
+    while (ch === PlainValue.Char.ANCHOR || ch === PlainValue.Char.COMMENT || ch === PlainValue.Char.TAG || ch === '\n') {
+      if (ch === '\n') {
+        let inEnd = offset;
+        let lineStart;
+        do {
+          lineStart = inEnd + 1;
+          inEnd = PlainValue.Node.endOfIndent(src, lineStart);
+        } while (src[inEnd] === '\n');
+        const indentDiff = inEnd - (lineStart + this.indent);
+        const noIndicatorAsIndent = parent.type === PlainValue.Type.SEQ_ITEM && parent.context.atLineStart;
+        if (src[inEnd] !== '#' && !PlainValue.Node.nextNodeIsIndented(src[inEnd], indentDiff, !noIndicatorAsIndent)) break;
+        this.atLineStart = true;
+        this.lineStart = lineStart;
+        lineHasProps = false;
+        offset = inEnd;
+      } else if (ch === PlainValue.Char.COMMENT) {
+        const end = PlainValue.Node.endOfLine(src, offset + 1);
+        props.push(new PlainValue.Range(offset, end));
+        offset = end;
+      } else {
+        let end = PlainValue.Node.endOfIdentifier(src, offset + 1);
+        if (ch === PlainValue.Char.TAG && src[end] === ',' && /^[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+,\d\d\d\d(-\d\d){0,2}\/\S/.test(src.slice(offset + 1, end + 13))) {
+          // Let's presume we're dealing with a YAML 1.0 domain tag here, rather
+          // than an empty but 'foo.bar' private-tagged node in a flow collection
+          // followed without whitespace by a plain string starting with a year
+          // or date divided by something.
+          end = PlainValue.Node.endOfIdentifier(src, end + 5);
+        }
+        props.push(new PlainValue.Range(offset, end));
+        lineHasProps = true;
+        offset = PlainValue.Node.endOfWhiteSpace(src, end);
+      }
+      ch = src[offset];
+    }
+    // '- &a : b' has an anchor on an empty node
+    if (lineHasProps && ch === ':' && PlainValue.Node.atBlank(src, offset + 1, true)) offset -= 1;
+    const type = ParseContext.parseType(src, offset, inFlow);
+    return {
+      props,
+      type,
+      valueStart: offset
+    };
+  }
+}
+
+// Published as 'yaml/parse-cst'
+function parse(src) {
+  const cr = [];
+  if (src.indexOf('\r') !== -1) {
+    src = src.replace(/\r\n?/g, (match, offset) => {
+      if (match.length > 1) cr.push(offset);
+      return '\n';
+    });
+  }
+  const documents = [];
+  let offset = 0;
+  do {
+    const doc = new Document();
+    const context = new ParseContext({
+      src
+    });
+    offset = doc.parse(context, offset);
+    documents.push(doc);
+  } while (offset < src.length);
+  documents.setOrigRanges = () => {
+    if (cr.length === 0) return false;
+    for (let i = 1; i < cr.length; ++i) cr[i] -= i;
+    let crOffset = 0;
+    for (let i = 0; i < documents.length; ++i) {
+      crOffset = documents[i].setOrigRanges(cr, crOffset);
+    }
+    cr.splice(0, cr.length);
+    return true;
+  };
+  documents.toString = () => documents.join('...\n');
+  return documents;
+}
+
+exports.parse = parse;
Index: frontend/node_modules/yaml/dist/resolveSeq-95613e94.js
===================================================================
--- frontend/node_modules/yaml/dist/resolveSeq-95613e94.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/dist/resolveSeq-95613e94.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1859 @@
+'use strict';
+
+var PlainValue = require('./PlainValue-516d5bc2.js');
+
+function addCommentBefore(str, indent, comment) {
+  if (!comment) return str;
+  const cc = comment.replace(/[\s\S]^/gm, `$&${indent}#`);
+  return `#${cc}\n${indent}${str}`;
+}
+function addComment(str, indent, comment) {
+  return !comment ? str : comment.indexOf('\n') === -1 ? `${str} #${comment}` : `${str}\n` + comment.replace(/^/gm, `${indent || ''}#`);
+}
+
+class Node {}
+
+function toJSON(value, arg, ctx) {
+  if (Array.isArray(value)) return value.map((v, i) => toJSON(v, String(i), ctx));
+  if (value && typeof value.toJSON === 'function') {
+    const anchor = ctx && ctx.anchors && ctx.anchors.get(value);
+    if (anchor) ctx.onCreate = res => {
+      anchor.res = res;
+      delete ctx.onCreate;
+    };
+    const res = value.toJSON(arg, ctx);
+    if (anchor && ctx.onCreate) ctx.onCreate(res);
+    return res;
+  }
+  if ((!ctx || !ctx.keep) && typeof value === 'bigint') return Number(value);
+  return value;
+}
+
+class Scalar extends Node {
+  constructor(value) {
+    super();
+    this.value = value;
+  }
+  toJSON(arg, ctx) {
+    return ctx && ctx.keep ? this.value : toJSON(this.value, arg, ctx);
+  }
+  toString() {
+    return String(this.value);
+  }
+}
+
+function collectionFromPath(schema, path, value) {
+  let v = value;
+  for (let i = path.length - 1; i >= 0; --i) {
+    const k = path[i];
+    if (Number.isInteger(k) && k >= 0) {
+      const a = [];
+      a[k] = v;
+      v = a;
+    } else {
+      const o = {};
+      Object.defineProperty(o, k, {
+        value: v,
+        writable: true,
+        enumerable: true,
+        configurable: true
+      });
+      v = o;
+    }
+  }
+  return schema.createNode(v, false);
+}
+
+// null, undefined, or an empty non-string iterable (e.g. [])
+const isEmptyPath = path => path == null || typeof path === 'object' && path[Symbol.iterator]().next().done;
+class Collection extends Node {
+  constructor(schema) {
+    super();
+    PlainValue._defineProperty(this, "items", []);
+    this.schema = schema;
+  }
+  addIn(path, value) {
+    if (isEmptyPath(path)) this.add(value);else {
+      const [key, ...rest] = path;
+      const node = this.get(key, true);
+      if (node instanceof Collection) node.addIn(rest, value);else if (node === undefined && this.schema) this.set(key, collectionFromPath(this.schema, rest, value));else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
+    }
+  }
+  deleteIn([key, ...rest]) {
+    if (rest.length === 0) return this.delete(key);
+    const node = this.get(key, true);
+    if (node instanceof Collection) return node.deleteIn(rest);else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
+  }
+  getIn([key, ...rest], keepScalar) {
+    const node = this.get(key, true);
+    if (rest.length === 0) return !keepScalar && node instanceof Scalar ? node.value : node;else return node instanceof Collection ? node.getIn(rest, keepScalar) : undefined;
+  }
+  hasAllNullValues() {
+    return this.items.every(node => {
+      if (!node || node.type !== 'PAIR') return false;
+      const n = node.value;
+      return n == null || n instanceof Scalar && n.value == null && !n.commentBefore && !n.comment && !n.tag;
+    });
+  }
+  hasIn([key, ...rest]) {
+    if (rest.length === 0) return this.has(key);
+    const node = this.get(key, true);
+    return node instanceof Collection ? node.hasIn(rest) : false;
+  }
+  setIn([key, ...rest], value) {
+    if (rest.length === 0) {
+      this.set(key, value);
+    } else {
+      const node = this.get(key, true);
+      if (node instanceof Collection) node.setIn(rest, value);else if (node === undefined && this.schema) this.set(key, collectionFromPath(this.schema, rest, value));else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`);
+    }
+  }
+
+  // overridden in implementations
+  /* istanbul ignore next */
+  toJSON() {
+    return null;
+  }
+  toString(ctx, {
+    blockItem,
+    flowChars,
+    isMap,
+    itemIndent
+  }, onComment, onChompKeep) {
+    const {
+      indent,
+      indentStep,
+      stringify
+    } = ctx;
+    const inFlow = this.type === PlainValue.Type.FLOW_MAP || this.type === PlainValue.Type.FLOW_SEQ || ctx.inFlow;
+    if (inFlow) itemIndent += indentStep;
+    const allNullValues = isMap && this.hasAllNullValues();
+    ctx = Object.assign({}, ctx, {
+      allNullValues,
+      indent: itemIndent,
+      inFlow,
+      type: null
+    });
+    let chompKeep = false;
+    let hasItemWithNewLine = false;
+    const nodes = this.items.reduce((nodes, item, i) => {
+      let comment;
+      if (item) {
+        if (!chompKeep && item.spaceBefore) nodes.push({
+          type: 'comment',
+          str: ''
+        });
+        if (item.commentBefore) item.commentBefore.match(/^.*$/gm).forEach(line => {
+          nodes.push({
+            type: 'comment',
+            str: `#${line}`
+          });
+        });
+        if (item.comment) comment = item.comment;
+        if (inFlow && (!chompKeep && item.spaceBefore || item.commentBefore || item.comment || item.key && (item.key.commentBefore || item.key.comment) || item.value && (item.value.commentBefore || item.value.comment))) hasItemWithNewLine = true;
+      }
+      chompKeep = false;
+      let str = stringify(item, ctx, () => comment = null, () => chompKeep = true);
+      if (inFlow && !hasItemWithNewLine && str.includes('\n')) hasItemWithNewLine = true;
+      if (inFlow && i < this.items.length - 1) str += ',';
+      str = addComment(str, itemIndent, comment);
+      if (chompKeep && (comment || inFlow)) chompKeep = false;
+      nodes.push({
+        type: 'item',
+        str
+      });
+      return nodes;
+    }, []);
+    let str;
+    if (nodes.length === 0) {
+      str = flowChars.start + flowChars.end;
+    } else if (inFlow) {
+      const {
+        start,
+        end
+      } = flowChars;
+      const strings = nodes.map(n => n.str);
+      if (hasItemWithNewLine || strings.reduce((sum, str) => sum + str.length + 2, 2) > Collection.maxFlowStringSingleLineLength) {
+        str = start;
+        for (const s of strings) {
+          str += s ? `\n${indentStep}${indent}${s}` : '\n';
+        }
+        str += `\n${indent}${end}`;
+      } else {
+        str = `${start} ${strings.join(' ')} ${end}`;
+      }
+    } else {
+      const strings = nodes.map(blockItem);
+      str = strings.shift();
+      for (const s of strings) str += s ? `\n${indent}${s}` : '\n';
+    }
+    if (this.comment) {
+      str += '\n' + this.comment.replace(/^/gm, `${indent}#`);
+      if (onComment) onComment();
+    } else if (chompKeep && onChompKeep) onChompKeep();
+    return str;
+  }
+}
+PlainValue._defineProperty(Collection, "maxFlowStringSingleLineLength", 60);
+
+function asItemIndex(key) {
+  let idx = key instanceof Scalar ? key.value : key;
+  if (idx && typeof idx === 'string') idx = Number(idx);
+  return Number.isInteger(idx) && idx >= 0 ? idx : null;
+}
+class YAMLSeq extends Collection {
+  add(value) {
+    this.items.push(value);
+  }
+  delete(key) {
+    const idx = asItemIndex(key);
+    if (typeof idx !== 'number') return false;
+    const del = this.items.splice(idx, 1);
+    return del.length > 0;
+  }
+  get(key, keepScalar) {
+    const idx = asItemIndex(key);
+    if (typeof idx !== 'number') return undefined;
+    const it = this.items[idx];
+    return !keepScalar && it instanceof Scalar ? it.value : it;
+  }
+  has(key) {
+    const idx = asItemIndex(key);
+    return typeof idx === 'number' && idx < this.items.length;
+  }
+  set(key, value) {
+    const idx = asItemIndex(key);
+    if (typeof idx !== 'number') throw new Error(`Expected a valid index, not ${key}.`);
+    this.items[idx] = value;
+  }
+  toJSON(_, ctx) {
+    const seq = [];
+    if (ctx && ctx.onCreate) ctx.onCreate(seq);
+    let i = 0;
+    for (const item of this.items) seq.push(toJSON(item, String(i++), ctx));
+    return seq;
+  }
+  toString(ctx, onComment, onChompKeep) {
+    if (!ctx) return JSON.stringify(this);
+    return super.toString(ctx, {
+      blockItem: n => n.type === 'comment' ? n.str : `- ${n.str}`,
+      flowChars: {
+        start: '[',
+        end: ']'
+      },
+      isMap: false,
+      itemIndent: (ctx.indent || '') + '  '
+    }, onComment, onChompKeep);
+  }
+}
+
+const stringifyKey = (key, jsKey, ctx) => {
+  if (jsKey === null) return '';
+  if (typeof jsKey !== 'object') return String(jsKey);
+  if (key instanceof Node && ctx && ctx.doc) return key.toString({
+    anchors: Object.create(null),
+    doc: ctx.doc,
+    indent: '',
+    indentStep: ctx.indentStep,
+    inFlow: true,
+    inStringifyKey: true,
+    stringify: ctx.stringify
+  });
+  return JSON.stringify(jsKey);
+};
+class Pair extends Node {
+  constructor(key, value = null) {
+    super();
+    this.key = key;
+    this.value = value;
+    this.type = Pair.Type.PAIR;
+  }
+  get commentBefore() {
+    return this.key instanceof Node ? this.key.commentBefore : undefined;
+  }
+  set commentBefore(cb) {
+    if (this.key == null) this.key = new Scalar(null);
+    if (this.key instanceof Node) this.key.commentBefore = cb;else {
+      const msg = 'Pair.commentBefore is an alias for Pair.key.commentBefore. To set it, the key must be a Node.';
+      throw new Error(msg);
+    }
+  }
+  addToJSMap(ctx, map) {
+    const key = toJSON(this.key, '', ctx);
+    if (map instanceof Map) {
+      const value = toJSON(this.value, key, ctx);
+      map.set(key, value);
+    } else if (map instanceof Set) {
+      map.add(key);
+    } else {
+      const stringKey = stringifyKey(this.key, key, ctx);
+      const value = toJSON(this.value, stringKey, ctx);
+      if (stringKey in map) Object.defineProperty(map, stringKey, {
+        value,
+        writable: true,
+        enumerable: true,
+        configurable: true
+      });else map[stringKey] = value;
+    }
+    return map;
+  }
+  toJSON(_, ctx) {
+    const pair = ctx && ctx.mapAsMap ? new Map() : {};
+    return this.addToJSMap(ctx, pair);
+  }
+  toString(ctx, onComment, onChompKeep) {
+    if (!ctx || !ctx.doc) return JSON.stringify(this);
+    const {
+      indent: indentSize,
+      indentSeq,
+      simpleKeys
+    } = ctx.doc.options;
+    let {
+      key,
+      value
+    } = this;
+    let keyComment = key instanceof Node && key.comment;
+    if (simpleKeys) {
+      if (keyComment) {
+        throw new Error('With simple keys, key nodes cannot have comments');
+      }
+      if (key instanceof Collection) {
+        const msg = 'With simple keys, collection cannot be used as a key value';
+        throw new Error(msg);
+      }
+    }
+    let explicitKey = !simpleKeys && (!key || keyComment || (key instanceof Node ? key instanceof Collection || key.type === PlainValue.Type.BLOCK_FOLDED || key.type === PlainValue.Type.BLOCK_LITERAL : typeof key === 'object'));
+    const {
+      doc,
+      indent,
+      indentStep,
+      stringify
+    } = ctx;
+    ctx = Object.assign({}, ctx, {
+      implicitKey: !explicitKey,
+      indent: indent + indentStep
+    });
+    let chompKeep = false;
+    let str = stringify(key, ctx, () => keyComment = null, () => chompKeep = true);
+    str = addComment(str, ctx.indent, keyComment);
+    if (!explicitKey && str.length > 1024) {
+      if (simpleKeys) throw new Error('With simple keys, single line scalar must not span more than 1024 characters');
+      explicitKey = true;
+    }
+    if (ctx.allNullValues && !simpleKeys) {
+      if (this.comment) {
+        str = addComment(str, ctx.indent, this.comment);
+        if (onComment) onComment();
+      } else if (chompKeep && !keyComment && onChompKeep) onChompKeep();
+      return ctx.inFlow && !explicitKey ? str : `? ${str}`;
+    }
+    str = explicitKey ? `? ${str}\n${indent}:` : `${str}:`;
+    if (this.comment) {
+      // expected (but not strictly required) to be a single-line comment
+      str = addComment(str, ctx.indent, this.comment);
+      if (onComment) onComment();
+    }
+    let vcb = '';
+    let valueComment = null;
+    if (value instanceof Node) {
+      if (value.spaceBefore) vcb = '\n';
+      if (value.commentBefore) {
+        const cs = value.commentBefore.replace(/^/gm, `${ctx.indent}#`);
+        vcb += `\n${cs}`;
+      }
+      valueComment = value.comment;
+    } else if (value && typeof value === 'object') {
+      value = doc.schema.createNode(value, true);
+    }
+    ctx.implicitKey = false;
+    if (!explicitKey && !this.comment && value instanceof Scalar) ctx.indentAtStart = str.length + 1;
+    chompKeep = false;
+    if (!indentSeq && indentSize >= 2 && !ctx.inFlow && !explicitKey && value instanceof YAMLSeq && value.type !== PlainValue.Type.FLOW_SEQ && !value.tag && !doc.anchors.getName(value)) {
+      // If indentSeq === false, consider '- ' as part of indentation where possible
+      ctx.indent = ctx.indent.substr(2);
+    }
+    const valueStr = stringify(value, ctx, () => valueComment = null, () => chompKeep = true);
+    let ws = ' ';
+    if (vcb || this.comment) {
+      ws = `${vcb}\n${ctx.indent}`;
+    } else if (!explicitKey && value instanceof Collection) {
+      const flow = valueStr[0] === '[' || valueStr[0] === '{';
+      if (!flow || valueStr.includes('\n')) ws = `\n${ctx.indent}`;
+    } else if (valueStr[0] === '\n') ws = '';
+    if (chompKeep && !valueComment && onChompKeep) onChompKeep();
+    return addComment(str + ws + valueStr, ctx.indent, valueComment);
+  }
+}
+PlainValue._defineProperty(Pair, "Type", {
+  PAIR: 'PAIR',
+  MERGE_PAIR: 'MERGE_PAIR'
+});
+
+const getAliasCount = (node, anchors) => {
+  if (node instanceof Alias) {
+    const anchor = anchors.get(node.source);
+    return anchor.count * anchor.aliasCount;
+  } else if (node instanceof Collection) {
+    let count = 0;
+    for (const item of node.items) {
+      const c = getAliasCount(item, anchors);
+      if (c > count) count = c;
+    }
+    return count;
+  } else if (node instanceof Pair) {
+    const kc = getAliasCount(node.key, anchors);
+    const vc = getAliasCount(node.value, anchors);
+    return Math.max(kc, vc);
+  }
+  return 1;
+};
+class Alias extends Node {
+  static stringify({
+    range,
+    source
+  }, {
+    anchors,
+    doc,
+    implicitKey,
+    inStringifyKey
+  }) {
+    let anchor = Object.keys(anchors).find(a => anchors[a] === source);
+    if (!anchor && inStringifyKey) anchor = doc.anchors.getName(source) || doc.anchors.newName();
+    if (anchor) return `*${anchor}${implicitKey ? ' ' : ''}`;
+    const msg = doc.anchors.getName(source) ? 'Alias node must be after source node' : 'Source node not found for alias node';
+    throw new Error(`${msg} [${range}]`);
+  }
+  constructor(source) {
+    super();
+    this.source = source;
+    this.type = PlainValue.Type.ALIAS;
+  }
+  set tag(t) {
+    throw new Error('Alias nodes cannot have tags');
+  }
+  toJSON(arg, ctx) {
+    if (!ctx) return toJSON(this.source, arg, ctx);
+    const {
+      anchors,
+      maxAliasCount
+    } = ctx;
+    const anchor = anchors.get(this.source);
+    /* istanbul ignore if */
+    if (!anchor || anchor.res === undefined) {
+      const msg = 'This should not happen: Alias anchor was not resolved?';
+      if (this.cstNode) throw new PlainValue.YAMLReferenceError(this.cstNode, msg);else throw new ReferenceError(msg);
+    }
+    if (maxAliasCount >= 0) {
+      anchor.count += 1;
+      if (anchor.aliasCount === 0) anchor.aliasCount = getAliasCount(this.source, anchors);
+      if (anchor.count * anchor.aliasCount > maxAliasCount) {
+        const msg = 'Excessive alias count indicates a resource exhaustion attack';
+        if (this.cstNode) throw new PlainValue.YAMLReferenceError(this.cstNode, msg);else throw new ReferenceError(msg);
+      }
+    }
+    return anchor.res;
+  }
+
+  // Only called when stringifying an alias mapping key while constructing
+  // Object output.
+  toString(ctx) {
+    return Alias.stringify(this, ctx);
+  }
+}
+PlainValue._defineProperty(Alias, "default", true);
+
+function findPair(items, key) {
+  const k = key instanceof Scalar ? key.value : key;
+  for (const it of items) {
+    if (it instanceof Pair) {
+      if (it.key === key || it.key === k) return it;
+      if (it.key && it.key.value === k) return it;
+    }
+  }
+  return undefined;
+}
+class YAMLMap extends Collection {
+  add(pair, overwrite) {
+    if (!pair) pair = new Pair(pair);else if (!(pair instanceof Pair)) pair = new Pair(pair.key || pair, pair.value);
+    const prev = findPair(this.items, pair.key);
+    const sortEntries = this.schema && this.schema.sortMapEntries;
+    if (prev) {
+      if (overwrite) prev.value = pair.value;else throw new Error(`Key ${pair.key} already set`);
+    } else if (sortEntries) {
+      const i = this.items.findIndex(item => sortEntries(pair, item) < 0);
+      if (i === -1) this.items.push(pair);else this.items.splice(i, 0, pair);
+    } else {
+      this.items.push(pair);
+    }
+  }
+  delete(key) {
+    const it = findPair(this.items, key);
+    if (!it) return false;
+    const del = this.items.splice(this.items.indexOf(it), 1);
+    return del.length > 0;
+  }
+  get(key, keepScalar) {
+    const it = findPair(this.items, key);
+    const node = it && it.value;
+    return !keepScalar && node instanceof Scalar ? node.value : node;
+  }
+  has(key) {
+    return !!findPair(this.items, key);
+  }
+  set(key, value) {
+    this.add(new Pair(key, value), true);
+  }
+
+  /**
+   * @param {*} arg ignored
+   * @param {*} ctx Conversion context, originally set in Document#toJSON()
+   * @param {Class} Type If set, forces the returned collection type
+   * @returns {*} Instance of Type, Map, or Object
+   */
+  toJSON(_, ctx, Type) {
+    const map = Type ? new Type() : ctx && ctx.mapAsMap ? new Map() : {};
+    if (ctx && ctx.onCreate) ctx.onCreate(map);
+    for (const item of this.items) item.addToJSMap(ctx, map);
+    return map;
+  }
+  toString(ctx, onComment, onChompKeep) {
+    if (!ctx) return JSON.stringify(this);
+    for (const item of this.items) {
+      if (!(item instanceof Pair)) throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`);
+    }
+    return super.toString(ctx, {
+      blockItem: n => n.str,
+      flowChars: {
+        start: '{',
+        end: '}'
+      },
+      isMap: true,
+      itemIndent: ctx.indent || ''
+    }, onComment, onChompKeep);
+  }
+}
+
+const MERGE_KEY = '<<';
+class Merge extends Pair {
+  constructor(pair) {
+    if (pair instanceof Pair) {
+      let seq = pair.value;
+      if (!(seq instanceof YAMLSeq)) {
+        seq = new YAMLSeq();
+        seq.items.push(pair.value);
+        seq.range = pair.value.range;
+      }
+      super(pair.key, seq);
+      this.range = pair.range;
+    } else {
+      super(new Scalar(MERGE_KEY), new YAMLSeq());
+    }
+    this.type = Pair.Type.MERGE_PAIR;
+  }
+
+  // If the value associated with a merge key is a single mapping node, each of
+  // its key/value pairs is inserted into the current mapping, unless the key
+  // already exists in it. If the value associated with the merge key is a
+  // sequence, then this sequence is expected to contain mapping nodes and each
+  // of these nodes is merged in turn according to its order in the sequence.
+  // Keys in mapping nodes earlier in the sequence override keys specified in
+  // later mapping nodes. -- http://yaml.org/type/merge.html
+  addToJSMap(ctx, map) {
+    for (const {
+      source
+    } of this.value.items) {
+      if (!(source instanceof YAMLMap)) throw new Error('Merge sources must be maps');
+      const srcMap = source.toJSON(null, ctx, Map);
+      for (const [key, value] of srcMap) {
+        if (map instanceof Map) {
+          if (!map.has(key)) map.set(key, value);
+        } else if (map instanceof Set) {
+          map.add(key);
+        } else if (!Object.prototype.hasOwnProperty.call(map, key)) {
+          Object.defineProperty(map, key, {
+            value,
+            writable: true,
+            enumerable: true,
+            configurable: true
+          });
+        }
+      }
+    }
+    return map;
+  }
+  toString(ctx, onComment) {
+    const seq = this.value;
+    if (seq.items.length > 1) return super.toString(ctx, onComment);
+    this.value = seq.items[0];
+    const str = super.toString(ctx, onComment);
+    this.value = seq;
+    return str;
+  }
+}
+
+const binaryOptions = {
+  defaultType: PlainValue.Type.BLOCK_LITERAL,
+  lineWidth: 76
+};
+const boolOptions = {
+  trueStr: 'true',
+  falseStr: 'false'
+};
+const intOptions = {
+  asBigInt: false
+};
+const nullOptions = {
+  nullStr: 'null'
+};
+const strOptions = {
+  defaultType: PlainValue.Type.PLAIN,
+  doubleQuoted: {
+    jsonEncoding: false,
+    minMultiLineLength: 40
+  },
+  fold: {
+    lineWidth: 80,
+    minContentWidth: 20
+  }
+};
+
+// falls back to string on no match
+function resolveScalar(str, tags, scalarFallback) {
+  for (const {
+    format,
+    test,
+    resolve
+  } of tags) {
+    if (test) {
+      const match = str.match(test);
+      if (match) {
+        let res = resolve.apply(null, match);
+        if (!(res instanceof Scalar)) res = new Scalar(res);
+        if (format) res.format = format;
+        return res;
+      }
+    }
+  }
+  if (scalarFallback) str = scalarFallback(str);
+  return new Scalar(str);
+}
+
+const FOLD_FLOW = 'flow';
+const FOLD_BLOCK = 'block';
+const FOLD_QUOTED = 'quoted';
+
+// presumes i+1 is at the start of a line
+// returns index of last newline in more-indented block
+const consumeMoreIndentedLines = (text, i) => {
+  let ch = text[i + 1];
+  while (ch === ' ' || ch === '\t') {
+    do {
+      ch = text[i += 1];
+    } while (ch && ch !== '\n');
+    ch = text[i + 1];
+  }
+  return i;
+};
+
+/**
+ * Tries to keep input at up to `lineWidth` characters, splitting only on spaces
+ * not followed by newlines or spaces unless `mode` is `'quoted'`. Lines are
+ * terminated with `\n` and started with `indent`.
+ *
+ * @param {string} text
+ * @param {string} indent
+ * @param {string} [mode='flow'] `'block'` prevents more-indented lines
+ *   from being folded; `'quoted'` allows for `\` escapes, including escaped
+ *   newlines
+ * @param {Object} options
+ * @param {number} [options.indentAtStart] Accounts for leading contents on
+ *   the first line, defaulting to `indent.length`
+ * @param {number} [options.lineWidth=80]
+ * @param {number} [options.minContentWidth=20] Allow highly indented lines to
+ *   stretch the line width or indent content from the start
+ * @param {function} options.onFold Called once if the text is folded
+ * @param {function} options.onFold Called once if any line of text exceeds
+ *   lineWidth characters
+ */
+function foldFlowLines(text, indent, mode, {
+  indentAtStart,
+  lineWidth = 80,
+  minContentWidth = 20,
+  onFold,
+  onOverflow
+}) {
+  if (!lineWidth || lineWidth < 0) return text;
+  const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length);
+  if (text.length <= endStep) return text;
+  const folds = [];
+  const escapedFolds = {};
+  let end = lineWidth - indent.length;
+  if (typeof indentAtStart === 'number') {
+    if (indentAtStart > lineWidth - Math.max(2, minContentWidth)) folds.push(0);else end = lineWidth - indentAtStart;
+  }
+  let split = undefined;
+  let prev = undefined;
+  let overflow = false;
+  let i = -1;
+  let escStart = -1;
+  let escEnd = -1;
+  if (mode === FOLD_BLOCK) {
+    i = consumeMoreIndentedLines(text, i);
+    if (i !== -1) end = i + endStep;
+  }
+  for (let ch; ch = text[i += 1];) {
+    if (mode === FOLD_QUOTED && ch === '\\') {
+      escStart = i;
+      switch (text[i + 1]) {
+        case 'x':
+          i += 3;
+          break;
+        case 'u':
+          i += 5;
+          break;
+        case 'U':
+          i += 9;
+          break;
+        default:
+          i += 1;
+      }
+      escEnd = i;
+    }
+    if (ch === '\n') {
+      if (mode === FOLD_BLOCK) i = consumeMoreIndentedLines(text, i);
+      end = i + endStep;
+      split = undefined;
+    } else {
+      if (ch === ' ' && prev && prev !== ' ' && prev !== '\n' && prev !== '\t') {
+        // space surrounded by non-space can be replaced with newline + indent
+        const next = text[i + 1];
+        if (next && next !== ' ' && next !== '\n' && next !== '\t') split = i;
+      }
+      if (i >= end) {
+        if (split) {
+          folds.push(split);
+          end = split + endStep;
+          split = undefined;
+        } else if (mode === FOLD_QUOTED) {
+          // white-space collected at end may stretch past lineWidth
+          while (prev === ' ' || prev === '\t') {
+            prev = ch;
+            ch = text[i += 1];
+            overflow = true;
+          }
+          // Account for newline escape, but don't break preceding escape
+          const j = i > escEnd + 1 ? i - 2 : escStart - 1;
+          // Bail out if lineWidth & minContentWidth are shorter than an escape string
+          if (escapedFolds[j]) return text;
+          folds.push(j);
+          escapedFolds[j] = true;
+          end = j + endStep;
+          split = undefined;
+        } else {
+          overflow = true;
+        }
+      }
+    }
+    prev = ch;
+  }
+  if (overflow && onOverflow) onOverflow();
+  if (folds.length === 0) return text;
+  if (onFold) onFold();
+  let res = text.slice(0, folds[0]);
+  for (let i = 0; i < folds.length; ++i) {
+    const fold = folds[i];
+    const end = folds[i + 1] || text.length;
+    if (fold === 0) res = `\n${indent}${text.slice(0, end)}`;else {
+      if (mode === FOLD_QUOTED && escapedFolds[fold]) res += `${text[fold]}\\`;
+      res += `\n${indent}${text.slice(fold + 1, end)}`;
+    }
+  }
+  return res;
+}
+
+const getFoldOptions = ({
+  indentAtStart
+}) => indentAtStart ? Object.assign({
+  indentAtStart
+}, strOptions.fold) : strOptions.fold;
+
+// Also checks for lines starting with %, as parsing the output as YAML 1.1 will
+// presume that's starting a new document.
+const containsDocumentMarker = str => /^(%|---|\.\.\.)/m.test(str);
+function lineLengthOverLimit(str, lineWidth, indentLength) {
+  if (!lineWidth || lineWidth < 0) return false;
+  const limit = lineWidth - indentLength;
+  const strLen = str.length;
+  if (strLen <= limit) return false;
+  for (let i = 0, start = 0; i < strLen; ++i) {
+    if (str[i] === '\n') {
+      if (i - start > limit) return true;
+      start = i + 1;
+      if (strLen - start <= limit) return false;
+    }
+  }
+  return true;
+}
+function doubleQuotedString(value, ctx) {
+  const {
+    implicitKey
+  } = ctx;
+  const {
+    jsonEncoding,
+    minMultiLineLength
+  } = strOptions.doubleQuoted;
+  const json = JSON.stringify(value);
+  if (jsonEncoding) return json;
+  const indent = ctx.indent || (containsDocumentMarker(value) ? '  ' : '');
+  let str = '';
+  let start = 0;
+  for (let i = 0, ch = json[i]; ch; ch = json[++i]) {
+    if (ch === ' ' && json[i + 1] === '\\' && json[i + 2] === 'n') {
+      // space before newline needs to be escaped to not be folded
+      str += json.slice(start, i) + '\\ ';
+      i += 1;
+      start = i;
+      ch = '\\';
+    }
+    if (ch === '\\') switch (json[i + 1]) {
+      case 'u':
+        {
+          str += json.slice(start, i);
+          const code = json.substr(i + 2, 4);
+          switch (code) {
+            case '0000':
+              str += '\\0';
+              break;
+            case '0007':
+              str += '\\a';
+              break;
+            case '000b':
+              str += '\\v';
+              break;
+            case '001b':
+              str += '\\e';
+              break;
+            case '0085':
+              str += '\\N';
+              break;
+            case '00a0':
+              str += '\\_';
+              break;
+            case '2028':
+              str += '\\L';
+              break;
+            case '2029':
+              str += '\\P';
+              break;
+            default:
+              if (code.substr(0, 2) === '00') str += '\\x' + code.substr(2);else str += json.substr(i, 6);
+          }
+          i += 5;
+          start = i + 1;
+        }
+        break;
+      case 'n':
+        if (implicitKey || json[i + 2] === '"' || json.length < minMultiLineLength) {
+          i += 1;
+        } else {
+          // folding will eat first newline
+          str += json.slice(start, i) + '\n\n';
+          while (json[i + 2] === '\\' && json[i + 3] === 'n' && json[i + 4] !== '"') {
+            str += '\n';
+            i += 2;
+          }
+          str += indent;
+          // space after newline needs to be escaped to not be folded
+          if (json[i + 2] === ' ') str += '\\';
+          i += 1;
+          start = i + 1;
+        }
+        break;
+      default:
+        i += 1;
+    }
+  }
+  str = start ? str + json.slice(start) : json;
+  return implicitKey ? str : foldFlowLines(str, indent, FOLD_QUOTED, getFoldOptions(ctx));
+}
+function singleQuotedString(value, ctx) {
+  if (ctx.implicitKey) {
+    if (/\n/.test(value)) return doubleQuotedString(value, ctx);
+  } else {
+    // single quoted string can't have leading or trailing whitespace around newline
+    if (/[ \t]\n|\n[ \t]/.test(value)) return doubleQuotedString(value, ctx);
+  }
+  const indent = ctx.indent || (containsDocumentMarker(value) ? '  ' : '');
+  const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'";
+  return ctx.implicitKey ? res : foldFlowLines(res, indent, FOLD_FLOW, getFoldOptions(ctx));
+}
+function blockString({
+  comment,
+  type,
+  value
+}, ctx, onComment, onChompKeep) {
+  // 1. Block can't end in whitespace unless the last line is non-empty.
+  // 2. Strings consisting of only whitespace are best rendered explicitly.
+  if (/\n[\t ]+$/.test(value) || /^\s*$/.test(value)) {
+    return doubleQuotedString(value, ctx);
+  }
+  const indent = ctx.indent || (ctx.forceBlockIndent || containsDocumentMarker(value) ? '  ' : '');
+  const indentSize = indent ? '2' : '1'; // root is at -1
+  const literal = type === PlainValue.Type.BLOCK_FOLDED ? false : type === PlainValue.Type.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, strOptions.fold.lineWidth, indent.length);
+  let header = literal ? '|' : '>';
+  if (!value) return header + '\n';
+  let wsStart = '';
+  let wsEnd = '';
+  value = value.replace(/[\n\t ]*$/, ws => {
+    const n = ws.indexOf('\n');
+    if (n === -1) {
+      header += '-'; // strip
+    } else if (value === ws || n !== ws.length - 1) {
+      header += '+'; // keep
+      if (onChompKeep) onChompKeep();
+    }
+    wsEnd = ws.replace(/\n$/, '');
+    return '';
+  }).replace(/^[\n ]*/, ws => {
+    if (ws.indexOf(' ') !== -1) header += indentSize;
+    const m = ws.match(/ +$/);
+    if (m) {
+      wsStart = ws.slice(0, -m[0].length);
+      return m[0];
+    } else {
+      wsStart = ws;
+      return '';
+    }
+  });
+  if (wsEnd) wsEnd = wsEnd.replace(/\n+(?!\n|$)/g, `$&${indent}`);
+  if (wsStart) wsStart = wsStart.replace(/\n+/g, `$&${indent}`);
+  if (comment) {
+    header += ' #' + comment.replace(/ ?[\r\n]+/g, ' ');
+    if (onComment) onComment();
+  }
+  if (!value) return `${header}${indentSize}\n${indent}${wsEnd}`;
+  if (literal) {
+    value = value.replace(/\n+/g, `$&${indent}`);
+    return `${header}\n${indent}${wsStart}${value}${wsEnd}`;
+  }
+  value = value.replace(/\n+/g, '\n$&').replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, '$1$2') // more-indented lines aren't folded
+  //         ^ ind.line  ^ empty     ^ capture next empty lines only at end of indent
+  .replace(/\n+/g, `$&${indent}`);
+  const body = foldFlowLines(`${wsStart}${value}${wsEnd}`, indent, FOLD_BLOCK, strOptions.fold);
+  return `${header}\n${indent}${body}`;
+}
+function plainString(item, ctx, onComment, onChompKeep) {
+  const {
+    comment,
+    type,
+    value
+  } = item;
+  const {
+    actualString,
+    implicitKey,
+    indent,
+    inFlow
+  } = ctx;
+  if (implicitKey && /[\n[\]{},]/.test(value) || inFlow && /[[\]{},]/.test(value)) {
+    return doubleQuotedString(value, ctx);
+  }
+  if (!value || /^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) {
+    // not allowed:
+    // - empty string, '-' or '?'
+    // - start with an indicator character (except [?:-]) or /[?-] /
+    // - '\n ', ': ' or ' \n' anywhere
+    // - '#' not preceded by a non-space char
+    // - end with ' ' or ':'
+    return implicitKey || inFlow || value.indexOf('\n') === -1 ? value.indexOf('"') !== -1 && value.indexOf("'") === -1 ? singleQuotedString(value, ctx) : doubleQuotedString(value, ctx) : blockString(item, ctx, onComment, onChompKeep);
+  }
+  if (!implicitKey && !inFlow && type !== PlainValue.Type.PLAIN && value.indexOf('\n') !== -1) {
+    // Where allowed & type not set explicitly, prefer block style for multiline strings
+    return blockString(item, ctx, onComment, onChompKeep);
+  }
+  if (indent === '' && containsDocumentMarker(value)) {
+    ctx.forceBlockIndent = true;
+    return blockString(item, ctx, onComment, onChompKeep);
+  }
+  const str = value.replace(/\n+/g, `$&\n${indent}`);
+  // Verify that output will be parsed as a string, as e.g. plain numbers and
+  // booleans get parsed with those types in v1.2 (e.g. '42', 'true' & '0.9e-3'),
+  // and others in v1.1.
+  if (actualString) {
+    const {
+      tags
+    } = ctx.doc.schema;
+    const resolved = resolveScalar(str, tags, tags.scalarFallback).value;
+    if (typeof resolved !== 'string') return doubleQuotedString(value, ctx);
+  }
+  const body = implicitKey ? str : foldFlowLines(str, indent, FOLD_FLOW, getFoldOptions(ctx));
+  if (comment && !inFlow && (body.indexOf('\n') !== -1 || comment.indexOf('\n') !== -1)) {
+    if (onComment) onComment();
+    return addCommentBefore(body, indent, comment);
+  }
+  return body;
+}
+function stringifyString(item, ctx, onComment, onChompKeep) {
+  const {
+    defaultType
+  } = strOptions;
+  const {
+    implicitKey,
+    inFlow
+  } = ctx;
+  let {
+    type,
+    value
+  } = item;
+  if (typeof value !== 'string') {
+    value = String(value);
+    item = Object.assign({}, item, {
+      value
+    });
+  }
+  const _stringify = _type => {
+    switch (_type) {
+      case PlainValue.Type.BLOCK_FOLDED:
+      case PlainValue.Type.BLOCK_LITERAL:
+        return blockString(item, ctx, onComment, onChompKeep);
+      case PlainValue.Type.QUOTE_DOUBLE:
+        return doubleQuotedString(value, ctx);
+      case PlainValue.Type.QUOTE_SINGLE:
+        return singleQuotedString(value, ctx);
+      case PlainValue.Type.PLAIN:
+        return plainString(item, ctx, onComment, onChompKeep);
+      default:
+        return null;
+    }
+  };
+  if (type !== PlainValue.Type.QUOTE_DOUBLE && /[\x00-\x08\x0b-\x1f\x7f-\x9f]/.test(value)) {
+    // force double quotes on control characters
+    type = PlainValue.Type.QUOTE_DOUBLE;
+  } else if ((implicitKey || inFlow) && (type === PlainValue.Type.BLOCK_FOLDED || type === PlainValue.Type.BLOCK_LITERAL)) {
+    // should not happen; blocks are not valid inside flow containers
+    type = PlainValue.Type.QUOTE_DOUBLE;
+  }
+  let res = _stringify(type);
+  if (res === null) {
+    res = _stringify(defaultType);
+    if (res === null) throw new Error(`Unsupported default string type ${defaultType}`);
+  }
+  return res;
+}
+
+function stringifyNumber({
+  format,
+  minFractionDigits,
+  tag,
+  value
+}) {
+  if (typeof value === 'bigint') return String(value);
+  if (!isFinite(value)) return isNaN(value) ? '.nan' : value < 0 ? '-.inf' : '.inf';
+  let n = JSON.stringify(value);
+  if (!format && minFractionDigits && (!tag || tag === 'tag:yaml.org,2002:float') && /^\d/.test(n)) {
+    let i = n.indexOf('.');
+    if (i < 0) {
+      i = n.length;
+      n += '.';
+    }
+    let d = minFractionDigits - (n.length - i - 1);
+    while (d-- > 0) n += '0';
+  }
+  return n;
+}
+
+function checkFlowCollectionEnd(errors, cst) {
+  let char, name;
+  switch (cst.type) {
+    case PlainValue.Type.FLOW_MAP:
+      char = '}';
+      name = 'flow map';
+      break;
+    case PlainValue.Type.FLOW_SEQ:
+      char = ']';
+      name = 'flow sequence';
+      break;
+    default:
+      errors.push(new PlainValue.YAMLSemanticError(cst, 'Not a flow collection!?'));
+      return;
+  }
+  let lastItem;
+  for (let i = cst.items.length - 1; i >= 0; --i) {
+    const item = cst.items[i];
+    if (!item || item.type !== PlainValue.Type.COMMENT) {
+      lastItem = item;
+      break;
+    }
+  }
+  if (lastItem && lastItem.char !== char) {
+    const msg = `Expected ${name} to end with ${char}`;
+    let err;
+    if (typeof lastItem.offset === 'number') {
+      err = new PlainValue.YAMLSemanticError(cst, msg);
+      err.offset = lastItem.offset + 1;
+    } else {
+      err = new PlainValue.YAMLSemanticError(lastItem, msg);
+      if (lastItem.range && lastItem.range.end) err.offset = lastItem.range.end - lastItem.range.start;
+    }
+    errors.push(err);
+  }
+}
+function checkFlowCommentSpace(errors, comment) {
+  const prev = comment.context.src[comment.range.start - 1];
+  if (prev !== '\n' && prev !== '\t' && prev !== ' ') {
+    const msg = 'Comments must be separated from other tokens by white space characters';
+    errors.push(new PlainValue.YAMLSemanticError(comment, msg));
+  }
+}
+function getLongKeyError(source, key) {
+  const sk = String(key);
+  const k = sk.substr(0, 8) + '...' + sk.substr(-8);
+  return new PlainValue.YAMLSemanticError(source, `The "${k}" key is too long`);
+}
+function resolveComments(collection, comments) {
+  for (const {
+    afterKey,
+    before,
+    comment
+  } of comments) {
+    let item = collection.items[before];
+    if (!item) {
+      if (comment !== undefined) {
+        if (collection.comment) collection.comment += '\n' + comment;else collection.comment = comment;
+      }
+    } else {
+      if (afterKey && item.value) item = item.value;
+      if (comment === undefined) {
+        if (afterKey || !item.commentBefore) item.spaceBefore = true;
+      } else {
+        if (item.commentBefore) item.commentBefore += '\n' + comment;else item.commentBefore = comment;
+      }
+    }
+  }
+}
+
+// on error, will return { str: string, errors: Error[] }
+function resolveString(doc, node) {
+  const res = node.strValue;
+  if (!res) return '';
+  if (typeof res === 'string') return res;
+  res.errors.forEach(error => {
+    if (!error.source) error.source = node;
+    doc.errors.push(error);
+  });
+  return res.str;
+}
+
+function resolveTagHandle(doc, node) {
+  const {
+    handle,
+    suffix
+  } = node.tag;
+  let prefix = doc.tagPrefixes.find(p => p.handle === handle);
+  if (!prefix) {
+    const dtp = doc.getDefaults().tagPrefixes;
+    if (dtp) prefix = dtp.find(p => p.handle === handle);
+    if (!prefix) throw new PlainValue.YAMLSemanticError(node, `The ${handle} tag handle is non-default and was not declared.`);
+  }
+  if (!suffix) throw new PlainValue.YAMLSemanticError(node, `The ${handle} tag has no suffix.`);
+  if (handle === '!' && (doc.version || doc.options.version) === '1.0') {
+    if (suffix[0] === '^') {
+      doc.warnings.push(new PlainValue.YAMLWarning(node, 'YAML 1.0 ^ tag expansion is not supported'));
+      return suffix;
+    }
+    if (/[:/]/.test(suffix)) {
+      // word/foo -> tag:word.yaml.org,2002:foo
+      const vocab = suffix.match(/^([a-z0-9-]+)\/(.*)/i);
+      return vocab ? `tag:${vocab[1]}.yaml.org,2002:${vocab[2]}` : `tag:${suffix}`;
+    }
+  }
+  return prefix.prefix + decodeURIComponent(suffix);
+}
+function resolveTagName(doc, node) {
+  const {
+    tag,
+    type
+  } = node;
+  let nonSpecific = false;
+  if (tag) {
+    const {
+      handle,
+      suffix,
+      verbatim
+    } = tag;
+    if (verbatim) {
+      if (verbatim !== '!' && verbatim !== '!!') return verbatim;
+      const msg = `Verbatim tags aren't resolved, so ${verbatim} is invalid.`;
+      doc.errors.push(new PlainValue.YAMLSemanticError(node, msg));
+    } else if (handle === '!' && !suffix) {
+      nonSpecific = true;
+    } else {
+      try {
+        return resolveTagHandle(doc, node);
+      } catch (error) {
+        doc.errors.push(error);
+      }
+    }
+  }
+  switch (type) {
+    case PlainValue.Type.BLOCK_FOLDED:
+    case PlainValue.Type.BLOCK_LITERAL:
+    case PlainValue.Type.QUOTE_DOUBLE:
+    case PlainValue.Type.QUOTE_SINGLE:
+      return PlainValue.defaultTags.STR;
+    case PlainValue.Type.FLOW_MAP:
+    case PlainValue.Type.MAP:
+      return PlainValue.defaultTags.MAP;
+    case PlainValue.Type.FLOW_SEQ:
+    case PlainValue.Type.SEQ:
+      return PlainValue.defaultTags.SEQ;
+    case PlainValue.Type.PLAIN:
+      return nonSpecific ? PlainValue.defaultTags.STR : null;
+    default:
+      return null;
+  }
+}
+
+function resolveByTagName(doc, node, tagName) {
+  const {
+    tags
+  } = doc.schema;
+  const matchWithTest = [];
+  for (const tag of tags) {
+    if (tag.tag === tagName) {
+      if (tag.test) matchWithTest.push(tag);else {
+        const res = tag.resolve(doc, node);
+        return res instanceof Collection ? res : new Scalar(res);
+      }
+    }
+  }
+  const str = resolveString(doc, node);
+  if (typeof str === 'string' && matchWithTest.length > 0) return resolveScalar(str, matchWithTest, tags.scalarFallback);
+  return null;
+}
+function getFallbackTagName({
+  type
+}) {
+  switch (type) {
+    case PlainValue.Type.FLOW_MAP:
+    case PlainValue.Type.MAP:
+      return PlainValue.defaultTags.MAP;
+    case PlainValue.Type.FLOW_SEQ:
+    case PlainValue.Type.SEQ:
+      return PlainValue.defaultTags.SEQ;
+    default:
+      return PlainValue.defaultTags.STR;
+  }
+}
+function resolveTag(doc, node, tagName) {
+  try {
+    const res = resolveByTagName(doc, node, tagName);
+    if (res) {
+      if (tagName && node.tag) res.tag = tagName;
+      return res;
+    }
+  } catch (error) {
+    if (error instanceof PlainValue.YAMLError) {
+      if (!error.source) error.source = node;
+      doc.errors.push(error);
+    } else {
+      const msg = error instanceof Error ? error.message : String(error);
+      doc.errors.push(new PlainValue.YAMLSemanticError(node, msg));
+    }
+    return null;
+  }
+  try {
+    const fallback = getFallbackTagName(node);
+    if (!fallback) throw new Error(`The tag ${tagName} is unavailable`);
+    const msg = `The tag ${tagName} is unavailable, falling back to ${fallback}`;
+    doc.warnings.push(new PlainValue.YAMLWarning(node, msg));
+    const res = resolveByTagName(doc, node, fallback);
+    res.tag = tagName;
+    return res;
+  } catch (error) {
+    const refError = new PlainValue.YAMLReferenceError(node, error.message);
+    refError.stack = error.stack;
+    doc.errors.push(refError);
+    return null;
+  }
+}
+
+const isCollectionItem = node => {
+  if (!node) return false;
+  const {
+    type
+  } = node;
+  return type === PlainValue.Type.MAP_KEY || type === PlainValue.Type.MAP_VALUE || type === PlainValue.Type.SEQ_ITEM;
+};
+function resolveNodeProps(errors, node) {
+  const comments = {
+    before: [],
+    after: []
+  };
+  let hasAnchor = false;
+  let hasTag = false;
+  const props = isCollectionItem(node.context.parent) ? node.context.parent.props.concat(node.props) : node.props;
+  for (const {
+    start,
+    end
+  } of props) {
+    switch (node.context.src[start]) {
+      case PlainValue.Char.COMMENT:
+        {
+          if (!node.commentHasRequiredWhitespace(start)) {
+            const msg = 'Comments must be separated from other tokens by white space characters';
+            errors.push(new PlainValue.YAMLSemanticError(node, msg));
+          }
+          const {
+            header,
+            valueRange
+          } = node;
+          const cc = valueRange && (start > valueRange.start || header && start > header.start) ? comments.after : comments.before;
+          cc.push(node.context.src.slice(start + 1, end));
+          break;
+        }
+
+      // Actual anchor & tag resolution is handled by schema, here we just complain
+      case PlainValue.Char.ANCHOR:
+        if (hasAnchor) {
+          const msg = 'A node can have at most one anchor';
+          errors.push(new PlainValue.YAMLSemanticError(node, msg));
+        }
+        hasAnchor = true;
+        break;
+      case PlainValue.Char.TAG:
+        if (hasTag) {
+          const msg = 'A node can have at most one tag';
+          errors.push(new PlainValue.YAMLSemanticError(node, msg));
+        }
+        hasTag = true;
+        break;
+    }
+  }
+  return {
+    comments,
+    hasAnchor,
+    hasTag
+  };
+}
+function resolveNodeValue(doc, node) {
+  const {
+    anchors,
+    errors,
+    schema
+  } = doc;
+  if (node.type === PlainValue.Type.ALIAS) {
+    const name = node.rawValue;
+    const src = anchors.getNode(name);
+    if (!src) {
+      const msg = `Aliased anchor not found: ${name}`;
+      errors.push(new PlainValue.YAMLReferenceError(node, msg));
+      return null;
+    }
+
+    // Lazy resolution for circular references
+    const res = new Alias(src);
+    anchors._cstAliases.push(res);
+    return res;
+  }
+  const tagName = resolveTagName(doc, node);
+  if (tagName) return resolveTag(doc, node, tagName);
+  if (node.type !== PlainValue.Type.PLAIN) {
+    const msg = `Failed to resolve ${node.type} node here`;
+    errors.push(new PlainValue.YAMLSyntaxError(node, msg));
+    return null;
+  }
+  try {
+    const str = resolveString(doc, node);
+    return resolveScalar(str, schema.tags, schema.tags.scalarFallback);
+  } catch (error) {
+    if (!error.source) error.source = node;
+    errors.push(error);
+    return null;
+  }
+}
+
+// sets node.resolved on success
+function resolveNode(doc, node) {
+  if (!node) return null;
+  if (node.error) doc.errors.push(node.error);
+  const {
+    comments,
+    hasAnchor,
+    hasTag
+  } = resolveNodeProps(doc.errors, node);
+  if (hasAnchor) {
+    const {
+      anchors
+    } = doc;
+    const name = node.anchor;
+    const prev = anchors.getNode(name);
+    // At this point, aliases for any preceding node with the same anchor
+    // name have already been resolved, so it may safely be renamed.
+    if (prev) anchors.map[anchors.newName(name)] = prev;
+    // During parsing, we need to store the CST node in anchors.map as
+    // anchors need to be available during resolution to allow for
+    // circular references.
+    anchors.map[name] = node;
+  }
+  if (node.type === PlainValue.Type.ALIAS && (hasAnchor || hasTag)) {
+    const msg = 'An alias node must not specify any properties';
+    doc.errors.push(new PlainValue.YAMLSemanticError(node, msg));
+  }
+  const res = resolveNodeValue(doc, node);
+  if (res) {
+    res.range = [node.range.start, node.range.end];
+    if (doc.options.keepCstNodes) res.cstNode = node;
+    if (doc.options.keepNodeTypes) res.type = node.type;
+    const cb = comments.before.join('\n');
+    if (cb) {
+      res.commentBefore = res.commentBefore ? `${res.commentBefore}\n${cb}` : cb;
+    }
+    const ca = comments.after.join('\n');
+    if (ca) res.comment = res.comment ? `${res.comment}\n${ca}` : ca;
+  }
+  return node.resolved = res;
+}
+
+function resolveMap(doc, cst) {
+  if (cst.type !== PlainValue.Type.MAP && cst.type !== PlainValue.Type.FLOW_MAP) {
+    const msg = `A ${cst.type} node cannot be resolved as a mapping`;
+    doc.errors.push(new PlainValue.YAMLSyntaxError(cst, msg));
+    return null;
+  }
+  const {
+    comments,
+    items
+  } = cst.type === PlainValue.Type.FLOW_MAP ? resolveFlowMapItems(doc, cst) : resolveBlockMapItems(doc, cst);
+  const map = new YAMLMap();
+  map.items = items;
+  resolveComments(map, comments);
+  let hasCollectionKey = false;
+  for (let i = 0; i < items.length; ++i) {
+    const {
+      key: iKey
+    } = items[i];
+    if (iKey instanceof Collection) hasCollectionKey = true;
+    if (doc.schema.merge && iKey && iKey.value === MERGE_KEY) {
+      items[i] = new Merge(items[i]);
+      const sources = items[i].value.items;
+      let error = null;
+      sources.some(node => {
+        if (node instanceof Alias) {
+          // During parsing, alias sources are CST nodes; to account for
+          // circular references their resolved values can't be used here.
+          const {
+            type
+          } = node.source;
+          if (type === PlainValue.Type.MAP || type === PlainValue.Type.FLOW_MAP) return false;
+          return error = 'Merge nodes aliases can only point to maps';
+        }
+        return error = 'Merge nodes can only have Alias nodes as values';
+      });
+      if (error) doc.errors.push(new PlainValue.YAMLSemanticError(cst, error));
+    } else {
+      for (let j = i + 1; j < items.length; ++j) {
+        const {
+          key: jKey
+        } = items[j];
+        if (iKey === jKey || iKey && jKey && Object.prototype.hasOwnProperty.call(iKey, 'value') && iKey.value === jKey.value) {
+          const msg = `Map keys must be unique; "${iKey}" is repeated`;
+          doc.errors.push(new PlainValue.YAMLSemanticError(cst, msg));
+          break;
+        }
+      }
+    }
+  }
+  if (hasCollectionKey && !doc.options.mapAsMap) {
+    const warn = 'Keys with collection values will be stringified as YAML due to JS Object restrictions. Use mapAsMap: true to avoid this.';
+    doc.warnings.push(new PlainValue.YAMLWarning(cst, warn));
+  }
+  cst.resolved = map;
+  return map;
+}
+const valueHasPairComment = ({
+  context: {
+    lineStart,
+    node,
+    src
+  },
+  props
+}) => {
+  if (props.length === 0) return false;
+  const {
+    start
+  } = props[0];
+  if (node && start > node.valueRange.start) return false;
+  if (src[start] !== PlainValue.Char.COMMENT) return false;
+  for (let i = lineStart; i < start; ++i) if (src[i] === '\n') return false;
+  return true;
+};
+function resolvePairComment(item, pair) {
+  if (!valueHasPairComment(item)) return;
+  const comment = item.getPropValue(0, PlainValue.Char.COMMENT, true);
+  let found = false;
+  const cb = pair.value.commentBefore;
+  if (cb && cb.startsWith(comment)) {
+    pair.value.commentBefore = cb.substr(comment.length + 1);
+    found = true;
+  } else {
+    const cc = pair.value.comment;
+    if (!item.node && cc && cc.startsWith(comment)) {
+      pair.value.comment = cc.substr(comment.length + 1);
+      found = true;
+    }
+  }
+  if (found) pair.comment = comment;
+}
+function resolveBlockMapItems(doc, cst) {
+  const comments = [];
+  const items = [];
+  let key = undefined;
+  let keyStart = null;
+  for (let i = 0; i < cst.items.length; ++i) {
+    const item = cst.items[i];
+    switch (item.type) {
+      case PlainValue.Type.BLANK_LINE:
+        comments.push({
+          afterKey: !!key,
+          before: items.length
+        });
+        break;
+      case PlainValue.Type.COMMENT:
+        comments.push({
+          afterKey: !!key,
+          before: items.length,
+          comment: item.comment
+        });
+        break;
+      case PlainValue.Type.MAP_KEY:
+        if (key !== undefined) items.push(new Pair(key));
+        if (item.error) doc.errors.push(item.error);
+        key = resolveNode(doc, item.node);
+        keyStart = null;
+        break;
+      case PlainValue.Type.MAP_VALUE:
+        {
+          if (key === undefined) key = null;
+          if (item.error) doc.errors.push(item.error);
+          if (!item.context.atLineStart && item.node && item.node.type === PlainValue.Type.MAP && !item.node.context.atLineStart) {
+            const msg = 'Nested mappings are not allowed in compact mappings';
+            doc.errors.push(new PlainValue.YAMLSemanticError(item.node, msg));
+          }
+          let valueNode = item.node;
+          if (!valueNode && item.props.length > 0) {
+            // Comments on an empty mapping value need to be preserved, so we
+            // need to construct a minimal empty node here to use instead of the
+            // missing `item.node`. -- eemeli/yaml#19
+            valueNode = new PlainValue.PlainValue(PlainValue.Type.PLAIN, []);
+            valueNode.context = {
+              parent: item,
+              src: item.context.src
+            };
+            const pos = item.range.start + 1;
+            valueNode.range = {
+              start: pos,
+              end: pos
+            };
+            valueNode.valueRange = {
+              start: pos,
+              end: pos
+            };
+            if (typeof item.range.origStart === 'number') {
+              const origPos = item.range.origStart + 1;
+              valueNode.range.origStart = valueNode.range.origEnd = origPos;
+              valueNode.valueRange.origStart = valueNode.valueRange.origEnd = origPos;
+            }
+          }
+          const pair = new Pair(key, resolveNode(doc, valueNode));
+          resolvePairComment(item, pair);
+          items.push(pair);
+          if (key && typeof keyStart === 'number') {
+            if (item.range.start > keyStart + 1024) doc.errors.push(getLongKeyError(cst, key));
+          }
+          key = undefined;
+          keyStart = null;
+        }
+        break;
+      default:
+        if (key !== undefined) items.push(new Pair(key));
+        key = resolveNode(doc, item);
+        keyStart = item.range.start;
+        if (item.error) doc.errors.push(item.error);
+        next: for (let j = i + 1;; ++j) {
+          const nextItem = cst.items[j];
+          switch (nextItem && nextItem.type) {
+            case PlainValue.Type.BLANK_LINE:
+            case PlainValue.Type.COMMENT:
+              continue next;
+            case PlainValue.Type.MAP_VALUE:
+              break next;
+            default:
+              {
+                const msg = 'Implicit map keys need to be followed by map values';
+                doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
+                break next;
+              }
+          }
+        }
+        if (item.valueRangeContainsNewline) {
+          const msg = 'Implicit map keys need to be on a single line';
+          doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
+        }
+    }
+  }
+  if (key !== undefined) items.push(new Pair(key));
+  return {
+    comments,
+    items
+  };
+}
+function resolveFlowMapItems(doc, cst) {
+  const comments = [];
+  const items = [];
+  let key = undefined;
+  let explicitKey = false;
+  let next = '{';
+  for (let i = 0; i < cst.items.length; ++i) {
+    const item = cst.items[i];
+    if (typeof item.char === 'string') {
+      const {
+        char,
+        offset
+      } = item;
+      if (char === '?' && key === undefined && !explicitKey) {
+        explicitKey = true;
+        next = ':';
+        continue;
+      }
+      if (char === ':') {
+        if (key === undefined) key = null;
+        if (next === ':') {
+          next = ',';
+          continue;
+        }
+      } else {
+        if (explicitKey) {
+          if (key === undefined && char !== ',') key = null;
+          explicitKey = false;
+        }
+        if (key !== undefined) {
+          items.push(new Pair(key));
+          key = undefined;
+          if (char === ',') {
+            next = ':';
+            continue;
+          }
+        }
+      }
+      if (char === '}') {
+        if (i === cst.items.length - 1) continue;
+      } else if (char === next) {
+        next = ':';
+        continue;
+      }
+      const msg = `Flow map contains an unexpected ${char}`;
+      const err = new PlainValue.YAMLSyntaxError(cst, msg);
+      err.offset = offset;
+      doc.errors.push(err);
+    } else if (item.type === PlainValue.Type.BLANK_LINE) {
+      comments.push({
+        afterKey: !!key,
+        before: items.length
+      });
+    } else if (item.type === PlainValue.Type.COMMENT) {
+      checkFlowCommentSpace(doc.errors, item);
+      comments.push({
+        afterKey: !!key,
+        before: items.length,
+        comment: item.comment
+      });
+    } else if (key === undefined) {
+      if (next === ',') doc.errors.push(new PlainValue.YAMLSemanticError(item, 'Separator , missing in flow map'));
+      key = resolveNode(doc, item);
+    } else {
+      if (next !== ',') doc.errors.push(new PlainValue.YAMLSemanticError(item, 'Indicator : missing in flow map entry'));
+      items.push(new Pair(key, resolveNode(doc, item)));
+      key = undefined;
+      explicitKey = false;
+    }
+  }
+  checkFlowCollectionEnd(doc.errors, cst);
+  if (key !== undefined) items.push(new Pair(key));
+  return {
+    comments,
+    items
+  };
+}
+
+function resolveSeq(doc, cst) {
+  if (cst.type !== PlainValue.Type.SEQ && cst.type !== PlainValue.Type.FLOW_SEQ) {
+    const msg = `A ${cst.type} node cannot be resolved as a sequence`;
+    doc.errors.push(new PlainValue.YAMLSyntaxError(cst, msg));
+    return null;
+  }
+  const {
+    comments,
+    items
+  } = cst.type === PlainValue.Type.FLOW_SEQ ? resolveFlowSeqItems(doc, cst) : resolveBlockSeqItems(doc, cst);
+  const seq = new YAMLSeq();
+  seq.items = items;
+  resolveComments(seq, comments);
+  if (!doc.options.mapAsMap && items.some(it => it instanceof Pair && it.key instanceof Collection)) {
+    const warn = 'Keys with collection values will be stringified as YAML due to JS Object restrictions. Use mapAsMap: true to avoid this.';
+    doc.warnings.push(new PlainValue.YAMLWarning(cst, warn));
+  }
+  cst.resolved = seq;
+  return seq;
+}
+function resolveBlockSeqItems(doc, cst) {
+  const comments = [];
+  const items = [];
+  for (let i = 0; i < cst.items.length; ++i) {
+    const item = cst.items[i];
+    switch (item.type) {
+      case PlainValue.Type.BLANK_LINE:
+        comments.push({
+          before: items.length
+        });
+        break;
+      case PlainValue.Type.COMMENT:
+        comments.push({
+          comment: item.comment,
+          before: items.length
+        });
+        break;
+      case PlainValue.Type.SEQ_ITEM:
+        if (item.error) doc.errors.push(item.error);
+        items.push(resolveNode(doc, item.node));
+        if (item.hasProps) {
+          const msg = 'Sequence items cannot have tags or anchors before the - indicator';
+          doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
+        }
+        break;
+      default:
+        if (item.error) doc.errors.push(item.error);
+        doc.errors.push(new PlainValue.YAMLSyntaxError(item, `Unexpected ${item.type} node in sequence`));
+    }
+  }
+  return {
+    comments,
+    items
+  };
+}
+function resolveFlowSeqItems(doc, cst) {
+  const comments = [];
+  const items = [];
+  let explicitKey = false;
+  let key = undefined;
+  let keyStart = null;
+  let next = '[';
+  let prevItem = null;
+  for (let i = 0; i < cst.items.length; ++i) {
+    const item = cst.items[i];
+    if (typeof item.char === 'string') {
+      const {
+        char,
+        offset
+      } = item;
+      if (char !== ':' && (explicitKey || key !== undefined)) {
+        if (explicitKey && key === undefined) key = next ? items.pop() : null;
+        items.push(new Pair(key));
+        explicitKey = false;
+        key = undefined;
+        keyStart = null;
+      }
+      if (char === next) {
+        next = null;
+      } else if (!next && char === '?') {
+        explicitKey = true;
+      } else if (next !== '[' && char === ':' && key === undefined) {
+        if (next === ',') {
+          key = items.pop();
+          if (key instanceof Pair) {
+            const msg = 'Chaining flow sequence pairs is invalid';
+            const err = new PlainValue.YAMLSemanticError(cst, msg);
+            err.offset = offset;
+            doc.errors.push(err);
+          }
+          if (!explicitKey && typeof keyStart === 'number') {
+            const keyEnd = item.range ? item.range.start : item.offset;
+            if (keyEnd > keyStart + 1024) doc.errors.push(getLongKeyError(cst, key));
+            const {
+              src
+            } = prevItem.context;
+            for (let i = keyStart; i < keyEnd; ++i) if (src[i] === '\n') {
+              const msg = 'Implicit keys of flow sequence pairs need to be on a single line';
+              doc.errors.push(new PlainValue.YAMLSemanticError(prevItem, msg));
+              break;
+            }
+          }
+        } else {
+          key = null;
+        }
+        keyStart = null;
+        explicitKey = false;
+        next = null;
+      } else if (next === '[' || char !== ']' || i < cst.items.length - 1) {
+        const msg = `Flow sequence contains an unexpected ${char}`;
+        const err = new PlainValue.YAMLSyntaxError(cst, msg);
+        err.offset = offset;
+        doc.errors.push(err);
+      }
+    } else if (item.type === PlainValue.Type.BLANK_LINE) {
+      comments.push({
+        before: items.length
+      });
+    } else if (item.type === PlainValue.Type.COMMENT) {
+      checkFlowCommentSpace(doc.errors, item);
+      comments.push({
+        comment: item.comment,
+        before: items.length
+      });
+    } else {
+      if (next) {
+        const msg = `Expected a ${next} in flow sequence`;
+        doc.errors.push(new PlainValue.YAMLSemanticError(item, msg));
+      }
+      const value = resolveNode(doc, item);
+      if (key === undefined) {
+        items.push(value);
+        prevItem = item;
+      } else {
+        items.push(new Pair(key, value));
+        key = undefined;
+      }
+      keyStart = item.range.start;
+      next = ',';
+    }
+  }
+  checkFlowCollectionEnd(doc.errors, cst);
+  if (key !== undefined) items.push(new Pair(key));
+  return {
+    comments,
+    items
+  };
+}
+
+exports.Alias = Alias;
+exports.Collection = Collection;
+exports.Merge = Merge;
+exports.Node = Node;
+exports.Pair = Pair;
+exports.Scalar = Scalar;
+exports.YAMLMap = YAMLMap;
+exports.YAMLSeq = YAMLSeq;
+exports.addComment = addComment;
+exports.binaryOptions = binaryOptions;
+exports.boolOptions = boolOptions;
+exports.findPair = findPair;
+exports.intOptions = intOptions;
+exports.isEmptyPath = isEmptyPath;
+exports.nullOptions = nullOptions;
+exports.resolveMap = resolveMap;
+exports.resolveNode = resolveNode;
+exports.resolveSeq = resolveSeq;
+exports.resolveString = resolveString;
+exports.strOptions = strOptions;
+exports.stringifyNumber = stringifyNumber;
+exports.stringifyString = stringifyString;
+exports.toJSON = toJSON;
Index: frontend/node_modules/yaml/dist/test-events.js
===================================================================
--- frontend/node_modules/yaml/dist/test-events.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/dist/test-events.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,146 @@
+'use strict';
+
+var parseCst = require('./parse-cst.js');
+var Document = require('./Document-a8d0fbf9.js');
+require('./PlainValue-516d5bc2.js');
+require('./resolveSeq-95613e94.js');
+require('./Schema-bcc6c2d7.js');
+require('./warnings-793925ce.js');
+
+// test harness for yaml-test-suite event tests
+function testEvents(src, options) {
+  const opt = Object.assign({
+    keepCstNodes: true,
+    keepNodeTypes: true,
+    version: '1.2'
+  }, options);
+  const docs = parseCst.parse(src).map(cstDoc => new Document.Document(opt).parse(cstDoc));
+  const errDoc = docs.find(doc => doc.errors.length > 0);
+  const error = errDoc ? errDoc.errors[0].message : null;
+  const events = ['+STR'];
+  try {
+    for (let i = 0; i < docs.length; ++i) {
+      const doc = docs[i];
+      let root = doc.contents;
+      if (Array.isArray(root)) root = root[0];
+      const [rootStart, rootEnd] = doc.range || [0, 0];
+      let e = doc.errors[0] && doc.errors[0].source;
+      if (e && e.type === 'SEQ_ITEM') e = e.node;
+      if (e && (e.type === 'DOCUMENT' || e.range.start < rootStart)) throw new Error();
+      let docStart = '+DOC';
+      const pre = src.slice(0, rootStart);
+      const explicitDoc = /---\s*$/.test(pre);
+      if (explicitDoc) docStart += ' ---';else if (!doc.contents) continue;
+      events.push(docStart);
+      addEvents(events, doc, e, root);
+      if (doc.contents && doc.contents.length > 1) throw new Error();
+      let docEnd = '-DOC';
+      if (rootEnd) {
+        const post = src.slice(rootEnd);
+        if (/^\.\.\./.test(post)) docEnd += ' ...';
+      }
+      events.push(docEnd);
+    }
+  } catch (e) {
+    return {
+      events,
+      error: error || e
+    };
+  }
+  events.push('-STR');
+  return {
+    events,
+    error
+  };
+}
+function addEvents(events, doc, e, node) {
+  if (!node) {
+    events.push('=VAL :');
+    return;
+  }
+  if (e && node.cstNode === e) throw new Error();
+  let props = '';
+  let anchor = doc.anchors.getName(node);
+  if (anchor) {
+    if (/\d$/.test(anchor)) {
+      const alt = anchor.replace(/\d$/, '');
+      if (doc.anchors.getNode(alt)) anchor = alt;
+    }
+    props = ` &${anchor}`;
+  }
+  if (node.cstNode && node.cstNode.tag) {
+    const {
+      handle,
+      suffix
+    } = node.cstNode.tag;
+    props += handle === '!' && !suffix ? ' <!>' : ` <${node.tag}>`;
+  }
+  let scalar = null;
+  switch (node.type) {
+    case 'ALIAS':
+      {
+        let alias = doc.anchors.getName(node.source);
+        if (/\d$/.test(alias)) {
+          const alt = alias.replace(/\d$/, '');
+          if (doc.anchors.getNode(alt)) alias = alt;
+        }
+        events.push(`=ALI${props} *${alias}`);
+      }
+      break;
+    case 'BLOCK_FOLDED':
+      scalar = '>';
+      break;
+    case 'BLOCK_LITERAL':
+      scalar = '|';
+      break;
+    case 'PLAIN':
+      scalar = ':';
+      break;
+    case 'QUOTE_DOUBLE':
+      scalar = '"';
+      break;
+    case 'QUOTE_SINGLE':
+      scalar = "'";
+      break;
+    case 'PAIR':
+      events.push(`+MAP${props}`);
+      addEvents(events, doc, e, node.key);
+      addEvents(events, doc, e, node.value);
+      events.push('-MAP');
+      break;
+    case 'FLOW_SEQ':
+    case 'SEQ':
+      {
+        const ev = node.type === 'FLOW_SEQ' ? '+SEQ []' : '+SEQ';
+        events.push(`${ev}${props}`);
+        node.items.forEach(item => {
+          addEvents(events, doc, e, item);
+        });
+        events.push('-SEQ');
+        break;
+      }
+    case 'FLOW_MAP':
+    case 'MAP':
+      {
+        const ev = node.type === 'FLOW_SEQ' ? '+MAP {}' : '+MAP';
+        events.push(`${ev}${props}`);
+        node.items.forEach(({
+          key,
+          value
+        }) => {
+          addEvents(events, doc, e, key);
+          addEvents(events, doc, e, value);
+        });
+        events.push('-MAP');
+        break;
+      }
+    default:
+      throw new Error(`Unexpected node type ${node.type}`);
+  }
+  if (scalar) {
+    const value = node.cstNode.strValue.replace(/\\/g, '\\\\').replace(/\0/g, '\\0').replace(/\x07/g, '\\a').replace(/\x08/g, '\\b').replace(/\t/g, '\\t').replace(/\n/g, '\\n').replace(/\v/g, '\\v').replace(/\f/g, '\\f').replace(/\r/g, '\\r').replace(/\x1b/g, '\\e');
+    events.push(`=VAL${props} ${scalar}${value}`);
+  }
+}
+
+exports.testEvents = testEvents;
Index: frontend/node_modules/yaml/dist/types.js
===================================================================
--- frontend/node_modules/yaml/dist/types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/dist/types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,23 @@
+'use strict';
+
+var resolveSeq = require('./resolveSeq-95613e94.js');
+var Schema = require('./Schema-bcc6c2d7.js');
+require('./PlainValue-516d5bc2.js');
+require('./warnings-793925ce.js');
+
+
+
+exports.Alias = resolveSeq.Alias;
+exports.Collection = resolveSeq.Collection;
+exports.Merge = resolveSeq.Merge;
+exports.Node = resolveSeq.Node;
+exports.Pair = resolveSeq.Pair;
+exports.Scalar = resolveSeq.Scalar;
+exports.YAMLMap = resolveSeq.YAMLMap;
+exports.YAMLSeq = resolveSeq.YAMLSeq;
+exports.binaryOptions = resolveSeq.binaryOptions;
+exports.boolOptions = resolveSeq.boolOptions;
+exports.intOptions = resolveSeq.intOptions;
+exports.nullOptions = resolveSeq.nullOptions;
+exports.strOptions = resolveSeq.strOptions;
+exports.Schema = Schema.Schema;
Index: frontend/node_modules/yaml/dist/util.js
===================================================================
--- frontend/node_modules/yaml/dist/util.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/dist/util.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,19 @@
+'use strict';
+
+var resolveSeq = require('./resolveSeq-95613e94.js');
+var PlainValue = require('./PlainValue-516d5bc2.js');
+
+
+
+exports.findPair = resolveSeq.findPair;
+exports.parseMap = resolveSeq.resolveMap;
+exports.parseSeq = resolveSeq.resolveSeq;
+exports.stringifyNumber = resolveSeq.stringifyNumber;
+exports.stringifyString = resolveSeq.stringifyString;
+exports.toJSON = resolveSeq.toJSON;
+exports.Type = PlainValue.Type;
+exports.YAMLError = PlainValue.YAMLError;
+exports.YAMLReferenceError = PlainValue.YAMLReferenceError;
+exports.YAMLSemanticError = PlainValue.YAMLSemanticError;
+exports.YAMLSyntaxError = PlainValue.YAMLSyntaxError;
+exports.YAMLWarning = PlainValue.YAMLWarning;
Index: frontend/node_modules/yaml/dist/warnings-793925ce.js
===================================================================
--- frontend/node_modules/yaml/dist/warnings-793925ce.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/dist/warnings-793925ce.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,359 @@
+'use strict';
+
+var PlainValue = require('./PlainValue-516d5bc2.js');
+var resolveSeq = require('./resolveSeq-95613e94.js');
+
+/* global atob, btoa, Buffer */
+const binary = {
+  identify: value => value instanceof Uint8Array,
+  // Buffer inherits from Uint8Array
+  default: false,
+  tag: 'tag:yaml.org,2002:binary',
+  /**
+   * Returns a Buffer in node and an Uint8Array in browsers
+   *
+   * To use the resulting buffer as an image, you'll want to do something like:
+   *
+   *   const blob = new Blob([buffer], { type: 'image/jpeg' })
+   *   document.querySelector('#photo').src = URL.createObjectURL(blob)
+   */
+  resolve: (doc, node) => {
+    const src = resolveSeq.resolveString(doc, node);
+    if (typeof Buffer === 'function') {
+      return Buffer.from(src, 'base64');
+    } else if (typeof atob === 'function') {
+      // On IE 11, atob() can't handle newlines
+      const str = atob(src.replace(/[\n\r]/g, ''));
+      const buffer = new Uint8Array(str.length);
+      for (let i = 0; i < str.length; ++i) buffer[i] = str.charCodeAt(i);
+      return buffer;
+    } else {
+      const msg = 'This environment does not support reading binary tags; either Buffer or atob is required';
+      doc.errors.push(new PlainValue.YAMLReferenceError(node, msg));
+      return null;
+    }
+  },
+  options: resolveSeq.binaryOptions,
+  stringify: ({
+    comment,
+    type,
+    value
+  }, ctx, onComment, onChompKeep) => {
+    let src;
+    if (typeof Buffer === 'function') {
+      src = value instanceof Buffer ? value.toString('base64') : Buffer.from(value.buffer).toString('base64');
+    } else if (typeof btoa === 'function') {
+      let s = '';
+      for (let i = 0; i < value.length; ++i) s += String.fromCharCode(value[i]);
+      src = btoa(s);
+    } else {
+      throw new Error('This environment does not support writing binary tags; either Buffer or btoa is required');
+    }
+    if (!type) type = resolveSeq.binaryOptions.defaultType;
+    if (type === PlainValue.Type.QUOTE_DOUBLE) {
+      value = src;
+    } else {
+      const {
+        lineWidth
+      } = resolveSeq.binaryOptions;
+      const n = Math.ceil(src.length / lineWidth);
+      const lines = new Array(n);
+      for (let i = 0, o = 0; i < n; ++i, o += lineWidth) {
+        lines[i] = src.substr(o, lineWidth);
+      }
+      value = lines.join(type === PlainValue.Type.BLOCK_LITERAL ? '\n' : ' ');
+    }
+    return resolveSeq.stringifyString({
+      comment,
+      type,
+      value
+    }, ctx, onComment, onChompKeep);
+  }
+};
+
+function parsePairs(doc, cst) {
+  const seq = resolveSeq.resolveSeq(doc, cst);
+  for (let i = 0; i < seq.items.length; ++i) {
+    let item = seq.items[i];
+    if (item instanceof resolveSeq.Pair) continue;else if (item instanceof resolveSeq.YAMLMap) {
+      if (item.items.length > 1) {
+        const msg = 'Each pair must have its own sequence indicator';
+        throw new PlainValue.YAMLSemanticError(cst, msg);
+      }
+      const pair = item.items[0] || new resolveSeq.Pair();
+      if (item.commentBefore) pair.commentBefore = pair.commentBefore ? `${item.commentBefore}\n${pair.commentBefore}` : item.commentBefore;
+      if (item.comment) pair.comment = pair.comment ? `${item.comment}\n${pair.comment}` : item.comment;
+      item = pair;
+    }
+    seq.items[i] = item instanceof resolveSeq.Pair ? item : new resolveSeq.Pair(item);
+  }
+  return seq;
+}
+function createPairs(schema, iterable, ctx) {
+  const pairs = new resolveSeq.YAMLSeq(schema);
+  pairs.tag = 'tag:yaml.org,2002:pairs';
+  for (const it of iterable) {
+    let key, value;
+    if (Array.isArray(it)) {
+      if (it.length === 2) {
+        key = it[0];
+        value = it[1];
+      } else throw new TypeError(`Expected [key, value] tuple: ${it}`);
+    } else if (it && it instanceof Object) {
+      const keys = Object.keys(it);
+      if (keys.length === 1) {
+        key = keys[0];
+        value = it[key];
+      } else throw new TypeError(`Expected { key: value } tuple: ${it}`);
+    } else {
+      key = it;
+    }
+    const pair = schema.createPair(key, value, ctx);
+    pairs.items.push(pair);
+  }
+  return pairs;
+}
+const pairs = {
+  default: false,
+  tag: 'tag:yaml.org,2002:pairs',
+  resolve: parsePairs,
+  createNode: createPairs
+};
+
+class YAMLOMap extends resolveSeq.YAMLSeq {
+  constructor() {
+    super();
+    PlainValue._defineProperty(this, "add", resolveSeq.YAMLMap.prototype.add.bind(this));
+    PlainValue._defineProperty(this, "delete", resolveSeq.YAMLMap.prototype.delete.bind(this));
+    PlainValue._defineProperty(this, "get", resolveSeq.YAMLMap.prototype.get.bind(this));
+    PlainValue._defineProperty(this, "has", resolveSeq.YAMLMap.prototype.has.bind(this));
+    PlainValue._defineProperty(this, "set", resolveSeq.YAMLMap.prototype.set.bind(this));
+    this.tag = YAMLOMap.tag;
+  }
+  toJSON(_, ctx) {
+    const map = new Map();
+    if (ctx && ctx.onCreate) ctx.onCreate(map);
+    for (const pair of this.items) {
+      let key, value;
+      if (pair instanceof resolveSeq.Pair) {
+        key = resolveSeq.toJSON(pair.key, '', ctx);
+        value = resolveSeq.toJSON(pair.value, key, ctx);
+      } else {
+        key = resolveSeq.toJSON(pair, '', ctx);
+      }
+      if (map.has(key)) throw new Error('Ordered maps must not include duplicate keys');
+      map.set(key, value);
+    }
+    return map;
+  }
+}
+PlainValue._defineProperty(YAMLOMap, "tag", 'tag:yaml.org,2002:omap');
+function parseOMap(doc, cst) {
+  const pairs = parsePairs(doc, cst);
+  const seenKeys = [];
+  for (const {
+    key
+  } of pairs.items) {
+    if (key instanceof resolveSeq.Scalar) {
+      if (seenKeys.includes(key.value)) {
+        const msg = 'Ordered maps must not include duplicate keys';
+        throw new PlainValue.YAMLSemanticError(cst, msg);
+      } else {
+        seenKeys.push(key.value);
+      }
+    }
+  }
+  return Object.assign(new YAMLOMap(), pairs);
+}
+function createOMap(schema, iterable, ctx) {
+  const pairs = createPairs(schema, iterable, ctx);
+  const omap = new YAMLOMap();
+  omap.items = pairs.items;
+  return omap;
+}
+const omap = {
+  identify: value => value instanceof Map,
+  nodeClass: YAMLOMap,
+  default: false,
+  tag: 'tag:yaml.org,2002:omap',
+  resolve: parseOMap,
+  createNode: createOMap
+};
+
+class YAMLSet extends resolveSeq.YAMLMap {
+  constructor() {
+    super();
+    this.tag = YAMLSet.tag;
+  }
+  add(key) {
+    const pair = key instanceof resolveSeq.Pair ? key : new resolveSeq.Pair(key);
+    const prev = resolveSeq.findPair(this.items, pair.key);
+    if (!prev) this.items.push(pair);
+  }
+  get(key, keepPair) {
+    const pair = resolveSeq.findPair(this.items, key);
+    return !keepPair && pair instanceof resolveSeq.Pair ? pair.key instanceof resolveSeq.Scalar ? pair.key.value : pair.key : pair;
+  }
+  set(key, value) {
+    if (typeof value !== 'boolean') throw new Error(`Expected boolean value for set(key, value) in a YAML set, not ${typeof value}`);
+    const prev = resolveSeq.findPair(this.items, key);
+    if (prev && !value) {
+      this.items.splice(this.items.indexOf(prev), 1);
+    } else if (!prev && value) {
+      this.items.push(new resolveSeq.Pair(key));
+    }
+  }
+  toJSON(_, ctx) {
+    return super.toJSON(_, ctx, Set);
+  }
+  toString(ctx, onComment, onChompKeep) {
+    if (!ctx) return JSON.stringify(this);
+    if (this.hasAllNullValues()) return super.toString(ctx, onComment, onChompKeep);else throw new Error('Set items must all have null values');
+  }
+}
+PlainValue._defineProperty(YAMLSet, "tag", 'tag:yaml.org,2002:set');
+function parseSet(doc, cst) {
+  const map = resolveSeq.resolveMap(doc, cst);
+  if (!map.hasAllNullValues()) throw new PlainValue.YAMLSemanticError(cst, 'Set items must all have null values');
+  return Object.assign(new YAMLSet(), map);
+}
+function createSet(schema, iterable, ctx) {
+  const set = new YAMLSet();
+  for (const value of iterable) set.items.push(schema.createPair(value, null, ctx));
+  return set;
+}
+const set = {
+  identify: value => value instanceof Set,
+  nodeClass: YAMLSet,
+  default: false,
+  tag: 'tag:yaml.org,2002:set',
+  resolve: parseSet,
+  createNode: createSet
+};
+
+const parseSexagesimal = (sign, parts) => {
+  const n = parts.split(':').reduce((n, p) => n * 60 + Number(p), 0);
+  return sign === '-' ? -n : n;
+};
+
+// hhhh:mm:ss.sss
+const stringifySexagesimal = ({
+  value
+}) => {
+  if (isNaN(value) || !isFinite(value)) return resolveSeq.stringifyNumber(value);
+  let sign = '';
+  if (value < 0) {
+    sign = '-';
+    value = Math.abs(value);
+  }
+  const parts = [value % 60]; // seconds, including ms
+  if (value < 60) {
+    parts.unshift(0); // at least one : is required
+  } else {
+    value = Math.round((value - parts[0]) / 60);
+    parts.unshift(value % 60); // minutes
+    if (value >= 60) {
+      value = Math.round((value - parts[0]) / 60);
+      parts.unshift(value); // hours
+    }
+  }
+  return sign + parts.map(n => n < 10 ? '0' + String(n) : String(n)).join(':').replace(/000000\d*$/, '') // % 60 may introduce error
+;
+};
+const intTime = {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:int',
+  format: 'TIME',
+  test: /^([-+]?)([0-9][0-9_]*(?::[0-5]?[0-9])+)$/,
+  resolve: (str, sign, parts) => parseSexagesimal(sign, parts.replace(/_/g, '')),
+  stringify: stringifySexagesimal
+};
+const floatTime = {
+  identify: value => typeof value === 'number',
+  default: true,
+  tag: 'tag:yaml.org,2002:float',
+  format: 'TIME',
+  test: /^([-+]?)([0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*)$/,
+  resolve: (str, sign, parts) => parseSexagesimal(sign, parts.replace(/_/g, '')),
+  stringify: stringifySexagesimal
+};
+const timestamp = {
+  identify: value => value instanceof Date,
+  default: true,
+  tag: 'tag:yaml.org,2002:timestamp',
+  // If the time zone is omitted, the timestamp is assumed to be specified in UTC. The time part
+  // may be omitted altogether, resulting in a date format. In such a case, the time part is
+  // assumed to be 00:00:00Z (start of day, UTC).
+  test: RegExp('^(?:' + '([0-9]{4})-([0-9]{1,2})-([0-9]{1,2})' +
+  // YYYY-Mm-Dd
+  '(?:(?:t|T|[ \\t]+)' +
+  // t | T | whitespace
+  '([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)' +
+  // Hh:Mm:Ss(.ss)?
+  '(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?' +
+  // Z | +5 | -03:30
+  ')?' + ')$'),
+  resolve: (str, year, month, day, hour, minute, second, millisec, tz) => {
+    if (millisec) millisec = (millisec + '00').substr(1, 3);
+    let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec || 0);
+    if (tz && tz !== 'Z') {
+      let d = parseSexagesimal(tz[0], tz.slice(1));
+      if (Math.abs(d) < 30) d *= 60;
+      date -= 60000 * d;
+    }
+    return new Date(date);
+  },
+  stringify: ({
+    value
+  }) => value.toISOString().replace(/((T00:00)?:00)?\.000Z$/, '')
+};
+
+/* global console, process, YAML_SILENCE_DEPRECATION_WARNINGS, YAML_SILENCE_WARNINGS */
+
+function shouldWarn(deprecation) {
+  const env = typeof process !== 'undefined' && process.env || {};
+  if (deprecation) {
+    if (typeof YAML_SILENCE_DEPRECATION_WARNINGS !== 'undefined') return !YAML_SILENCE_DEPRECATION_WARNINGS;
+    return !env.YAML_SILENCE_DEPRECATION_WARNINGS;
+  }
+  if (typeof YAML_SILENCE_WARNINGS !== 'undefined') return !YAML_SILENCE_WARNINGS;
+  return !env.YAML_SILENCE_WARNINGS;
+}
+function warn(warning, type) {
+  if (shouldWarn(false)) {
+    const emit = typeof process !== 'undefined' && process.emitWarning;
+    // This will throw in Jest if `warning` is an Error instance due to
+    // https://github.com/facebook/jest/issues/2549
+    if (emit) emit(warning, type);else {
+      // eslint-disable-next-line no-console
+      console.warn(type ? `${type}: ${warning}` : warning);
+    }
+  }
+}
+function warnFileDeprecation(filename) {
+  if (shouldWarn(true)) {
+    const path = filename.replace(/.*yaml[/\\]/i, '').replace(/\.js$/, '').replace(/\\/g, '/');
+    warn(`The endpoint 'yaml/${path}' will be removed in a future release.`, 'DeprecationWarning');
+  }
+}
+const warned = {};
+function warnOptionDeprecation(name, alternative) {
+  if (!warned[name] && shouldWarn(true)) {
+    warned[name] = true;
+    let msg = `The option '${name}' will be removed in a future release`;
+    msg += alternative ? `, use '${alternative}' instead.` : '.';
+    warn(msg, 'DeprecationWarning');
+  }
+}
+
+exports.binary = binary;
+exports.floatTime = floatTime;
+exports.intTime = intTime;
+exports.omap = omap;
+exports.pairs = pairs;
+exports.set = set;
+exports.timestamp = timestamp;
+exports.warn = warn;
+exports.warnFileDeprecation = warnFileDeprecation;
+exports.warnOptionDeprecation = warnOptionDeprecation;
Index: frontend/node_modules/yaml/index.d.ts
===================================================================
--- frontend/node_modules/yaml/index.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/index.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,372 @@
+import { CST } from './parse-cst'
+import {
+  AST,
+  Alias,
+  Collection,
+  Merge,
+  Node,
+  Scalar,
+  Schema,
+  YAMLMap,
+  YAMLSeq
+} from './types'
+import { Type, YAMLError, YAMLWarning } from './util'
+
+export { AST, CST }
+export { default as parseCST } from './parse-cst'
+
+/**
+ * `yaml` defines document-specific options in three places: as an argument of
+ * parse, create and stringify calls, in the values of `YAML.defaultOptions`,
+ * and in the version-dependent `YAML.Document.defaults` object. Values set in
+ * `YAML.defaultOptions` override version-dependent defaults, and argument
+ * options override both.
+ */
+export const defaultOptions: Options
+
+export interface Options extends Schema.Options {
+  /**
+   * Default prefix for anchors.
+   *
+   * Default: `'a'`, resulting in anchors `a1`, `a2`, etc.
+   */
+  anchorPrefix?: string
+  /**
+   * The number of spaces to use when indenting code.
+   *
+   * Default: `2`
+   */
+  indent?: number
+  /**
+   * Whether block sequences should be indented.
+   *
+   * Default: `true`
+   */
+  indentSeq?: boolean
+  /**
+   * Allow non-JSON JavaScript objects to remain in the `toJSON` output.
+   * Relevant with the YAML 1.1 `!!timestamp` and `!!binary` tags as well as BigInts.
+   *
+   * Default: `true`
+   */
+  keepBlobsInJSON?: boolean
+  /**
+   * Include references in the AST to each node's corresponding CST node.
+   *
+   * Default: `false`
+   */
+  keepCstNodes?: boolean
+  /**
+   * Store the original node type when parsing documents.
+   *
+   * Default: `true`
+   */
+  keepNodeTypes?: boolean
+  /**
+   * When outputting JS, use Map rather than Object to represent mappings.
+   *
+   * Default: `false`
+   */
+  mapAsMap?: boolean
+  /**
+   * Prevent exponential entity expansion attacks by limiting data aliasing count;
+   * set to `-1` to disable checks; `0` disallows all alias nodes.
+   *
+   * Default: `100`
+   */
+  maxAliasCount?: number
+  /**
+   * Include line position & node type directly in errors; drop their verbose source and context.
+   *
+   * Default: `false`
+   */
+  prettyErrors?: boolean
+  /**
+   * When stringifying, require keys to be scalars and to use implicit rather than explicit notation.
+   *
+   * Default: `false`
+   */
+  simpleKeys?: boolean
+  /**
+   * The YAML version used by documents without a `%YAML` directive.
+   *
+   * Default: `"1.2"`
+   */
+  version?: '1.0' | '1.1' | '1.2'
+}
+
+/**
+ * Some customization options are availabe to control the parsing and
+ * stringification of scalars. Note that these values are used by all documents.
+ */
+export const scalarOptions: {
+  binary: scalarOptions.Binary
+  bool: scalarOptions.Bool
+  int: scalarOptions.Int
+  null: scalarOptions.Null
+  str: scalarOptions.Str
+}
+export namespace scalarOptions {
+  interface Binary {
+    /**
+     * The type of string literal used to stringify `!!binary` values.
+     *
+     * Default: `'BLOCK_LITERAL'`
+     */
+    defaultType: Scalar.Type
+    /**
+     * Maximum line width for `!!binary`.
+     *
+     * Default: `76`
+     */
+    lineWidth: number
+  }
+
+  interface Bool {
+    /**
+     * String representation for `true`. With the core schema, use `'true' | 'True' | 'TRUE'`.
+     *
+     * Default: `'true'`
+     */
+    trueStr: string
+    /**
+     * String representation for `false`. With the core schema, use `'false' | 'False' | 'FALSE'`.
+     *
+     * Default: `'false'`
+     */
+    falseStr: string
+  }
+
+  interface Int {
+    /**
+     * Whether integers should be parsed into BigInt values.
+     *
+     * Default: `false`
+     */
+    asBigInt: boolean
+  }
+
+  interface Null {
+    /**
+     * String representation for `null`. With the core schema, use `'null' | 'Null' | 'NULL' | '~' | ''`.
+     *
+     * Default: `'null'`
+     */
+    nullStr: string
+  }
+
+  interface Str {
+    /**
+     * The default type of string literal used to stringify values
+     *
+     * Default: `'PLAIN'`
+     */
+    defaultType: Scalar.Type
+    doubleQuoted: {
+      /**
+       * Whether to restrict double-quoted strings to use JSON-compatible syntax.
+       *
+       * Default: `false`
+       */
+      jsonEncoding: boolean
+      /**
+       * Minimum length to use multiple lines to represent the value.
+       *
+       * Default: `40`
+       */
+      minMultiLineLength: number
+    }
+    fold: {
+      /**
+       * Maximum line width (set to `0` to disable folding).
+       *
+       * Default: `80`
+       */
+      lineWidth: number
+      /**
+       * Minimum width for highly-indented content.
+       *
+       * Default: `20`
+       */
+      minContentWidth: number
+    }
+  }
+}
+
+export class Document extends Collection {
+  cstNode?: CST.Document
+  constructor(options?: Options)
+  tag: never
+  directivesEndMarker?: boolean
+  type: Type.DOCUMENT
+  /**
+   * Anchors associated with the document's nodes;
+   * also provides alias & merge node creators.
+   */
+  anchors: Document.Anchors
+  /** The document contents. */
+  contents: any
+  /** Errors encountered during parsing. */
+  errors: YAMLError[]
+  /**
+   * The schema used with the document. Use `setSchema()` to change or
+   * initialise.
+   */
+  schema?: Schema
+  /**
+   * Array of prefixes; each will have a string `handle` that
+   * starts and ends with `!` and a string `prefix` that the handle will be replaced by.
+   */
+  tagPrefixes: Document.TagPrefix[]
+  /**
+   * The parsed version of the source document;
+   * if true-ish, stringified output will include a `%YAML` directive.
+   */
+  version?: string
+  /** Warnings encountered during parsing. */
+  warnings: YAMLWarning[]
+  /**
+   * List the tags used in the document that are not in the default
+   * `tag:yaml.org,2002:` namespace.
+   */
+  listNonDefaultTags(): string[]
+  /** Parse a CST into this document */
+  parse(cst: CST.Document): this
+  /**
+   * When a document is created with `new YAML.Document()`, the schema object is
+   * not set as it may be influenced by parsed directives; call this with no
+   * arguments to set it manually, or with arguments to change the schema used
+   * by the document.
+   **/
+  setSchema(
+    id?: Options['version'] | Schema.Name,
+    customTags?: (Schema.TagId | Schema.Tag)[]
+  ): void
+  /** Set `handle` as a shorthand string for the `prefix` tag namespace. */
+  setTagPrefix(handle: string, prefix: string): void
+  /**
+   * A plain JavaScript representation of the document `contents`.
+   *
+   * @param arg Used by `JSON.stringify` to indicate the array index or property
+   *   name. If its value is a `string` and the document `contents` has a scalar
+   *   value, the `keepBlobsInJSON` option has no effect.
+   * @param onAnchor If defined, called with the resolved `value` and reference
+   *   `count` for each anchor in the document.
+   * */
+  toJSON(arg?: string, onAnchor?: (value: any, count: number) => void): any
+  /** A YAML representation of the document. */
+  toString(): string
+}
+
+export namespace Document {
+  interface Parsed extends Document {
+    contents: Scalar | YAMLMap | YAMLSeq | null
+    /** The schema used with the document. */
+    schema: Schema
+  }
+
+  interface Anchors {
+    /**
+     * Create a new `Alias` node, adding the required anchor for `node`.
+     * If `name` is empty, a new anchor name will be generated.
+     */
+    createAlias(node: Node, name?: string): Alias
+    /**
+     * Create a new `Merge` node with the given source nodes.
+     * Non-`Alias` sources will be automatically wrapped.
+     */
+    createMergePair(...nodes: Node[]): Merge
+    /** The anchor name associated with `node`, if set. */
+    getName(node: Node): undefined | string
+    /** List of all defined anchor names. */
+    getNames(): string[]
+    /** The node associated with the anchor `name`, if set. */
+    getNode(name: string): undefined | Node
+    /**
+     * Find an available anchor name with the given `prefix` and a
+     * numerical suffix.
+     */
+    newName(prefix: string): string
+    /**
+     * Associate an anchor with `node`. If `name` is empty, a new name will be generated.
+     * To remove an anchor, use `setAnchor(null, name)`.
+     */
+    setAnchor(node: Node | null, name?: string): void | string
+  }
+
+  interface TagPrefix {
+    handle: string
+    prefix: string
+  }
+}
+
+/**
+ * Recursively turns objects into collections. Generic objects as well as `Map`
+ * and its descendants become mappings, while arrays and other iterable objects
+ * result in sequences.
+ *
+ * The primary purpose of this function is to enable attaching comments or other
+ * metadata to a value, or to otherwise exert more fine-grained control over the
+ * stringified output. To that end, you'll need to assign its return value to
+ * the `contents` of a Document (or somewhere within said contents), as the
+ * document's schema is required for YAML string output.
+ *
+ * @param wrapScalars If undefined or `true`, also wraps plain values in
+ *   `Scalar` objects; if `false` and `value` is not an object, it will be
+ *   returned directly.
+ * @param tag Use to specify the collection type, e.g. `"!!omap"`. Note that
+ *   this requires the corresponding tag to be available based on the default
+ *   options. To use a specific document's schema, use `doc.schema.createNode`.
+ */
+export function createNode(
+  value: any,
+  wrapScalars?: true,
+  tag?: string
+): YAMLMap | YAMLSeq | Scalar
+
+/**
+ * YAML.createNode recursively turns objects into Map and arrays to Seq collections.
+ * Its primary use is to enable attaching comments or other metadata to a value,
+ * or to otherwise exert more fine-grained control over the stringified output.
+ *
+ * Doesn't wrap plain values in Scalar objects.
+ */
+export function createNode(
+  value: any,
+  wrapScalars: false,
+  tag?: string
+): YAMLMap | YAMLSeq | string | number | boolean | null
+
+/**
+ * Parse an input string into a single YAML.Document.
+ */
+export function parseDocument(str: string, options?: Options): Document.Parsed
+
+/**
+ * Parse the input as a stream of YAML documents.
+ *
+ * Documents should be separated from each other by `...` or `---` marker lines.
+ */
+export function parseAllDocuments(
+  str: string,
+  options?: Options
+): Document.Parsed[]
+
+/**
+ * Parse an input string into JavaScript.
+ *
+ * Only supports input consisting of a single YAML document; for multi-document
+ * support you should use `YAML.parseAllDocuments`. May throw on error, and may
+ * log warnings using `console.warn`.
+ *
+ * @param str A string with YAML formatting.
+ * @returns The value will match the type of the root value of the parsed YAML
+ *   document, so Maps become objects, Sequences arrays, and scalars result in
+ *   nulls, booleans, numbers and strings.
+ */
+export function parse(str: string, options?: Options): any
+
+/**
+ * @returns Will always include \n as the last character, as is expected of YAML documents.
+ */
+export function stringify(value: any, options?: Options): string
Index: frontend/node_modules/yaml/index.js
===================================================================
--- frontend/node_modules/yaml/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+module.exports = require('./dist').YAML
Index: frontend/node_modules/yaml/map.js
===================================================================
--- frontend/node_modules/yaml/map.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/map.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2 @@
+module.exports = require('./dist/types').YAMLMap
+require('./dist/legacy-exports').warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/package.json
===================================================================
--- frontend/node_modules/yaml/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/package.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,105 @@
+{
+  "name": "yaml",
+  "version": "1.10.3",
+  "license": "ISC",
+  "author": "Eemeli Aro <eemeli@gmail.com>",
+  "repository": "github:eemeli/yaml",
+  "description": "JavaScript parser and stringifier for YAML",
+  "keywords": [
+    "YAML",
+    "parser",
+    "stringifier"
+  ],
+  "homepage": "https://eemeli.org/yaml/v1/",
+  "files": [
+    "browser/",
+    "dist/",
+    "types/",
+    "*.d.ts",
+    "*.js",
+    "*.mjs",
+    "!*config.js"
+  ],
+  "type": "commonjs",
+  "main": "./index.js",
+  "browser": {
+    "./index.js": "./browser/index.js",
+    "./map.js": "./browser/map.js",
+    "./pair.js": "./browser/pair.js",
+    "./parse-cst.js": "./browser/parse-cst.js",
+    "./scalar.js": "./browser/scalar.js",
+    "./schema.js": "./browser/schema.js",
+    "./seq.js": "./browser/seq.js",
+    "./types.js": "./browser/types.js",
+    "./types.mjs": "./browser/types.js",
+    "./types/binary.js": "./browser/types/binary.js",
+    "./types/omap.js": "./browser/types/omap.js",
+    "./types/pairs.js": "./browser/types/pairs.js",
+    "./types/set.js": "./browser/types/set.js",
+    "./types/timestamp.js": "./browser/types/timestamp.js",
+    "./util.js": "./browser/util.js",
+    "./util.mjs": "./browser/util.js"
+  },
+  "exports": {
+    ".": "./index.js",
+    "./parse-cst": "./parse-cst.js",
+    "./types": [
+      {
+        "import": "./types.mjs"
+      },
+      "./types.js"
+    ],
+    "./util": [
+      {
+        "import": "./util.mjs"
+      },
+      "./util.js"
+    ],
+    "./": "./"
+  },
+  "scripts": {
+    "build": "npm run build:node && npm run build:browser",
+    "build:browser": "rollup -c rollup.browser-config.js",
+    "build:node": "rollup -c rollup.node-config.js",
+    "clean": "git clean -fdxe node_modules",
+    "lint": "eslint src/",
+    "prettier": "prettier --write .",
+    "start": "cross-env TRACE_LEVEL=log npm run build:node && node -i -e 'YAML=require(\".\")'",
+    "test": "jest",
+    "test:browsers": "cd playground && npm test",
+    "test:dist": "npm run build:node && jest",
+    "test:types": "tsc --lib ES2017 --noEmit tests/typings.ts",
+    "docs:install": "cd docs-slate && bundle install",
+    "docs:deploy": "cd docs-slate && ./deploy.sh",
+    "docs": "cd docs-slate && bundle exec middleman server",
+    "preversion": "npm test && npm run build"
+  },
+  "browserslist": "> 0.5%, not dead",
+  "prettier": {
+    "arrowParens": "avoid",
+    "semi": false,
+    "singleQuote": true,
+    "trailingComma": "none"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.12.10",
+    "@babel/plugin-proposal-class-properties": "^7.12.1",
+    "@babel/preset-env": "^7.12.11",
+    "@rollup/plugin-babel": "^5.2.3",
+    "babel-eslint": "^10.1.0",
+    "babel-jest": "^26.6.3",
+    "babel-plugin-trace": "^1.1.0",
+    "common-tags": "^1.8.0",
+    "cross-env": "^7.0.3",
+    "eslint": "^7.19.0",
+    "eslint-config-prettier": "^7.2.0",
+    "fast-check": "^2.12.0",
+    "jest": "^26.6.3",
+    "prettier": "^2.2.1",
+    "rollup": "^2.38.2",
+    "typescript": "^4.1.3"
+  },
+  "engines": {
+    "node": ">= 6"
+  }
+}
Index: frontend/node_modules/yaml/pair.js
===================================================================
--- frontend/node_modules/yaml/pair.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/pair.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2 @@
+module.exports = require('./dist/types').Pair
+require('./dist/legacy-exports').warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/parse-cst.d.ts
===================================================================
--- frontend/node_modules/yaml/parse-cst.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/parse-cst.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,191 @@
+import { Type, YAMLSyntaxError } from './util'
+
+export default function parseCST(str: string): ParsedCST
+
+export interface ParsedCST extends Array<CST.Document> {
+  setOrigRanges(): boolean
+}
+
+export namespace CST {
+  interface Range {
+    start: number
+    end: number
+    origStart?: number
+    origEnd?: number
+    isEmpty(): boolean
+  }
+
+  interface ParseContext {
+    /** Node starts at beginning of line */
+    atLineStart: boolean
+    /** true if currently in a collection context */
+    inCollection: boolean
+    /** true if currently in a flow context */
+    inFlow: boolean
+    /** Current level of indentation */
+    indent: number
+    /** Start of the current line */
+    lineStart: number
+    /** The parent of the node */
+    parent: Node
+    /** Source of the YAML document */
+    src: string
+  }
+
+  interface Node {
+    context: ParseContext | null
+    /** if not null, indicates a parser failure */
+    error: YAMLSyntaxError | null
+    /** span of context.src parsed into this node */
+    range: Range | null
+    valueRange: Range | null
+    /** anchors, tags and comments */
+    props: Range[]
+    /** specific node type */
+    type: Type
+    /** if non-null, overrides source value */
+    value: string | null
+
+    readonly anchor: string | null
+    readonly comment: string | null
+    readonly hasComment: boolean
+    readonly hasProps: boolean
+    readonly jsonLike: boolean
+    readonly rangeAsLinePos: null | {
+      start: { line: number; col: number }
+      end?: { line: number; col: number }
+    }
+    readonly rawValue: string | null
+    readonly tag:
+      | null
+      | { verbatim: string }
+      | { handle: string; suffix: string }
+    readonly valueRangeContainsNewline: boolean
+  }
+
+  interface Alias extends Node {
+    type: Type.ALIAS
+    /** contain the anchor without the * prefix */
+    readonly rawValue: string
+  }
+
+  type Scalar = BlockValue | PlainValue | QuoteValue
+
+  interface BlockValue extends Node {
+    type: Type.BLOCK_FOLDED | Type.BLOCK_LITERAL
+    chomping: 'CLIP' | 'KEEP' | 'STRIP'
+    blockIndent: number | null
+    header: Range
+    readonly strValue: string | null
+  }
+
+  interface BlockFolded extends BlockValue {
+    type: Type.BLOCK_FOLDED
+  }
+
+  interface BlockLiteral extends BlockValue {
+    type: Type.BLOCK_LITERAL
+  }
+
+  interface PlainValue extends Node {
+    type: Type.PLAIN
+    readonly strValue: string | null
+  }
+
+  interface QuoteValue extends Node {
+    type: Type.QUOTE_DOUBLE | Type.QUOTE_SINGLE
+    readonly strValue:
+      | null
+      | string
+      | { str: string; errors: YAMLSyntaxError[] }
+  }
+
+  interface QuoteDouble extends QuoteValue {
+    type: Type.QUOTE_DOUBLE
+  }
+
+  interface QuoteSingle extends QuoteValue {
+    type: Type.QUOTE_SINGLE
+  }
+
+  interface Comment extends Node {
+    type: Type.COMMENT
+    readonly anchor: null
+    readonly comment: string
+    readonly rawValue: null
+    readonly tag: null
+  }
+
+  interface BlankLine extends Node {
+    type: Type.BLANK_LINE
+  }
+
+  interface MapItem extends Node {
+    type: Type.MAP_KEY | Type.MAP_VALUE
+    node: ContentNode | null
+  }
+
+  interface MapKey extends MapItem {
+    type: Type.MAP_KEY
+  }
+
+  interface MapValue extends MapItem {
+    type: Type.MAP_VALUE
+  }
+
+  interface Map extends Node {
+    type: Type.MAP
+    /** implicit keys are not wrapped */
+    items: Array<BlankLine | Comment | Alias | Scalar | MapItem>
+  }
+
+  interface SeqItem extends Node {
+    type: Type.SEQ_ITEM
+    node: ContentNode | null
+  }
+
+  interface Seq extends Node {
+    type: Type.SEQ
+    items: Array<BlankLine | Comment | SeqItem>
+  }
+
+  interface FlowChar {
+    char: '{' | '}' | '[' | ']' | ',' | '?' | ':'
+    offset: number
+    origOffset?: number
+  }
+
+  interface FlowCollection extends Node {
+    type: Type.FLOW_MAP | Type.FLOW_SEQ
+    items: Array<
+      FlowChar | BlankLine | Comment | Alias | Scalar | FlowCollection
+    >
+  }
+
+  interface FlowMap extends FlowCollection {
+    type: Type.FLOW_MAP
+  }
+
+  interface FlowSeq extends FlowCollection {
+    type: Type.FLOW_SEQ
+  }
+
+  type ContentNode = Alias | Scalar | Map | Seq | FlowCollection
+
+  interface Directive extends Node {
+    type: Type.DIRECTIVE
+    name: string
+    readonly anchor: null
+    readonly parameters: string[]
+    readonly tag: null
+  }
+
+  interface Document extends Node {
+    type: Type.DOCUMENT
+    directives: Array<BlankLine | Comment | Directive>
+    contents: Array<BlankLine | Comment | ContentNode>
+    readonly anchor: null
+    readonly comment: null
+    readonly tag: null
+  }
+}
Index: frontend/node_modules/yaml/parse-cst.js
===================================================================
--- frontend/node_modules/yaml/parse-cst.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/parse-cst.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,1 @@
+module.exports = require('./dist/parse-cst').parse
Index: frontend/node_modules/yaml/scalar.js
===================================================================
--- frontend/node_modules/yaml/scalar.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/scalar.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2 @@
+module.exports = require('./dist/types').Scalar
+require('./dist/legacy-exports').warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/schema.js
===================================================================
--- frontend/node_modules/yaml/schema.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/schema.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,9 @@
+const types = require('./dist/types')
+const util = require('./dist/util')
+
+module.exports = types.Schema
+module.exports.nullOptions = types.nullOptions
+module.exports.strOptions = types.strOptions
+module.exports.stringify = util.stringifyString
+
+require('./dist/legacy-exports').warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/seq.js
===================================================================
--- frontend/node_modules/yaml/seq.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/seq.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,2 @@
+module.exports = require('./dist/types').YAMLSeq
+require('./dist/legacy-exports').warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/types.d.ts
===================================================================
--- frontend/node_modules/yaml/types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/types.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,407 @@
+import { Document, scalarOptions } from './index'
+import { CST } from './parse-cst'
+import { Type } from './util'
+
+export const binaryOptions: scalarOptions.Binary
+export const boolOptions: scalarOptions.Bool
+export const intOptions: scalarOptions.Int
+export const nullOptions: scalarOptions.Null
+export const strOptions: scalarOptions.Str
+
+export class Schema {
+  /** Default: `'tag:yaml.org,2002:'` */
+  static defaultPrefix: string
+  static defaultTags: {
+    /** Default: `'tag:yaml.org,2002:map'` */
+    MAP: string
+    /** Default: `'tag:yaml.org,2002:seq'` */
+    SEQ: string
+    /** Default: `'tag:yaml.org,2002:str'` */
+    STR: string
+  }
+  constructor(options: Schema.Options)
+  /**
+   * Convert any value into a `Node` using this schema, recursively turning
+   * objects into collections.
+   *
+   * @param wrapScalars If `true`, also wraps plain values in `Scalar` objects;
+   *   if undefined or `false` and `value` is not an object, it will be returned
+   *   directly.
+   * @param tag Use to specify the collection type, e.g. `"!!omap"`. Note that
+   *   this requires the corresponding tag to be available in this schema.
+   */
+  createNode(
+    value: any,
+    wrapScalars?: boolean,
+    tag?: string,
+    ctx?: Schema.CreateNodeContext
+  ): Node
+  /**
+   * Convert a key and a value into a `Pair` using this schema, recursively
+   * wrapping all values as `Scalar` or `Collection` nodes.
+   *
+   * @param ctx To not wrap scalars, use a context `{ wrapScalars: false }`
+   */
+  createPair(key: any, value: any, ctx?: Schema.CreateNodeContext): Pair
+  merge: boolean
+  name: Schema.Name
+  sortMapEntries: ((a: Pair, b: Pair) => number) | null
+  tags: Schema.Tag[]
+}
+
+export namespace Schema {
+  type Name = 'core' | 'failsafe' | 'json' | 'yaml-1.1'
+
+  interface Options {
+    /**
+     * Array of additional tags to include in the schema, or a function that may
+     * modify the schema's base tag array.
+     */
+    customTags?: (TagId | Tag)[] | ((tags: Tag[]) => Tag[])
+    /**
+     * Enable support for `<<` merge keys.
+     *
+     * Default: `false` for YAML 1.2, `true` for earlier versions
+     */
+    merge?: boolean
+    /**
+     * The base schema to use.
+     *
+     * Default: `"core"` for YAML 1.2, `"yaml-1.1"` for earlier versions
+     */
+    schema?: Name
+    /**
+     * When stringifying, sort map entries. If `true`, sort by comparing key values with `<`.
+     *
+     * Default: `false`
+     */
+    sortMapEntries?: boolean | ((a: Pair, b: Pair) => number)
+    /**
+     * @deprecated Use `customTags` instead.
+     */
+    tags?: Options['customTags']
+  }
+
+  interface CreateNodeContext {
+    wrapScalars?: boolean
+    [key: string]: any
+  }
+
+  interface StringifyContext {
+    forceBlockIndent?: boolean
+    implicitKey?: boolean
+    indent?: string
+    indentAtStart?: number
+    inFlow?: boolean
+    [key: string]: any
+  }
+
+  type TagId =
+    | 'binary'
+    | 'bool'
+    | 'float'
+    | 'floatExp'
+    | 'floatNaN'
+    | 'floatTime'
+    | 'int'
+    | 'intHex'
+    | 'intOct'
+    | 'intTime'
+    | 'null'
+    | 'omap'
+    | 'pairs'
+    | 'set'
+    | 'timestamp'
+
+  type Tag = CustomTag | DefaultTag
+
+  interface BaseTag {
+    /**
+     * An optional factory function, used e.g. by collections when wrapping JS objects as AST nodes.
+     */
+    createNode?: (
+      schema: Schema,
+      value: any,
+      ctx: Schema.CreateNodeContext
+    ) => YAMLMap | YAMLSeq | Scalar
+    /**
+     * If a tag has multiple forms that should be parsed and/or stringified differently, use `format` to identify them.
+     */
+    format?: string
+    /**
+     * Used by `YAML.createNode` to detect your data type, e.g. using `typeof` or
+     * `instanceof`.
+     */
+    identify(value: any): boolean
+    /**
+     * The `Node` child class that implements this tag. Required for collections and tags that have overlapping JS representations.
+     */
+    nodeClass?: new () => any
+    /**
+     * Used by some tags to configure their stringification, where applicable.
+     */
+    options?: object
+    /**
+     * Optional function stringifying the AST node in the current context. If your
+     * data includes a suitable `.toString()` method, you can probably leave this
+     * undefined and use the default stringifier.
+     *
+     * @param item The node being stringified.
+     * @param ctx Contains the stringifying context variables.
+     * @param onComment Callback to signal that the stringifier includes the
+     *   item's comment in its output.
+     * @param onChompKeep Callback to signal that the output uses a block scalar
+     *   type with the `+` chomping indicator.
+     */
+    stringify?: (
+      item: Node,
+      ctx: Schema.StringifyContext,
+      onComment?: () => void,
+      onChompKeep?: () => void
+    ) => string
+    /**
+     * The identifier for your data type, with which its stringified form will be
+     * prefixed. Should either be a !-prefixed local `!tag`, or a fully qualified
+     * `tag:domain,date:foo`.
+     */
+    tag: string
+  }
+
+  interface CustomTag extends BaseTag {
+    /**
+     * A JavaScript class that should be matched to this tag, e.g. `Date` for `!!timestamp`.
+     * @deprecated Use `Tag.identify` instead
+     */
+    class?: new () => any
+    /**
+     * Turns a CST node into an AST node. If returning a non-`Node` value, the
+     * output will be wrapped as a `Scalar`.
+     */
+    resolve(doc: Document, cstNode: CST.Node): Node | any
+  }
+
+  interface DefaultTag extends BaseTag {
+    /**
+     * If `true`, together with `test` allows for values to be stringified without
+     * an explicit tag. For most cases, it's unlikely that you'll actually want to
+     * use this, even if you first think you do.
+     */
+    default: true
+    /**
+     * Alternative form used by default tags; called with `test` match results.
+     */
+    resolve(...match: string[]): Node | any
+    /**
+     * Together with `default` allows for values to be stringified without an
+     * explicit tag and detected using a regular expression. For most cases, it's
+     * unlikely that you'll actually want to use these, even if you first think
+     * you do.
+     */
+    test: RegExp
+  }
+}
+
+export class Node {
+  /** A comment on or immediately after this */
+  comment?: string | null
+  /** A comment before this */
+  commentBefore?: string | null
+  /** Only available when `keepCstNodes` is set to `true` */
+  cstNode?: CST.Node
+  /**
+   * The [start, end] range of characters of the source parsed
+   * into this node (undefined for pairs or if not parsed)
+   */
+  range?: [number, number] | null
+  /** A blank line before this node and its commentBefore */
+  spaceBefore?: boolean
+  /** A fully qualified tag, if required */
+  tag?: string
+  /** A plain JS representation of this node */
+  toJSON(arg?: any): any
+  /** The type of this node */
+  type?: Type | Pair.Type
+}
+
+export class Scalar extends Node {
+  constructor(value: any)
+  type?: Scalar.Type
+  /**
+   * By default (undefined), numbers use decimal notation.
+   * The YAML 1.2 core schema only supports 'HEX' and 'OCT'.
+   */
+  format?: 'BIN' | 'HEX' | 'OCT' | 'TIME'
+  value: any
+  toJSON(arg?: any, ctx?: AST.NodeToJsonContext): any
+  toString(): string
+}
+export namespace Scalar {
+  type Type =
+    | Type.BLOCK_FOLDED
+    | Type.BLOCK_LITERAL
+    | Type.PLAIN
+    | Type.QUOTE_DOUBLE
+    | Type.QUOTE_SINGLE
+}
+
+export class Alias extends Node {
+  type: Type.ALIAS
+  source: Node
+  cstNode?: CST.Alias
+  toString(ctx: Schema.StringifyContext): string
+}
+
+export class Pair extends Node {
+  constructor(key: any, value?: any)
+  type: Pair.Type.PAIR | Pair.Type.MERGE_PAIR
+  /** Always Node or null when parsed, but can be set to anything. */
+  key: any
+  /** Always Node or null when parsed, but can be set to anything. */
+  value: any
+  cstNode?: never // no corresponding cstNode
+  toJSON(arg?: any, ctx?: AST.NodeToJsonContext): object | Map<any, any>
+  toString(
+    ctx?: Schema.StringifyContext,
+    onComment?: () => void,
+    onChompKeep?: () => void
+  ): string
+}
+export namespace Pair {
+  enum Type {
+    PAIR = 'PAIR',
+    MERGE_PAIR = 'MERGE_PAIR'
+  }
+}
+
+export class Merge extends Pair {
+  type: Pair.Type.MERGE_PAIR
+  /** Always Scalar('<<'), defined by the type specification */
+  key: AST.PlainValue
+  /** Always YAMLSeq<Alias(Map)>, stringified as *A if length = 1 */
+  value: YAMLSeq
+  toString(ctx?: Schema.StringifyContext, onComment?: () => void): string
+}
+
+export class Collection extends Node {
+  type?: Type.MAP | Type.FLOW_MAP | Type.SEQ | Type.FLOW_SEQ | Type.DOCUMENT
+  items: any[]
+  schema?: Schema
+
+  /**
+   * Adds a value to the collection. For `!!map` and `!!omap` the value must
+   * be a Pair instance or a `{ key, value }` object, which may not have a key
+   * that already exists in the map.
+   */
+  add(value: any): void
+  addIn(path: Iterable<any>, value: any): void
+  /**
+   * Removes a value from the collection.
+   * @returns `true` if the item was found and removed.
+   */
+  delete(key: any): boolean
+  deleteIn(path: Iterable<any>): boolean
+  /**
+   * Returns item at `key`, or `undefined` if not found. By default unwraps
+   * scalar values from their surrounding node; to disable set `keepScalar` to
+   * `true` (collections are always returned intact).
+   */
+  get(key: any, keepScalar?: boolean): any
+  getIn(path: Iterable<any>, keepScalar?: boolean): any
+  /**
+   * Checks if the collection includes a value with the key `key`.
+   */
+  has(key: any): boolean
+  hasIn(path: Iterable<any>): boolean
+  /**
+   * Sets a value in this collection. For `!!set`, `value` needs to be a
+   * boolean to add/remove the item from the set.
+   */
+  set(key: any, value: any): void
+  setIn(path: Iterable<any>, value: any): void
+}
+
+export class YAMLMap extends Collection {
+  type?: Type.FLOW_MAP | Type.MAP
+  items: Array<Pair>
+  hasAllNullValues(): boolean
+  toJSON(arg?: any, ctx?: AST.NodeToJsonContext): object | Map<any, any>
+  toString(
+    ctx?: Schema.StringifyContext,
+    onComment?: () => void,
+    onChompKeep?: () => void
+  ): string
+}
+
+export class YAMLSeq extends Collection {
+  type?: Type.FLOW_SEQ | Type.SEQ
+  delete(key: number | string | Scalar): boolean
+  get(key: number | string | Scalar, keepScalar?: boolean): any
+  has(key: number | string | Scalar): boolean
+  set(key: number | string | Scalar, value: any): void
+  hasAllNullValues(): boolean
+  toJSON(arg?: any, ctx?: AST.NodeToJsonContext): any[]
+  toString(
+    ctx?: Schema.StringifyContext,
+    onComment?: () => void,
+    onChompKeep?: () => void
+  ): string
+}
+
+export namespace AST {
+  interface NodeToJsonContext {
+    anchors?: any[]
+    doc: Document
+    keep?: boolean
+    mapAsMap?: boolean
+    maxAliasCount?: number
+    onCreate?: (node: Node) => void
+    [key: string]: any
+  }
+
+  interface BlockFolded extends Scalar {
+    type: Type.BLOCK_FOLDED
+    cstNode?: CST.BlockFolded
+  }
+
+  interface BlockLiteral extends Scalar {
+    type: Type.BLOCK_LITERAL
+    cstNode?: CST.BlockLiteral
+  }
+
+  interface PlainValue extends Scalar {
+    type: Type.PLAIN
+    cstNode?: CST.PlainValue
+  }
+
+  interface QuoteDouble extends Scalar {
+    type: Type.QUOTE_DOUBLE
+    cstNode?: CST.QuoteDouble
+  }
+
+  interface QuoteSingle extends Scalar {
+    type: Type.QUOTE_SINGLE
+    cstNode?: CST.QuoteSingle
+  }
+
+  interface FlowMap extends YAMLMap {
+    type: Type.FLOW_MAP
+    cstNode?: CST.FlowMap
+  }
+
+  interface BlockMap extends YAMLMap {
+    type: Type.MAP
+    cstNode?: CST.Map
+  }
+
+  interface FlowSeq extends YAMLSeq {
+    type: Type.FLOW_SEQ
+    items: Array<Node>
+    cstNode?: CST.FlowSeq
+  }
+
+  interface BlockSeq extends YAMLSeq {
+    type: Type.SEQ
+    items: Array<Node | null>
+    cstNode?: CST.Seq
+  }
+}
Index: frontend/node_modules/yaml/types.js
===================================================================
--- frontend/node_modules/yaml/types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/types.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,17 @@
+const types = require('./dist/types')
+
+exports.binaryOptions = types.binaryOptions
+exports.boolOptions = types.boolOptions
+exports.intOptions = types.intOptions
+exports.nullOptions = types.nullOptions
+exports.strOptions = types.strOptions
+
+exports.Schema = types.Schema
+exports.Alias = types.Alias
+exports.Collection = types.Collection
+exports.Merge = types.Merge
+exports.Node = types.Node
+exports.Pair = types.Pair
+exports.Scalar = types.Scalar
+exports.YAMLMap = types.YAMLMap
+exports.YAMLSeq = types.YAMLSeq
Index: frontend/node_modules/yaml/types.mjs
===================================================================
--- frontend/node_modules/yaml/types.mjs	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/types.mjs	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,17 @@
+import types from './dist/types.js'
+
+export const binaryOptions = types.binaryOptions
+export const boolOptions = types.boolOptions
+export const intOptions = types.intOptions
+export const nullOptions = types.nullOptions
+export const strOptions = types.strOptions
+
+export const Schema = types.Schema
+export const Alias = types.Alias
+export const Collection = types.Collection
+export const Merge = types.Merge
+export const Node = types.Node
+export const Pair = types.Pair
+export const Scalar = types.Scalar
+export const YAMLMap = types.YAMLMap
+export const YAMLSeq = types.YAMLSeq
Index: frontend/node_modules/yaml/types/binary.js
===================================================================
--- frontend/node_modules/yaml/types/binary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/types/binary.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,8 @@
+'use strict'
+Object.defineProperty(exports, '__esModule', { value: true })
+
+const legacy = require('../dist/legacy-exports')
+exports.binary = legacy.binary
+exports.default = [exports.binary]
+
+legacy.warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/types/omap.js
===================================================================
--- frontend/node_modules/yaml/types/omap.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/types/omap.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+const legacy = require('../dist/legacy-exports')
+module.exports = legacy.omap
+legacy.warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/types/pairs.js
===================================================================
--- frontend/node_modules/yaml/types/pairs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/types/pairs.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+const legacy = require('../dist/legacy-exports')
+module.exports = legacy.pairs
+legacy.warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/types/set.js
===================================================================
--- frontend/node_modules/yaml/types/set.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/types/set.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,3 @@
+const legacy = require('../dist/legacy-exports')
+module.exports = legacy.set
+legacy.warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/types/timestamp.js
===================================================================
--- frontend/node_modules/yaml/types/timestamp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/types/timestamp.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,10 @@
+'use strict'
+Object.defineProperty(exports, '__esModule', { value: true })
+
+const legacy = require('../dist/legacy-exports')
+exports.default = [legacy.intTime, legacy.floatTime, legacy.timestamp]
+exports.floatTime = legacy.floatTime
+exports.intTime = legacy.intTime
+exports.timestamp = legacy.timestamp
+
+legacy.warnFileDeprecation(__filename)
Index: frontend/node_modules/yaml/util.d.ts
===================================================================
--- frontend/node_modules/yaml/util.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/util.d.ts	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,86 @@
+import { Document } from './index'
+import { CST } from './parse-cst'
+import { AST, Pair, Scalar, Schema } from './types'
+
+export function findPair(items: any[], key: Scalar | any): Pair | undefined
+
+export function parseMap(doc: Document, cst: CST.Map): AST.BlockMap
+export function parseMap(doc: Document, cst: CST.FlowMap): AST.FlowMap
+export function parseSeq(doc: Document, cst: CST.Seq): AST.BlockSeq
+export function parseSeq(doc: Document, cst: CST.FlowSeq): AST.FlowSeq
+
+export function stringifyNumber(item: Scalar): string
+export function stringifyString(
+  item: Scalar,
+  ctx: Schema.StringifyContext,
+  onComment?: () => void,
+  onChompKeep?: () => void
+): string
+
+export function toJSON(
+  value: any,
+  arg?: any,
+  ctx?: Schema.CreateNodeContext
+): any
+
+export enum Type {
+  ALIAS = 'ALIAS',
+  BLANK_LINE = 'BLANK_LINE',
+  BLOCK_FOLDED = 'BLOCK_FOLDED',
+  BLOCK_LITERAL = 'BLOCK_LITERAL',
+  COMMENT = 'COMMENT',
+  DIRECTIVE = 'DIRECTIVE',
+  DOCUMENT = 'DOCUMENT',
+  FLOW_MAP = 'FLOW_MAP',
+  FLOW_SEQ = 'FLOW_SEQ',
+  MAP = 'MAP',
+  MAP_KEY = 'MAP_KEY',
+  MAP_VALUE = 'MAP_VALUE',
+  PLAIN = 'PLAIN',
+  QUOTE_DOUBLE = 'QUOTE_DOUBLE',
+  QUOTE_SINGLE = 'QUOTE_SINGLE',
+  SEQ = 'SEQ',
+  SEQ_ITEM = 'SEQ_ITEM'
+}
+
+interface LinePos {
+  line: number
+  col: number
+}
+
+export class YAMLError extends Error {
+  name:
+    | 'YAMLReferenceError'
+    | 'YAMLSemanticError'
+    | 'YAMLSyntaxError'
+    | 'YAMLWarning'
+  message: string
+  source?: CST.Node
+
+  nodeType?: Type
+  range?: CST.Range
+  linePos?: { start: LinePos; end: LinePos }
+
+  /**
+   * Drops `source` and adds `nodeType`, `range` and `linePos`, as well as
+   * adding details to `message`. Run automatically for document errors if
+   * the `prettyErrors` option is set.
+   */
+  makePretty(): void
+}
+
+export class YAMLReferenceError extends YAMLError {
+  name: 'YAMLReferenceError'
+}
+
+export class YAMLSemanticError extends YAMLError {
+  name: 'YAMLSemanticError'
+}
+
+export class YAMLSyntaxError extends YAMLError {
+  name: 'YAMLSyntaxError'
+}
+
+export class YAMLWarning extends YAMLError {
+  name: 'YAMLWarning'
+}
Index: frontend/node_modules/yaml/util.js
===================================================================
--- frontend/node_modules/yaml/util.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/util.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,16 @@
+const util = require('./dist/util')
+
+exports.findPair = util.findPair
+exports.toJSON = util.toJSON
+exports.parseMap = util.parseMap
+exports.parseSeq = util.parseSeq
+
+exports.stringifyNumber = util.stringifyNumber
+exports.stringifyString = util.stringifyString
+exports.Type = util.Type
+
+exports.YAMLError = util.YAMLError
+exports.YAMLReferenceError = util.YAMLReferenceError
+exports.YAMLSemanticError = util.YAMLSemanticError
+exports.YAMLSyntaxError = util.YAMLSyntaxError
+exports.YAMLWarning = util.YAMLWarning
Index: frontend/node_modules/yaml/util.mjs
===================================================================
--- frontend/node_modules/yaml/util.mjs	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/yaml/util.mjs	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,18 @@
+import util from './dist/util.js'
+
+export const findPair = util.findPair
+export const toJSON = util.toJSON
+
+export const parseMap = util.parseMap
+export const parseSeq = util.parseSeq
+
+export const stringifyNumber = util.stringifyNumber
+export const stringifyString = util.stringifyString
+
+export const Type = util.Type
+
+export const YAMLError = util.YAMLError
+export const YAMLReferenceError = util.YAMLReferenceError
+export const YAMLSemanticError = util.YAMLSemanticError
+export const YAMLSyntaxError = util.YAMLSyntaxError
+export const YAMLWarning = util.YAMLWarning
