import{createRequire}from'module';const require=createRequire(import.meta.url); var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); var __commonJS = (cb, mod) => function __require2() { try { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; } catch (e) { throw mod = 0, e; } }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); // ../../node_modules/yaml/dist/nodes/identity.js var require_identity = __commonJS({ "../../node_modules/yaml/dist/nodes/identity.js"(exports) { "use strict"; var ALIAS = /* @__PURE__ */ Symbol.for("yaml.alias"); var DOC = /* @__PURE__ */ Symbol.for("yaml.document"); var MAP = /* @__PURE__ */ Symbol.for("yaml.map"); var PAIR = /* @__PURE__ */ Symbol.for("yaml.pair"); var SCALAR = /* @__PURE__ */ Symbol.for("yaml.scalar"); var SEQ = /* @__PURE__ */ Symbol.for("yaml.seq"); var NODE_TYPE = /* @__PURE__ */ Symbol.for("yaml.node.type"); var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS; var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC; var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP; var isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR; var isScalar = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SCALAR; var isSeq = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SEQ; function isCollection(node) { if (node && typeof node === "object") switch (node[NODE_TYPE]) { case MAP: case SEQ: return true; } return false; } function isNode(node) { if (node && typeof node === "object") switch (node[NODE_TYPE]) { case ALIAS: case MAP: case SCALAR: case SEQ: return true; } return false; } var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor; exports.ALIAS = ALIAS; exports.DOC = DOC; exports.MAP = MAP; exports.NODE_TYPE = NODE_TYPE; exports.PAIR = PAIR; exports.SCALAR = SCALAR; exports.SEQ = SEQ; exports.hasAnchor = hasAnchor; exports.isAlias = isAlias; exports.isCollection = isCollection; exports.isDocument = isDocument; exports.isMap = isMap; exports.isNode = isNode; exports.isPair = isPair; exports.isScalar = isScalar; exports.isSeq = isSeq; } }); // ../../node_modules/yaml/dist/visit.js var require_visit = __commonJS({ "../../node_modules/yaml/dist/visit.js"(exports) { "use strict"; var identity = require_identity(); var BREAK = /* @__PURE__ */ Symbol("break visit"); var SKIP = /* @__PURE__ */ Symbol("skip children"); var REMOVE = /* @__PURE__ */ Symbol("remove node"); function visit(node, visitor) { const visitor_ = initVisitor(visitor); if (identity.isDocument(node)) { const cd = visit_(null, node.contents, visitor_, Object.freeze([node])); if (cd === REMOVE) node.contents = null; } else visit_(null, node, visitor_, Object.freeze([])); } visit.BREAK = BREAK; visit.SKIP = SKIP; visit.REMOVE = REMOVE; function visit_(key, node, visitor, path) { const ctrl = callVisitor(key, node, visitor, path); if (identity.isNode(ctrl) || identity.isPair(ctrl)) { replaceNode(key, path, ctrl); return visit_(key, ctrl, visitor, path); } if (typeof ctrl !== "symbol") { if (identity.isCollection(node)) { path = Object.freeze(path.concat(node)); for (let i = 0; i < node.items.length; ++i) { const ci = visit_(i, node.items[i], visitor, path); if (typeof ci === "number") i = ci - 1; else if (ci === BREAK) return BREAK; else if (ci === REMOVE) { node.items.splice(i, 1); i -= 1; } } } else if (identity.isPair(node)) { path = Object.freeze(path.concat(node)); const ck = visit_("key", node.key, visitor, path); if (ck === BREAK) return BREAK; else if (ck === REMOVE) node.key = null; const cv = visit_("value", node.value, visitor, path); if (cv === BREAK) return BREAK; else if (cv === REMOVE) node.value = null; } } return ctrl; } async function visitAsync(node, visitor) { const visitor_ = initVisitor(visitor); if (identity.isDocument(node)) { const cd = await visitAsync_(null, node.contents, visitor_, Object.freeze([node])); if (cd === REMOVE) node.contents = null; } else await visitAsync_(null, node, visitor_, Object.freeze([])); } visitAsync.BREAK = BREAK; visitAsync.SKIP = SKIP; visitAsync.REMOVE = REMOVE; async function visitAsync_(key, node, visitor, path) { const ctrl = await callVisitor(key, node, visitor, path); if (identity.isNode(ctrl) || identity.isPair(ctrl)) { replaceNode(key, path, ctrl); return visitAsync_(key, ctrl, visitor, path); } if (typeof ctrl !== "symbol") { if (identity.isCollection(node)) { path = Object.freeze(path.concat(node)); for (let i = 0; i < node.items.length; ++i) { const ci = await visitAsync_(i, node.items[i], visitor, path); if (typeof ci === "number") i = ci - 1; else if (ci === BREAK) return BREAK; else if (ci === REMOVE) { node.items.splice(i, 1); i -= 1; } } } else if (identity.isPair(node)) { path = Object.freeze(path.concat(node)); const ck = await visitAsync_("key", node.key, visitor, path); if (ck === BREAK) return BREAK; else if (ck === REMOVE) node.key = null; const cv = await visitAsync_("value", node.value, visitor, path); if (cv === BREAK) return BREAK; else if (cv === REMOVE) node.value = null; } } return ctrl; } function initVisitor(visitor) { if (typeof visitor === "object" && (visitor.Collection || visitor.Node || visitor.Value)) { return Object.assign({ Alias: visitor.Node, Map: visitor.Node, Scalar: visitor.Node, Seq: visitor.Node }, visitor.Value && { Map: visitor.Value, Scalar: visitor.Value, Seq: visitor.Value }, visitor.Collection && { Map: visitor.Collection, Seq: visitor.Collection }, visitor); } return visitor; } function callVisitor(key, node, visitor, path) { if (typeof visitor === "function") return visitor(key, node, path); if (identity.isMap(node)) return visitor.Map?.(key, node, path); if (identity.isSeq(node)) return visitor.Seq?.(key, node, path); if (identity.isPair(node)) return visitor.Pair?.(key, node, path); if (identity.isScalar(node)) return visitor.Scalar?.(key, node, path); if (identity.isAlias(node)) return visitor.Alias?.(key, node, path); return void 0; } function replaceNode(key, path, node) { const parent = path[path.length - 1]; if (identity.isCollection(parent)) { parent.items[key] = node; } else if (identity.isPair(parent)) { if (key === "key") parent.key = node; else parent.value = node; } else if (identity.isDocument(parent)) { parent.contents = node; } else { const pt = identity.isAlias(parent) ? "alias" : "scalar"; throw new Error(`Cannot replace node with ${pt} parent`); } } exports.visit = visit; exports.visitAsync = visitAsync; } }); // ../../node_modules/yaml/dist/doc/directives.js var require_directives = __commonJS({ "../../node_modules/yaml/dist/doc/directives.js"(exports) { "use strict"; var identity = require_identity(); var visit = require_visit(); var escapeChars = { "!": "%21", ",": "%2C", "[": "%5B", "]": "%5D", "{": "%7B", "}": "%7D" }; var escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, (ch) => escapeChars[ch]); var Directives = class _Directives { constructor(yaml, tags) { this.docStart = null; this.docEnd = false; this.yaml = Object.assign({}, _Directives.defaultYaml, yaml); this.tags = Object.assign({}, _Directives.defaultTags, tags); } clone() { const copy = new _Directives(this.yaml, this.tags); copy.docStart = this.docStart; return copy; } /** * During parsing, get a Directives instance for the current document and * update the stream state according to the current version's spec. */ atDocument() { const res = new _Directives(this.yaml, this.tags); switch (this.yaml.version) { case "1.1": this.atNextDocument = true; break; case "1.2": this.atNextDocument = false; this.yaml = { explicit: _Directives.defaultYaml.explicit, version: "1.2" }; this.tags = Object.assign({}, _Directives.defaultTags); break; } return res; } /** * @param onError - May be called even if the action was successful * @returns `true` on success */ add(line, onError) { if (this.atNextDocument) { this.yaml = { explicit: _Directives.defaultYaml.explicit, version: "1.1" }; this.tags = Object.assign({}, _Directives.defaultTags); this.atNextDocument = false; } const parts = line.trim().split(/[ \t]+/); const name = parts.shift(); switch (name) { case "%TAG": { if (parts.length !== 2) { onError(0, "%TAG directive should contain exactly two parts"); if (parts.length < 2) return false; } const [handle, prefix] = parts; this.tags[handle] = prefix; return true; } case "%YAML": { this.yaml.explicit = true; if (parts.length !== 1) { onError(0, "%YAML directive should contain exactly one part"); return false; } const [version] = parts; if (version === "1.1" || version === "1.2") { this.yaml.version = version; return true; } else { const isValid = /^\d+\.\d+$/.test(version); onError(6, `Unsupported YAML version ${version}`, isValid); return false; } } default: onError(0, `Unknown directive ${name}`, true); return false; } } /** * Resolves a tag, matching handles to those defined in %TAG directives. * * @returns Resolved tag, which may also be the non-specific tag `'!'` or a * `'!local'` tag, or `null` if unresolvable. */ tagName(source, onError) { if (source === "!") return "!"; if (source[0] !== "!") { onError(`Not a valid tag: ${source}`); return null; } if (source[1] === "<") { const verbatim = source.slice(2, -1); if (verbatim === "!" || verbatim === "!!") { onError(`Verbatim tags aren't resolved, so ${source} is invalid.`); return null; } if (source[source.length - 1] !== ">") onError("Verbatim tags must end with a >"); return verbatim; } const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s); if (!suffix) onError(`The ${source} tag has no suffix`); const prefix = this.tags[handle]; if (prefix) { try { return prefix + decodeURIComponent(suffix); } catch (error) { onError(String(error)); return null; } } if (handle === "!") return source; onError(`Could not resolve tag: ${source}`); return null; } /** * Given a fully resolved tag, returns its printable string form, * taking into account current tag prefixes and defaults. */ tagString(tag) { for (const [handle, prefix] of Object.entries(this.tags)) { if (tag.startsWith(prefix)) return handle + escapeTagName(tag.substring(prefix.length)); } return tag[0] === "!" ? tag : `!<${tag}>`; } toString(doc) { const lines = this.yaml.explicit ? [`%YAML ${this.yaml.version || "1.2"}`] : []; const tagEntries = Object.entries(this.tags); let tagNames; if (doc && tagEntries.length > 0 && identity.isNode(doc.contents)) { const tags = {}; visit.visit(doc.contents, (_key, node) => { if (identity.isNode(node) && node.tag) tags[node.tag] = true; }); tagNames = Object.keys(tags); } else tagNames = []; for (const [handle, prefix] of tagEntries) { if (handle === "!!" && prefix === "tag:yaml.org,2002:") continue; if (!doc || tagNames.some((tn) => tn.startsWith(prefix))) lines.push(`%TAG ${handle} ${prefix}`); } return lines.join("\n"); } }; Directives.defaultYaml = { explicit: false, version: "1.2" }; Directives.defaultTags = { "!!": "tag:yaml.org,2002:" }; exports.Directives = Directives; } }); // ../../node_modules/yaml/dist/doc/anchors.js var require_anchors = __commonJS({ "../../node_modules/yaml/dist/doc/anchors.js"(exports) { "use strict"; var identity = require_identity(); var visit = require_visit(); function anchorIsValid(anchor) { if (/[\x00-\x19\s,[\]{}]/.test(anchor)) { const sa = JSON.stringify(anchor); const msg = `Anchor must not contain whitespace or control characters: ${sa}`; throw new Error(msg); } return true; } function anchorNames(root) { const anchors = /* @__PURE__ */ new Set(); visit.visit(root, { Value(_key, node) { if (node.anchor) anchors.add(node.anchor); } }); return anchors; } function findNewAnchor(prefix, exclude) { for (let i = 1; true; ++i) { const name = `${prefix}${i}`; if (!exclude.has(name)) return name; } } function createNodeAnchors(doc, prefix) { const aliasObjects = []; const sourceObjects = /* @__PURE__ */ new Map(); let prevAnchors = null; return { onAnchor: (source) => { aliasObjects.push(source); prevAnchors ?? (prevAnchors = anchorNames(doc)); const anchor = findNewAnchor(prefix, prevAnchors); prevAnchors.add(anchor); return anchor; }, /** * With circular references, the source node is only resolved after all * of its child nodes are. This is why anchors are set only after all of * the nodes have been created. */ setAnchors: () => { for (const source of aliasObjects) { const ref = sourceObjects.get(source); if (typeof ref === "object" && ref.anchor && (identity.isScalar(ref.node) || identity.isCollection(ref.node))) { ref.node.anchor = ref.anchor; } else { const error = new Error("Failed to resolve repeated object (this should not happen)"); error.source = source; throw error; } } }, sourceObjects }; } exports.anchorIsValid = anchorIsValid; exports.anchorNames = anchorNames; exports.createNodeAnchors = createNodeAnchors; exports.findNewAnchor = findNewAnchor; } }); // ../../node_modules/yaml/dist/doc/applyReviver.js var require_applyReviver = __commonJS({ "../../node_modules/yaml/dist/doc/applyReviver.js"(exports) { "use strict"; function applyReviver(reviver, obj, key, val) { if (val && typeof val === "object") { if (Array.isArray(val)) { for (let i = 0, len = val.length; i < len; ++i) { const v0 = val[i]; const v1 = applyReviver(reviver, val, String(i), v0); if (v1 === void 0) delete val[i]; else if (v1 !== v0) val[i] = v1; } } else if (val instanceof Map) { for (const k of Array.from(val.keys())) { const v0 = val.get(k); const v1 = applyReviver(reviver, val, k, v0); if (v1 === void 0) val.delete(k); else if (v1 !== v0) val.set(k, v1); } } else if (val instanceof Set) { for (const v0 of Array.from(val)) { const v1 = applyReviver(reviver, val, v0, v0); if (v1 === void 0) val.delete(v0); else if (v1 !== v0) { val.delete(v0); val.add(v1); } } } else { for (const [k, v0] of Object.entries(val)) { const v1 = applyReviver(reviver, val, k, v0); if (v1 === void 0) delete val[k]; else if (v1 !== v0) val[k] = v1; } } } return reviver.call(obj, key, val); } exports.applyReviver = applyReviver; } }); // ../../node_modules/yaml/dist/nodes/toJS.js var require_toJS = __commonJS({ "../../node_modules/yaml/dist/nodes/toJS.js"(exports) { "use strict"; var identity = require_identity(); function toJS(value, arg, ctx) { if (Array.isArray(value)) return value.map((v, i) => toJS(v, String(i), ctx)); if (value && typeof value.toJSON === "function") { if (!ctx || !identity.hasAnchor(value)) return value.toJSON(arg, ctx); const data = { aliasCount: 0, count: 1, res: void 0 }; ctx.anchors.set(value, data); ctx.onCreate = (res2) => { data.res = res2; delete ctx.onCreate; }; const res = value.toJSON(arg, ctx); if (ctx.onCreate) ctx.onCreate(res); return res; } if (typeof value === "bigint" && !ctx?.keep) return Number(value); return value; } exports.toJS = toJS; } }); // ../../node_modules/yaml/dist/nodes/Node.js var require_Node = __commonJS({ "../../node_modules/yaml/dist/nodes/Node.js"(exports) { "use strict"; var applyReviver = require_applyReviver(); var identity = require_identity(); var toJS = require_toJS(); var NodeBase = class { constructor(type) { Object.defineProperty(this, identity.NODE_TYPE, { value: type }); } /** Create a copy of this node. */ clone() { const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); if (this.range) copy.range = this.range.slice(); return copy; } /** A plain JavaScript representation of this node. */ toJS(doc, { mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { if (!identity.isDocument(doc)) throw new TypeError("A document argument is required"); const ctx = { anchors: /* @__PURE__ */ new Map(), doc, keep: true, mapAsMap: mapAsMap === true, mapKeyWarned: false, maxAliasCount: typeof maxAliasCount === "number" ? maxAliasCount : 100 }; const res = toJS.toJS(this, "", ctx); if (typeof onAnchor === "function") for (const { count, res: res2 } of ctx.anchors.values()) onAnchor(res2, count); return typeof reviver === "function" ? applyReviver.applyReviver(reviver, { "": res }, "", res) : res; } }; exports.NodeBase = NodeBase; } }); // ../../node_modules/yaml/dist/nodes/Alias.js var require_Alias = __commonJS({ "../../node_modules/yaml/dist/nodes/Alias.js"(exports) { "use strict"; var anchors = require_anchors(); var visit = require_visit(); var identity = require_identity(); var Node = require_Node(); var toJS = require_toJS(); var Alias = class extends Node.NodeBase { constructor(source) { super(identity.ALIAS); this.source = source; Object.defineProperty(this, "tag", { set() { throw new Error("Alias nodes cannot have tags"); } }); } /** * Resolve the value of this alias within `doc`, finding the last * instance of the `source` anchor before this node. */ resolve(doc, ctx) { if (ctx?.maxAliasCount === 0) throw new ReferenceError("Alias resolution is disabled"); let nodes; if (ctx?.aliasResolveCache) { nodes = ctx.aliasResolveCache; } else { nodes = []; visit.visit(doc, { Node: (_key, node) => { if (identity.isAlias(node) || identity.hasAnchor(node)) nodes.push(node); } }); if (ctx) ctx.aliasResolveCache = nodes; } let found = void 0; for (const node of nodes) { if (node === this) break; if (node.anchor === this.source) found = node; } if (found && ctx) { const { anchors: anchors2, doc: doc2, maxAliasCount } = ctx; let data = anchors2.get(found); if (!data) { toJS.toJS(found, null, ctx); data = anchors2.get(found); } if (data?.res === void 0) { const msg = "This should not happen: Alias anchor was not resolved?"; throw new ReferenceError(msg); } if (maxAliasCount >= 0) { data.count += 1; if (data.aliasCount === 0) data.aliasCount = getAliasCount(doc2, found, anchors2); if (data.count * data.aliasCount > maxAliasCount) { const msg = "Excessive alias count indicates a resource exhaustion attack"; throw new ReferenceError(msg); } } } return found; } toJSON(_arg, ctx) { if (!ctx) return { source: this.source }; const source = this.resolve(ctx.doc, ctx); if (!source) { const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; throw new ReferenceError(msg); } return ctx.anchors.get(source).res; } toString(ctx, _onComment, _onChompKeep) { const src = `*${this.source}`; if (ctx) { anchors.anchorIsValid(this.source); if (ctx.options.verifyAliasOrder && !ctx.anchors.has(this.source)) { const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; throw new Error(msg); } if (ctx.implicitKey) return `${src} `; } return src; } }; function getAliasCount(doc, node, anchors2) { if (identity.isAlias(node)) { const source = node.resolve(doc); const anchor = anchors2 && source && anchors2.get(source); return anchor ? anchor.count * anchor.aliasCount : 0; } else if (identity.isCollection(node)) { let count = 0; for (const item of node.items) { const c = getAliasCount(doc, item, anchors2); if (c > count) count = c; } return count; } else if (identity.isPair(node)) { const kc = getAliasCount(doc, node.key, anchors2); const vc = getAliasCount(doc, node.value, anchors2); return Math.max(kc, vc); } return 1; } exports.Alias = Alias; } }); // ../../node_modules/yaml/dist/nodes/Scalar.js var require_Scalar = __commonJS({ "../../node_modules/yaml/dist/nodes/Scalar.js"(exports) { "use strict"; var identity = require_identity(); var Node = require_Node(); var toJS = require_toJS(); var isScalarValue = (value) => !value || typeof value !== "function" && typeof value !== "object"; var Scalar = class extends Node.NodeBase { constructor(value) { super(identity.SCALAR); this.value = value; } toJSON(arg, ctx) { return ctx?.keep ? this.value : toJS.toJS(this.value, arg, ctx); } toString() { return String(this.value); } }; Scalar.BLOCK_FOLDED = "BLOCK_FOLDED"; Scalar.BLOCK_LITERAL = "BLOCK_LITERAL"; Scalar.PLAIN = "PLAIN"; Scalar.QUOTE_DOUBLE = "QUOTE_DOUBLE"; Scalar.QUOTE_SINGLE = "QUOTE_SINGLE"; exports.Scalar = Scalar; exports.isScalarValue = isScalarValue; } }); // ../../node_modules/yaml/dist/doc/createNode.js var require_createNode = __commonJS({ "../../node_modules/yaml/dist/doc/createNode.js"(exports) { "use strict"; var Alias = require_Alias(); var identity = require_identity(); var Scalar = require_Scalar(); var defaultTagPrefix = "tag:yaml.org,2002:"; 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; } return tags.find((t) => t.identify?.(value) && !t.format); } function createNode(value, tagName, ctx) { if (identity.isDocument(value)) value = value.contents; if (identity.isNode(value)) return value; if (identity.isPair(value)) { const map = ctx.schema[identity.MAP].createNode?.(ctx.schema, null, ctx); map.items.push(value); return map; } if (value instanceof String || value instanceof Number || value instanceof Boolean || typeof BigInt !== "undefined" && value instanceof BigInt) { value = value.valueOf(); } const { aliasDuplicateObjects, onAnchor, onTagObj, schema, sourceObjects } = ctx; let ref = void 0; if (aliasDuplicateObjects && value && typeof value === "object") { ref = sourceObjects.get(value); if (ref) { ref.anchor ?? (ref.anchor = onAnchor(value)); return new Alias.Alias(ref.anchor); } else { ref = { anchor: null, node: null }; sourceObjects.set(value, ref); } } if (tagName?.startsWith("!!")) tagName = defaultTagPrefix + tagName.slice(2); let tagObj = findTagObject(value, tagName, schema.tags); if (!tagObj) { if (value && typeof value.toJSON === "function") { value = value.toJSON(); } if (!value || typeof value !== "object") { const node2 = new Scalar.Scalar(value); if (ref) ref.node = node2; return node2; } tagObj = value instanceof Map ? schema[identity.MAP] : Symbol.iterator in Object(value) ? schema[identity.SEQ] : schema[identity.MAP]; } if (onTagObj) { onTagObj(tagObj); delete ctx.onTagObj; } const node = tagObj?.createNode ? tagObj.createNode(ctx.schema, value, ctx) : typeof tagObj?.nodeClass?.from === "function" ? tagObj.nodeClass.from(ctx.schema, value, ctx) : new Scalar.Scalar(value); if (tagName) node.tag = tagName; else if (!tagObj.default) node.tag = tagObj.tag; if (ref) ref.node = node; return node; } exports.createNode = createNode; } }); // ../../node_modules/yaml/dist/nodes/Collection.js var require_Collection = __commonJS({ "../../node_modules/yaml/dist/nodes/Collection.js"(exports) { "use strict"; var createNode = require_createNode(); var identity = require_identity(); var Node = require_Node(); function collectionFromPath(schema, path, value) { let v = value; for (let i = path.length - 1; i >= 0; --i) { const k = path[i]; if (typeof k === "number" && Number.isInteger(k) && k >= 0) { const a = []; a[k] = v; v = a; } else { v = /* @__PURE__ */ new Map([[k, v]]); } } return createNode.createNode(v, void 0, { aliasDuplicateObjects: false, keepUndefined: false, onAnchor: () => { throw new Error("This should not happen, please report a bug."); }, schema, sourceObjects: /* @__PURE__ */ new Map() }); } var isEmptyPath = (path) => path == null || typeof path === "object" && !!path[Symbol.iterator]().next().done; var Collection = class extends Node.NodeBase { constructor(type, schema) { super(type); Object.defineProperty(this, "schema", { value: schema, configurable: true, enumerable: false, writable: true }); } /** * Create a copy of this collection. * * @param schema - If defined, overwrites the original's schema */ clone(schema) { const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); if (schema) copy.schema = schema; copy.items = copy.items.map((it) => identity.isNode(it) || identity.isPair(it) ? it.clone(schema) : it); if (this.range) copy.range = this.range.slice(); return copy; } /** * 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. */ addIn(path, value) { if (isEmptyPath(path)) this.add(value); else { const [key, ...rest] = path; const node = this.get(key, true); if (identity.isCollection(node)) node.addIn(rest, value); else if (node === void 0 && this.schema) this.set(key, collectionFromPath(this.schema, rest, value)); else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); } } /** * Removes a value from the collection. * @returns `true` if the item was found and removed. */ deleteIn(path) { const [key, ...rest] = path; if (rest.length === 0) return this.delete(key); const node = this.get(key, true); if (identity.isCollection(node)) return node.deleteIn(rest); else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); } /** * 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). */ getIn(path, keepScalar) { const [key, ...rest] = path; const node = this.get(key, true); if (rest.length === 0) return !keepScalar && identity.isScalar(node) ? node.value : node; else return identity.isCollection(node) ? node.getIn(rest, keepScalar) : void 0; } hasAllNullValues(allowScalar) { return this.items.every((node) => { if (!identity.isPair(node)) return false; const n = node.value; return n == null || allowScalar && identity.isScalar(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag; }); } /** * Checks if the collection includes a value with the key `key`. */ hasIn(path) { const [key, ...rest] = path; if (rest.length === 0) return this.has(key); const node = this.get(key, true); return identity.isCollection(node) ? node.hasIn(rest) : false; } /** * Sets a value in this collection. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. */ setIn(path, value) { const [key, ...rest] = path; if (rest.length === 0) { this.set(key, value); } else { const node = this.get(key, true); if (identity.isCollection(node)) node.setIn(rest, value); else if (node === void 0 && this.schema) this.set(key, collectionFromPath(this.schema, rest, value)); else throw new Error(`Expected YAML collection at ${key}. Remaining path: ${rest}`); } } }; exports.Collection = Collection; exports.collectionFromPath = collectionFromPath; exports.isEmptyPath = isEmptyPath; } }); // ../../node_modules/yaml/dist/stringify/stringifyComment.js var require_stringifyComment = __commonJS({ "../../node_modules/yaml/dist/stringify/stringifyComment.js"(exports) { "use strict"; var stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#"); function indentComment(comment, indent) { if (/^\n+$/.test(comment)) return comment.substring(1); return indent ? comment.replace(/^(?! *$)/gm, indent) : comment; } var lineComment = (str, indent, comment) => str.endsWith("\n") ? indentComment(comment, indent) : comment.includes("\n") ? "\n" + indentComment(comment, indent) : (str.endsWith(" ") ? "" : " ") + comment; exports.indentComment = indentComment; exports.lineComment = lineComment; exports.stringifyComment = stringifyComment; } }); // ../../node_modules/yaml/dist/stringify/foldFlowLines.js var require_foldFlowLines = __commonJS({ "../../node_modules/yaml/dist/stringify/foldFlowLines.js"(exports) { "use strict"; var FOLD_FLOW = "flow"; var FOLD_BLOCK = "block"; var FOLD_QUOTED = "quoted"; function foldFlowLines(text, indent, mode = "flow", { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) { if (!lineWidth || lineWidth < 0) return text; if (lineWidth < minContentWidth) minContentWidth = 0; 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 = void 0; let prev = void 0; let overflow = false; let i = -1; let escStart = -1; let escEnd = -1; if (mode === FOLD_BLOCK) { i = consumeMoreIndentedLines(text, i, indent.length); 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, indent.length); end = i + indent.length + endStep; split = void 0; } else { if (ch === " " && prev && prev !== " " && prev !== "\n" && prev !== " ") { const next = text[i + 1]; if (next && next !== " " && next !== "\n" && next !== " ") split = i; } if (i >= end) { if (split) { folds.push(split); end = split + endStep; split = void 0; } else if (mode === FOLD_QUOTED) { while (prev === " " || prev === " ") { prev = ch; ch = text[i += 1]; overflow = true; } const j = i > escEnd + 1 ? i - 2 : escStart - 1; if (escapedFolds[j]) return text; folds.push(j); escapedFolds[j] = true; end = j + endStep; split = void 0; } 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 i2 = 0; i2 < folds.length; ++i2) { const fold = folds[i2]; const end2 = folds[i2 + 1] || text.length; if (fold === 0) res = ` ${indent}${text.slice(0, end2)}`; else { if (mode === FOLD_QUOTED && escapedFolds[fold]) res += `${text[fold]}\\`; res += ` ${indent}${text.slice(fold + 1, end2)}`; } } return res; } function consumeMoreIndentedLines(text, i, indent) { let end = i; let start = i + 1; let ch = text[start]; while (ch === " " || ch === " ") { if (i < start + indent) { ch = text[++i]; } else { do { ch = text[++i]; } while (ch && ch !== "\n"); end = i; start = i + 1; ch = text[start]; } } return end; } exports.FOLD_BLOCK = FOLD_BLOCK; exports.FOLD_FLOW = FOLD_FLOW; exports.FOLD_QUOTED = FOLD_QUOTED; exports.foldFlowLines = foldFlowLines; } }); // ../../node_modules/yaml/dist/stringify/stringifyString.js var require_stringifyString = __commonJS({ "../../node_modules/yaml/dist/stringify/stringifyString.js"(exports) { "use strict"; var Scalar = require_Scalar(); var foldFlowLines = require_foldFlowLines(); var getFoldOptions = (ctx, isBlock) => ({ indentAtStart: isBlock ? ctx.indent.length : ctx.indentAtStart, lineWidth: ctx.options.lineWidth, minContentWidth: ctx.options.minContentWidth }); var 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 json2 = JSON.stringify(value); if (ctx.options.doubleQuotedAsJSON) return json2; const { implicitKey } = ctx; const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength; const indent = ctx.indent || (containsDocumentMarker(value) ? " " : ""); let str = ""; let start = 0; for (let i = 0, ch = json2[i]; ch; ch = json2[++i]) { if (ch === " " && json2[i + 1] === "\\" && json2[i + 2] === "n") { str += json2.slice(start, i) + "\\ "; i += 1; start = i; ch = "\\"; } if (ch === "\\") switch (json2[i + 1]) { case "u": { str += json2.slice(start, i); const code = json2.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 += json2.substr(i, 6); } i += 5; start = i + 1; } break; case "n": if (implicitKey || json2[i + 2] === '"' || json2.length < minMultiLineLength) { i += 1; } else { str += json2.slice(start, i) + "\n\n"; while (json2[i + 2] === "\\" && json2[i + 3] === "n" && json2[i + 4] !== '"') { str += "\n"; i += 2; } str += indent; if (json2[i + 2] === " ") str += "\\"; i += 1; start = i + 1; } break; default: i += 1; } } str = start ? str + json2.slice(start) : json2; return implicitKey ? str : foldFlowLines.foldFlowLines(str, indent, foldFlowLines.FOLD_QUOTED, getFoldOptions(ctx, false)); } function singleQuotedString(value, ctx) { if (ctx.options.singleQuote === false || ctx.implicitKey && value.includes("\n") || /[ \t]\n|\n[ \t]/.test(value)) return doubleQuotedString(value, ctx); const indent = ctx.indent || (containsDocumentMarker(value) ? " " : ""); const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$& ${indent}`) + "'"; return ctx.implicitKey ? res : foldFlowLines.foldFlowLines(res, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false)); } function quotedString(value, ctx) { const { singleQuote } = ctx.options; let qs; if (singleQuote === false) qs = doubleQuotedString; else { const hasDouble = value.includes('"'); const hasSingle = value.includes("'"); if (hasDouble && !hasSingle) qs = singleQuotedString; else if (hasSingle && !hasDouble) qs = doubleQuotedString; else qs = singleQuote ? singleQuotedString : doubleQuotedString; } return qs(value, ctx); } var blockEndNewlines; try { blockEndNewlines = new RegExp("(^|(?\n"; let chomp; let endStart; for (endStart = value.length; endStart > 0; --endStart) { const ch = value[endStart - 1]; if (ch !== "\n" && ch !== " " && ch !== " ") break; } let end = value.substring(endStart); const endNlPos = end.indexOf("\n"); if (endNlPos === -1) { chomp = "-"; } else if (value === end || endNlPos !== end.length - 1) { chomp = "+"; if (onChompKeep) onChompKeep(); } else { chomp = ""; } if (end) { value = value.slice(0, -end.length); if (end[end.length - 1] === "\n") end = end.slice(0, -1); end = end.replace(blockEndNewlines, `$&${indent}`); } let startWithSpace = false; let startEnd; let startNlPos = -1; for (startEnd = 0; startEnd < value.length; ++startEnd) { const ch = value[startEnd]; if (ch === " ") startWithSpace = true; else if (ch === "\n") startNlPos = startEnd; else break; } let start = value.substring(0, startNlPos < startEnd ? startNlPos + 1 : startEnd); if (start) { value = value.substring(start.length); start = start.replace(/\n+/g, `$&${indent}`); } const indentSize = indent ? "2" : "1"; let header = (startWithSpace ? indentSize : "") + chomp; if (comment) { header += " " + commentString(comment.replace(/ ?[\r\n]+/g, " ")); if (onComment) onComment(); } if (!literal) { const foldedValue = value.replace(/\n+/g, "\n$&").replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, "$1$2").replace(/\n+/g, `$&${indent}`); let literalFallback = false; const foldOptions = getFoldOptions(ctx, true); if (blockQuote !== "folded" && type !== Scalar.Scalar.BLOCK_FOLDED) { foldOptions.onOverflow = () => { literalFallback = true; }; } const body = foldFlowLines.foldFlowLines(`${start}${foldedValue}${end}`, indent, foldFlowLines.FOLD_BLOCK, foldOptions); if (!literalFallback) return `>${header} ${indent}${body}`; } value = value.replace(/\n+/g, `$&${indent}`); return `|${header} ${indent}${start}${value}${end}`; } function plainString(item, ctx, onComment, onChompKeep) { const { type, value } = item; const { actualString, implicitKey, indent, indentStep, inFlow } = ctx; if (implicitKey && value.includes("\n") || inFlow && /[[\]{},]/.test(value)) { return quotedString(value, ctx); } if (/^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) { return implicitKey || inFlow || !value.includes("\n") ? quotedString(value, ctx) : blockString(item, ctx, onComment, onChompKeep); } if (!implicitKey && !inFlow && type !== Scalar.Scalar.PLAIN && value.includes("\n")) { return blockString(item, ctx, onComment, onChompKeep); } if (containsDocumentMarker(value)) { if (indent === "") { ctx.forceBlockIndent = true; return blockString(item, ctx, onComment, onChompKeep); } else if (implicitKey && indent === indentStep) { return quotedString(value, ctx); } } const str = value.replace(/\n+/g, `$& ${indent}`); if (actualString) { const test = (tag) => tag.default && tag.tag !== "tag:yaml.org,2002:str" && tag.test?.test(str); const { compat, tags } = ctx.doc.schema; if (tags.some(test) || compat?.some(test)) return quotedString(value, ctx); } return implicitKey ? str : foldFlowLines.foldFlowLines(str, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false)); } function stringifyString(item, ctx, onComment, onChompKeep) { const { implicitKey, inFlow } = ctx; const ss = typeof item.value === "string" ? item : Object.assign({}, item, { value: String(item.value) }); let { type } = item; if (type !== Scalar.Scalar.QUOTE_DOUBLE) { if (/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(ss.value)) type = Scalar.Scalar.QUOTE_DOUBLE; } const _stringify = (_type) => { switch (_type) { case Scalar.Scalar.BLOCK_FOLDED: case Scalar.Scalar.BLOCK_LITERAL: return implicitKey || inFlow ? quotedString(ss.value, ctx) : blockString(ss, ctx, onComment, onChompKeep); case Scalar.Scalar.QUOTE_DOUBLE: return doubleQuotedString(ss.value, ctx); case Scalar.Scalar.QUOTE_SINGLE: return singleQuotedString(ss.value, ctx); case Scalar.Scalar.PLAIN: return plainString(ss, ctx, onComment, onChompKeep); default: return null; } }; let res = _stringify(type); if (res === null) { const { defaultKeyType, defaultStringType } = ctx.options; const t = implicitKey && defaultKeyType || defaultStringType; res = _stringify(t); if (res === null) throw new Error(`Unsupported default string type ${t}`); } return res; } exports.stringifyString = stringifyString; } }); // ../../node_modules/yaml/dist/stringify/stringify.js var require_stringify = __commonJS({ "../../node_modules/yaml/dist/stringify/stringify.js"(exports) { "use strict"; var anchors = require_anchors(); var identity = require_identity(); var stringifyComment = require_stringifyComment(); var stringifyString = require_stringifyString(); function createStringifyContext(doc, options) { const opt = Object.assign({ blockQuote: true, commentString: stringifyComment.stringifyComment, defaultKeyType: null, defaultStringType: "PLAIN", directives: null, doubleQuotedAsJSON: false, doubleQuotedMinMultiLineLength: 40, falseStr: "false", flowCollectionPadding: true, indentSeq: true, lineWidth: 80, minContentWidth: 20, nullStr: "null", simpleKeys: false, singleQuote: null, trailingComma: false, trueStr: "true", verifyAliasOrder: true }, doc.schema.toStringOptions, options); let inFlow; switch (opt.collectionStyle) { case "block": inFlow = false; break; case "flow": inFlow = true; break; default: inFlow = null; } return { anchors: /* @__PURE__ */ new Set(), doc, flowCollectionPadding: opt.flowCollectionPadding ? " " : "", indent: "", indentStep: typeof opt.indent === "number" ? " ".repeat(opt.indent) : " ", inFlow, options: opt }; } function getTagObject(tags, item) { 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 = void 0; let obj; if (identity.isScalar(item)) { obj = item.value; let match = tags.filter((t) => t.identify?.(obj)); if (match.length > 1) { const testMatch = match.filter((t) => t.test); if (testMatch.length > 0) match = testMatch; } 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?.constructor?.name ?? (obj === null ? "null" : typeof obj); throw new Error(`Tag not resolved for ${name} value`); } return tagObj; } function stringifyProps(node, tagObj, { anchors: anchors$1, doc }) { if (!doc.directives) return ""; const props = []; const anchor = (identity.isScalar(node) || identity.isCollection(node)) && node.anchor; if (anchor && anchors.anchorIsValid(anchor)) { anchors$1.add(anchor); props.push(`&${anchor}`); } const tag = node.tag ?? (tagObj.default ? null : tagObj.tag); if (tag) props.push(doc.directives.tagString(tag)); return props.join(" "); } function stringify(item, ctx, onComment, onChompKeep) { if (identity.isPair(item)) return item.toString(ctx, onComment, onChompKeep); if (identity.isAlias(item)) { if (ctx.doc.directives) return item.toString(ctx); if (ctx.resolvedAliases?.has(item)) { throw new TypeError(`Cannot stringify circular structure without alias nodes`); } else { if (ctx.resolvedAliases) ctx.resolvedAliases.add(item); else ctx.resolvedAliases = /* @__PURE__ */ new Set([item]); item = item.resolve(ctx.doc); } } let tagObj = void 0; const node = identity.isNode(item) ? item : ctx.doc.createNode(item, { onTagObj: (o) => tagObj = o }); tagObj ?? (tagObj = getTagObject(ctx.doc.schema.tags, node)); const props = stringifyProps(node, tagObj, ctx); if (props.length > 0) ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1; const str = typeof tagObj.stringify === "function" ? tagObj.stringify(node, ctx, onComment, onChompKeep) : identity.isScalar(node) ? stringifyString.stringifyString(node, ctx, onComment, onChompKeep) : node.toString(ctx, onComment, onChompKeep); if (!props) return str; return identity.isScalar(node) || str[0] === "{" || str[0] === "[" ? `${props} ${str}` : `${props} ${ctx.indent}${str}`; } exports.createStringifyContext = createStringifyContext; exports.stringify = stringify; } }); // ../../node_modules/yaml/dist/stringify/stringifyPair.js var require_stringifyPair = __commonJS({ "../../node_modules/yaml/dist/stringify/stringifyPair.js"(exports) { "use strict"; var identity = require_identity(); var Scalar = require_Scalar(); var stringify = require_stringify(); var stringifyComment = require_stringifyComment(); function stringifyPair({ key, value }, ctx, onComment, onChompKeep) { const { allNullValues, doc, indent, indentStep, options: { commentString, indentSeq, simpleKeys } } = ctx; let keyComment = identity.isNode(key) && key.comment || null; if (simpleKeys) { if (keyComment) { throw new Error("With simple keys, key nodes cannot have comments"); } if (identity.isCollection(key) || !identity.isNode(key) && typeof key === "object") { const msg = "With simple keys, collection cannot be used as a key value"; throw new Error(msg); } } let explicitKey = !simpleKeys && (!key || keyComment && value == null && !ctx.inFlow || identity.isCollection(key) || (identity.isScalar(key) ? key.type === Scalar.Scalar.BLOCK_FOLDED || key.type === Scalar.Scalar.BLOCK_LITERAL : typeof key === "object")); ctx = Object.assign({}, ctx, { allNullValues: false, implicitKey: !explicitKey && (simpleKeys || !allNullValues), indent: indent + indentStep }); let keyCommentDone = false; let chompKeep = false; let str = stringify.stringify(key, ctx, () => keyCommentDone = true, () => chompKeep = true); if (!explicitKey && !ctx.inFlow && 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.inFlow) { if (allNullValues || value == null) { if (keyCommentDone && onComment) onComment(); return str === "" ? "?" : explicitKey ? `? ${str}` : str; } } else if (allNullValues && !simpleKeys || value == null && explicitKey) { str = `? ${str}`; if (keyComment && !keyCommentDone) { str += stringifyComment.lineComment(str, ctx.indent, commentString(keyComment)); } else if (chompKeep && onChompKeep) onChompKeep(); return str; } if (keyCommentDone) keyComment = null; if (explicitKey) { if (keyComment) str += stringifyComment.lineComment(str, ctx.indent, commentString(keyComment)); str = `? ${str} ${indent}:`; } else { str = `${str}:`; if (keyComment) str += stringifyComment.lineComment(str, ctx.indent, commentString(keyComment)); } let vsb, vcb, valueComment; if (identity.isNode(value)) { vsb = !!value.spaceBefore; vcb = value.commentBefore; valueComment = value.comment; } else { vsb = false; vcb = null; valueComment = null; if (value && typeof value === "object") value = doc.createNode(value); } ctx.implicitKey = false; if (!explicitKey && !keyComment && identity.isScalar(value)) ctx.indentAtStart = str.length + 1; chompKeep = false; if (!indentSeq && indentStep.length >= 2 && !ctx.inFlow && !explicitKey && identity.isSeq(value) && !value.flow && !value.tag && !value.anchor) { ctx.indent = ctx.indent.substring(2); } let valueCommentDone = false; const valueStr = stringify.stringify(value, ctx, () => valueCommentDone = true, () => chompKeep = true); let ws = " "; if (keyComment || vsb || vcb) { ws = vsb ? "\n" : ""; if (vcb) { const cs = commentString(vcb); ws += ` ${stringifyComment.indentComment(cs, ctx.indent)}`; } if (valueStr === "" && !ctx.inFlow) { if (ws === "\n" && valueComment) ws = "\n\n"; } else { ws += ` ${ctx.indent}`; } } else if (!explicitKey && identity.isCollection(value)) { const vs0 = valueStr[0]; const nl0 = valueStr.indexOf("\n"); const hasNewline = nl0 !== -1; const flow = ctx.inFlow ?? value.flow ?? value.items.length === 0; if (hasNewline || !flow) { let hasPropsLine = false; if (hasNewline && (vs0 === "&" || vs0 === "!")) { let sp0 = valueStr.indexOf(" "); if (vs0 === "&" && sp0 !== -1 && sp0 < nl0 && valueStr[sp0 + 1] === "!") { sp0 = valueStr.indexOf(" ", sp0 + 1); } if (sp0 === -1 || nl0 < sp0) hasPropsLine = true; } if (!hasPropsLine) ws = ` ${ctx.indent}`; } } else if (valueStr === "" || valueStr[0] === "\n") { ws = ""; } str += ws + valueStr; if (ctx.inFlow) { if (valueCommentDone && onComment) onComment(); } else if (valueComment && !valueCommentDone) { str += stringifyComment.lineComment(str, ctx.indent, commentString(valueComment)); } else if (chompKeep && onChompKeep) { onChompKeep(); } return str; } exports.stringifyPair = stringifyPair; } }); // ../../node_modules/yaml/dist/log.js var require_log = __commonJS({ "../../node_modules/yaml/dist/log.js"(exports) { "use strict"; var node_process = __require("process"); function debug(logLevel, ...messages) { if (logLevel === "debug") console.log(...messages); } function warn(logLevel, warning) { if (logLevel === "debug" || logLevel === "warn") { if (typeof node_process.emitWarning === "function") node_process.emitWarning(warning); else console.warn(warning); } } exports.debug = debug; exports.warn = warn; } }); // ../../node_modules/yaml/dist/schema/yaml-1.1/merge.js var require_merge = __commonJS({ "../../node_modules/yaml/dist/schema/yaml-1.1/merge.js"(exports) { "use strict"; var identity = require_identity(); var Scalar = require_Scalar(); var MERGE_KEY = "<<"; var merge = { identify: (value) => value === MERGE_KEY || typeof value === "symbol" && value.description === MERGE_KEY, default: "key", tag: "tag:yaml.org,2002:merge", test: /^<<$/, resolve: () => Object.assign(new Scalar.Scalar(Symbol(MERGE_KEY)), { addToJSMap: addMergeToJSMap }), stringify: () => MERGE_KEY }; var isMergeKey = (ctx, key) => (merge.identify(key) || identity.isScalar(key) && (!key.type || key.type === Scalar.Scalar.PLAIN) && merge.identify(key.value)) && ctx?.doc.schema.tags.some((tag) => tag.tag === merge.tag && tag.default); function addMergeToJSMap(ctx, map, value) { const source = resolveAliasValue(ctx, value); if (identity.isSeq(source)) for (const it of source.items) mergeValue(ctx, map, it); else if (Array.isArray(source)) for (const it of source) mergeValue(ctx, map, it); else mergeValue(ctx, map, source); } function mergeValue(ctx, map, value) { const source = resolveAliasValue(ctx, value); if (!identity.isMap(source)) throw new Error("Merge sources must be maps or map aliases"); const srcMap = source.toJSON(null, ctx, Map); for (const [key, value2] of srcMap) { if (map instanceof Map) { if (!map.has(key)) map.set(key, value2); } else if (map instanceof Set) { map.add(key); } else if (!Object.prototype.hasOwnProperty.call(map, key)) { Object.defineProperty(map, key, { value: value2, writable: true, enumerable: true, configurable: true }); } } return map; } function resolveAliasValue(ctx, value) { return ctx && identity.isAlias(value) ? value.resolve(ctx.doc, ctx) : value; } exports.addMergeToJSMap = addMergeToJSMap; exports.isMergeKey = isMergeKey; exports.merge = merge; } }); // ../../node_modules/yaml/dist/nodes/addPairToJSMap.js var require_addPairToJSMap = __commonJS({ "../../node_modules/yaml/dist/nodes/addPairToJSMap.js"(exports) { "use strict"; var log2 = require_log(); var merge = require_merge(); var stringify = require_stringify(); var identity = require_identity(); var toJS = require_toJS(); function addPairToJSMap(ctx, map, { key, value }) { if (identity.isNode(key) && key.addToJSMap) key.addToJSMap(ctx, map, value); else if (merge.isMergeKey(ctx, key)) merge.addMergeToJSMap(ctx, map, value); else { const jsKey = toJS.toJS(key, "", ctx); if (map instanceof Map) { map.set(jsKey, toJS.toJS(value, jsKey, ctx)); } else if (map instanceof Set) { map.add(jsKey); } else { const stringKey = stringifyKey(key, jsKey, ctx); const jsValue = toJS.toJS(value, stringKey, ctx); if (stringKey in map) Object.defineProperty(map, stringKey, { value: jsValue, writable: true, enumerable: true, configurable: true }); else map[stringKey] = jsValue; } } return map; } function stringifyKey(key, jsKey, ctx) { if (jsKey === null) return ""; if (typeof jsKey !== "object") return String(jsKey); if (identity.isNode(key) && ctx?.doc) { const strCtx = stringify.createStringifyContext(ctx.doc, {}); strCtx.anchors = /* @__PURE__ */ new Set(); for (const node of ctx.anchors.keys()) strCtx.anchors.add(node.anchor); strCtx.inFlow = true; strCtx.inStringifyKey = true; const strKey = key.toString(strCtx); if (!ctx.mapKeyWarned) { let jsonStr = JSON.stringify(strKey); if (jsonStr.length > 40) jsonStr = jsonStr.substring(0, 36) + '..."'; log2.warn(ctx.doc.options.logLevel, `Keys with collection values will be stringified due to JS Object restrictions: ${jsonStr}. Set mapAsMap: true to use object keys.`); ctx.mapKeyWarned = true; } return strKey; } return JSON.stringify(jsKey); } exports.addPairToJSMap = addPairToJSMap; } }); // ../../node_modules/yaml/dist/nodes/Pair.js var require_Pair = __commonJS({ "../../node_modules/yaml/dist/nodes/Pair.js"(exports) { "use strict"; var createNode = require_createNode(); var stringifyPair = require_stringifyPair(); var addPairToJSMap = require_addPairToJSMap(); var identity = require_identity(); function createPair(key, value, ctx) { const k = createNode.createNode(key, void 0, ctx); const v = createNode.createNode(value, void 0, ctx); return new Pair(k, v); } var Pair = class _Pair { constructor(key, value = null) { Object.defineProperty(this, identity.NODE_TYPE, { value: identity.PAIR }); this.key = key; this.value = value; } clone(schema) { let { key, value } = this; if (identity.isNode(key)) key = key.clone(schema); if (identity.isNode(value)) value = value.clone(schema); return new _Pair(key, value); } toJSON(_, ctx) { const pair = ctx?.mapAsMap ? /* @__PURE__ */ new Map() : {}; return addPairToJSMap.addPairToJSMap(ctx, pair, this); } toString(ctx, onComment, onChompKeep) { return ctx?.doc ? stringifyPair.stringifyPair(this, ctx, onComment, onChompKeep) : JSON.stringify(this); } }; exports.Pair = Pair; exports.createPair = createPair; } }); // ../../node_modules/yaml/dist/stringify/stringifyCollection.js var require_stringifyCollection = __commonJS({ "../../node_modules/yaml/dist/stringify/stringifyCollection.js"(exports) { "use strict"; var identity = require_identity(); var stringify = require_stringify(); var stringifyComment = require_stringifyComment(); function stringifyCollection(collection, ctx, options) { const flow = ctx.inFlow ?? collection.flow; const stringify2 = flow ? stringifyFlowCollection : stringifyBlockCollection; return stringify2(collection, ctx, options); } function stringifyBlockCollection({ comment, items }, ctx, { blockItemPrefix, flowChars, itemIndent, onChompKeep, onComment }) { const { indent, options: { commentString } } = ctx; const itemCtx = Object.assign({}, ctx, { indent: itemIndent, type: null }); let chompKeep = false; const lines = []; for (let i = 0; i < items.length; ++i) { const item = items[i]; let comment2 = null; if (identity.isNode(item)) { if (!chompKeep && item.spaceBefore) lines.push(""); addCommentBefore(ctx, lines, item.commentBefore, chompKeep); if (item.comment) comment2 = item.comment; } else if (identity.isPair(item)) { const ik = identity.isNode(item.key) ? item.key : null; if (ik) { if (!chompKeep && ik.spaceBefore) lines.push(""); addCommentBefore(ctx, lines, ik.commentBefore, chompKeep); } } chompKeep = false; let str2 = stringify.stringify(item, itemCtx, () => comment2 = null, () => chompKeep = true); if (comment2) str2 += stringifyComment.lineComment(str2, itemIndent, commentString(comment2)); if (chompKeep && comment2) chompKeep = false; lines.push(blockItemPrefix + str2); } let str; if (lines.length === 0) { str = flowChars.start + flowChars.end; } else { str = lines[0]; for (let i = 1; i < lines.length; ++i) { const line = lines[i]; str += line ? ` ${indent}${line}` : "\n"; } } if (comment) { str += "\n" + stringifyComment.indentComment(commentString(comment), indent); if (onComment) onComment(); } else if (chompKeep && onChompKeep) onChompKeep(); return str; } function stringifyFlowCollection({ items }, ctx, { flowChars, itemIndent }) { const { indent, indentStep, flowCollectionPadding: fcPadding, options: { commentString } } = ctx; itemIndent += indentStep; const itemCtx = Object.assign({}, ctx, { indent: itemIndent, inFlow: true, type: null }); let reqNewline = false; let linesAtValue = 0; const lines = []; for (let i = 0; i < items.length; ++i) { const item = items[i]; let comment = null; if (identity.isNode(item)) { if (item.spaceBefore) lines.push(""); addCommentBefore(ctx, lines, item.commentBefore, false); if (item.comment) comment = item.comment; } else if (identity.isPair(item)) { const ik = identity.isNode(item.key) ? item.key : null; if (ik) { if (ik.spaceBefore) lines.push(""); addCommentBefore(ctx, lines, ik.commentBefore, false); if (ik.comment) reqNewline = true; } const iv = identity.isNode(item.value) ? item.value : null; if (iv) { if (iv.comment) comment = iv.comment; if (iv.commentBefore) reqNewline = true; } else if (item.value == null && ik?.comment) { comment = ik.comment; } } if (comment) reqNewline = true; let str = stringify.stringify(item, itemCtx, () => comment = null); reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n")); if (i < items.length - 1) { str += ","; } else if (ctx.options.trailingComma) { if (ctx.options.lineWidth > 0) { reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (str.length + 2) > ctx.options.lineWidth); } if (reqNewline) { str += ","; } } if (comment) str += stringifyComment.lineComment(str, itemIndent, commentString(comment)); lines.push(str); linesAtValue = lines.length; } const { start, end } = flowChars; if (lines.length === 0) { return start + end; } else { if (!reqNewline) { const len = lines.reduce((sum, line) => sum + line.length + 2, 2); reqNewline = ctx.options.lineWidth > 0 && len > ctx.options.lineWidth; } if (reqNewline) { let str = start; for (const line of lines) str += line ? ` ${indentStep}${indent}${line}` : "\n"; return `${str} ${indent}${end}`; } else { return `${start}${fcPadding}${lines.join(" ")}${fcPadding}${end}`; } } } function addCommentBefore({ indent, options: { commentString } }, lines, comment, chompKeep) { if (comment && chompKeep) comment = comment.replace(/^\n+/, ""); if (comment) { const ic = stringifyComment.indentComment(commentString(comment), indent); lines.push(ic.trimStart()); } } exports.stringifyCollection = stringifyCollection; } }); // ../../node_modules/yaml/dist/nodes/YAMLMap.js var require_YAMLMap = __commonJS({ "../../node_modules/yaml/dist/nodes/YAMLMap.js"(exports) { "use strict"; var stringifyCollection = require_stringifyCollection(); var addPairToJSMap = require_addPairToJSMap(); var Collection = require_Collection(); var identity = require_identity(); var Pair = require_Pair(); var Scalar = require_Scalar(); function findPair(items, key) { const k = identity.isScalar(key) ? key.value : key; for (const it of items) { if (identity.isPair(it)) { if (it.key === key || it.key === k) return it; if (identity.isScalar(it.key) && it.key.value === k) return it; } } return void 0; } var YAMLMap = class extends Collection.Collection { static get tagName() { return "tag:yaml.org,2002:map"; } constructor(schema) { super(identity.MAP, schema); this.items = []; } /** * A generic collection parsing method that can be extended * to other node classes that inherit from YAMLMap */ static from(schema, obj, ctx) { const { keepUndefined, replacer } = ctx; const map = new this(schema); const add = (key, value) => { if (typeof replacer === "function") value = replacer.call(obj, key, value); else if (Array.isArray(replacer) && !replacer.includes(key)) return; if (value !== void 0 || keepUndefined) map.items.push(Pair.createPair(key, value, ctx)); }; if (obj instanceof Map) { for (const [key, value] of obj) add(key, value); } else if (obj && typeof obj === "object") { for (const key of Object.keys(obj)) add(key, obj[key]); } if (typeof schema.sortMapEntries === "function") { map.items.sort(schema.sortMapEntries); } return map; } /** * Adds a value to the collection. * * @param overwrite - If not set `true`, using a key that is already in the * collection will throw. Otherwise, overwrites the previous value. */ add(pair, overwrite) { let _pair; if (identity.isPair(pair)) _pair = pair; else if (!pair || typeof pair !== "object" || !("key" in pair)) { _pair = new Pair.Pair(pair, pair?.value); } else _pair = new Pair.Pair(pair.key, pair.value); const prev = findPair(this.items, _pair.key); const sortEntries = this.schema?.sortMapEntries; if (prev) { if (!overwrite) throw new Error(`Key ${_pair.key} already set`); if (identity.isScalar(prev.value) && Scalar.isScalarValue(_pair.value)) prev.value.value = _pair.value; else prev.value = _pair.value; } 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?.value; return (!keepScalar && identity.isScalar(node) ? node.value : node) ?? void 0; } has(key) { return !!findPair(this.items, key); } set(key, value) { this.add(new Pair.Pair(key, value), true); } /** * @param ctx - Conversion context, originally set in Document#toJS() * @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?.mapAsMap ? /* @__PURE__ */ new Map() : {}; if (ctx?.onCreate) ctx.onCreate(map); for (const item of this.items) addPairToJSMap.addPairToJSMap(ctx, map, item); return map; } toString(ctx, onComment, onChompKeep) { if (!ctx) return JSON.stringify(this); for (const item of this.items) { if (!identity.isPair(item)) throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`); } if (!ctx.allNullValues && this.hasAllNullValues(false)) ctx = Object.assign({}, ctx, { allNullValues: true }); return stringifyCollection.stringifyCollection(this, ctx, { blockItemPrefix: "", flowChars: { start: "{", end: "}" }, itemIndent: ctx.indent || "", onChompKeep, onComment }); } }; exports.YAMLMap = YAMLMap; exports.findPair = findPair; } }); // ../../node_modules/yaml/dist/schema/common/map.js var require_map = __commonJS({ "../../node_modules/yaml/dist/schema/common/map.js"(exports) { "use strict"; var identity = require_identity(); var YAMLMap = require_YAMLMap(); var map = { collection: "map", default: true, nodeClass: YAMLMap.YAMLMap, tag: "tag:yaml.org,2002:map", resolve(map2, onError) { if (!identity.isMap(map2)) onError("Expected a mapping for this tag"); return map2; }, createNode: (schema, obj, ctx) => YAMLMap.YAMLMap.from(schema, obj, ctx) }; exports.map = map; } }); // ../../node_modules/yaml/dist/nodes/YAMLSeq.js var require_YAMLSeq = __commonJS({ "../../node_modules/yaml/dist/nodes/YAMLSeq.js"(exports) { "use strict"; var createNode = require_createNode(); var stringifyCollection = require_stringifyCollection(); var Collection = require_Collection(); var identity = require_identity(); var Scalar = require_Scalar(); var toJS = require_toJS(); var YAMLSeq = class extends Collection.Collection { static get tagName() { return "tag:yaml.org,2002:seq"; } constructor(schema) { super(identity.SEQ, schema); this.items = []; } add(value) { this.items.push(value); } /** * Removes a value from the collection. * * `key` must contain a representation of an integer for this to succeed. * It may be wrapped in a `Scalar`. * * @returns `true` if the item was found and removed. */ 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 void 0; const it = this.items[idx]; return !keepScalar && identity.isScalar(it) ? it.value : it; } /** * Checks if the collection includes a value with the key `key`. * * `key` must contain a representation of an integer for this to succeed. * It may be wrapped in a `Scalar`. */ has(key) { const idx = asItemIndex(key); return typeof idx === "number" && idx < this.items.length; } /** * Sets a value in this collection. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. * * If `key` does not contain a representation of an integer, this will throw. * It may be wrapped in a `Scalar`. */ set(key, value) { const idx = asItemIndex(key); if (typeof idx !== "number") throw new Error(`Expected a valid index, not ${key}.`); const prev = this.items[idx]; if (identity.isScalar(prev) && Scalar.isScalarValue(value)) prev.value = value; else this.items[idx] = value; } toJSON(_, ctx) { const seq = []; if (ctx?.onCreate) ctx.onCreate(seq); let i = 0; for (const item of this.items) seq.push(toJS.toJS(item, String(i++), ctx)); return seq; } toString(ctx, onComment, onChompKeep) { if (!ctx) return JSON.stringify(this); return stringifyCollection.stringifyCollection(this, ctx, { blockItemPrefix: "- ", flowChars: { start: "[", end: "]" }, itemIndent: (ctx.indent || "") + " ", onChompKeep, onComment }); } static from(schema, obj, ctx) { const { replacer } = ctx; const seq = new this(schema); if (obj && Symbol.iterator in Object(obj)) { let i = 0; for (let it of obj) { if (typeof replacer === "function") { const key = obj instanceof Set ? it : String(i++); it = replacer.call(obj, key, it); } seq.items.push(createNode.createNode(it, void 0, ctx)); } } return seq; } }; function asItemIndex(key) { let idx = identity.isScalar(key) ? key.value : key; if (idx && typeof idx === "string") idx = Number(idx); return typeof idx === "number" && Number.isInteger(idx) && idx >= 0 ? idx : null; } exports.YAMLSeq = YAMLSeq; } }); // ../../node_modules/yaml/dist/schema/common/seq.js var require_seq = __commonJS({ "../../node_modules/yaml/dist/schema/common/seq.js"(exports) { "use strict"; var identity = require_identity(); var YAMLSeq = require_YAMLSeq(); var seq = { collection: "seq", default: true, nodeClass: YAMLSeq.YAMLSeq, tag: "tag:yaml.org,2002:seq", resolve(seq2, onError) { if (!identity.isSeq(seq2)) onError("Expected a sequence for this tag"); return seq2; }, createNode: (schema, obj, ctx) => YAMLSeq.YAMLSeq.from(schema, obj, ctx) }; exports.seq = seq; } }); // ../../node_modules/yaml/dist/schema/common/string.js var require_string = __commonJS({ "../../node_modules/yaml/dist/schema/common/string.js"(exports) { "use strict"; var stringifyString = require_stringifyString(); var string = { identify: (value) => typeof value === "string", default: true, tag: "tag:yaml.org,2002:str", resolve: (str) => str, stringify(item, ctx, onComment, onChompKeep) { ctx = Object.assign({ actualString: true }, ctx); return stringifyString.stringifyString(item, ctx, onComment, onChompKeep); } }; exports.string = string; } }); // ../../node_modules/yaml/dist/schema/common/null.js var require_null = __commonJS({ "../../node_modules/yaml/dist/schema/common/null.js"(exports) { "use strict"; var Scalar = require_Scalar(); var nullTag = { identify: (value) => value == null, createNode: () => new Scalar.Scalar(null), default: true, tag: "tag:yaml.org,2002:null", test: /^(?:~|[Nn]ull|NULL)?$/, resolve: () => new Scalar.Scalar(null), stringify: ({ source }, ctx) => typeof source === "string" && nullTag.test.test(source) ? source : ctx.options.nullStr }; exports.nullTag = nullTag; } }); // ../../node_modules/yaml/dist/schema/core/bool.js var require_bool = __commonJS({ "../../node_modules/yaml/dist/schema/core/bool.js"(exports) { "use strict"; var Scalar = require_Scalar(); var boolTag = { identify: (value) => typeof value === "boolean", default: true, tag: "tag:yaml.org,2002:bool", test: /^(?:[Tt]rue|TRUE|[Ff]alse|FALSE)$/, resolve: (str) => new Scalar.Scalar(str[0] === "t" || str[0] === "T"), stringify({ source, value }, ctx) { if (source && boolTag.test.test(source)) { const sv = source[0] === "t" || source[0] === "T"; if (value === sv) return source; } return value ? ctx.options.trueStr : ctx.options.falseStr; } }; exports.boolTag = boolTag; } }); // ../../node_modules/yaml/dist/stringify/stringifyNumber.js var require_stringifyNumber = __commonJS({ "../../node_modules/yaml/dist/stringify/stringifyNumber.js"(exports) { "use strict"; function stringifyNumber({ format, minFractionDigits, tag, value }) { if (typeof value === "bigint") return String(value); const num = typeof value === "number" ? value : Number(value); if (!isFinite(num)) return isNaN(num) ? ".nan" : num < 0 ? "-.inf" : ".inf"; let n = Object.is(value, -0) ? "-0" : JSON.stringify(value); if (!format && minFractionDigits && (!tag || tag === "tag:yaml.org,2002:float") && /^-?\d/.test(n) && !n.includes("e")) { 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; } exports.stringifyNumber = stringifyNumber; } }); // ../../node_modules/yaml/dist/schema/core/float.js var require_float = __commonJS({ "../../node_modules/yaml/dist/schema/core/float.js"(exports) { "use strict"; var Scalar = require_Scalar(); var stringifyNumber = require_stringifyNumber(); var floatNaN = { identify: (value) => typeof value === "number", default: true, tag: "tag:yaml.org,2002:float", test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, resolve: (str) => str.slice(-3).toLowerCase() === "nan" ? NaN : str[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY, stringify: stringifyNumber.stringifyNumber }; var floatExp = { 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(node) { const num = Number(node.value); return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node); } }; var float = { identify: (value) => typeof value === "number", default: true, tag: "tag:yaml.org,2002:float", test: /^[-+]?(?:\.[0-9]+|[0-9]+\.[0-9]*)$/, resolve(str) { const node = new Scalar.Scalar(parseFloat(str)); const dot = str.indexOf("."); if (dot !== -1 && str[str.length - 1] === "0") node.minFractionDigits = str.length - dot - 1; return node; }, stringify: stringifyNumber.stringifyNumber }; exports.float = float; exports.floatExp = floatExp; exports.floatNaN = floatNaN; } }); // ../../node_modules/yaml/dist/schema/core/int.js var require_int = __commonJS({ "../../node_modules/yaml/dist/schema/core/int.js"(exports) { "use strict"; var stringifyNumber = require_stringifyNumber(); var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value); var intResolve = (str, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str.substring(offset), radix); function intStringify(node, radix, prefix) { const { value } = node; if (intIdentify(value) && value >= 0) return prefix + value.toString(radix); return stringifyNumber.stringifyNumber(node); } var intOct = { identify: (value) => intIdentify(value) && value >= 0, default: true, tag: "tag:yaml.org,2002:int", format: "OCT", test: /^0o[0-7]+$/, resolve: (str, _onError, opt) => intResolve(str, 2, 8, opt), stringify: (node) => intStringify(node, 8, "0o") }; var int = { identify: intIdentify, default: true, tag: "tag:yaml.org,2002:int", test: /^[-+]?[0-9]+$/, resolve: (str, _onError, opt) => intResolve(str, 0, 10, opt), stringify: stringifyNumber.stringifyNumber }; var intHex = { identify: (value) => intIdentify(value) && value >= 0, default: true, tag: "tag:yaml.org,2002:int", format: "HEX", test: /^0x[0-9a-fA-F]+$/, resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt), stringify: (node) => intStringify(node, 16, "0x") }; exports.int = int; exports.intHex = intHex; exports.intOct = intOct; } }); // ../../node_modules/yaml/dist/schema/core/schema.js var require_schema = __commonJS({ "../../node_modules/yaml/dist/schema/core/schema.js"(exports) { "use strict"; var map = require_map(); var _null = require_null(); var seq = require_seq(); var string = require_string(); var bool = require_bool(); var float = require_float(); var int = require_int(); var schema = [ map.map, seq.seq, string.string, _null.nullTag, bool.boolTag, int.intOct, int.int, int.intHex, float.floatNaN, float.floatExp, float.float ]; exports.schema = schema; } }); // ../../node_modules/yaml/dist/schema/json/schema.js var require_schema2 = __commonJS({ "../../node_modules/yaml/dist/schema/json/schema.js"(exports) { "use strict"; var Scalar = require_Scalar(); var map = require_map(); var seq = require_seq(); function intIdentify(value) { return typeof value === "bigint" || Number.isInteger(value); } var stringifyJSON = ({ value }) => JSON.stringify(value); var jsonScalars = [ { identify: (value) => typeof value === "string", default: true, tag: "tag:yaml.org,2002:str", resolve: (str) => str, stringify: stringifyJSON }, { identify: (value) => value == null, createNode: () => new Scalar.Scalar(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, default: true, tag: "tag:yaml.org,2002:int", test: /^-?(?:0|[1-9][0-9]*)$/, resolve: (str, _onError, { intAsBigInt }) => intAsBigInt ? BigInt(str) : parseInt(str, 10), stringify: ({ value }) => intIdentify(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 } ]; var jsonError = { default: true, tag: "", test: /^/, resolve(str, onError) { onError(`Unresolved plain scalar ${JSON.stringify(str)}`); return str; } }; var schema = [map.map, seq.seq].concat(jsonScalars, jsonError); exports.schema = schema; } }); // ../../node_modules/yaml/dist/schema/yaml-1.1/binary.js var require_binary = __commonJS({ "../../node_modules/yaml/dist/schema/yaml-1.1/binary.js"(exports) { "use strict"; var node_buffer = __require("buffer"); var Scalar = require_Scalar(); var stringifyString = require_stringifyString(); var 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(src, onError) { if (typeof node_buffer.Buffer === "function") { return node_buffer.Buffer.from(src, "base64"); } else if (typeof atob === "function") { 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 { onError("This environment does not support reading binary tags; either Buffer or atob is required"); return src; } }, stringify({ comment, type, value }, ctx, onComment, onChompKeep) { if (!value) return ""; const buf = value; let str; if (typeof node_buffer.Buffer === "function") { str = buf instanceof node_buffer.Buffer ? buf.toString("base64") : node_buffer.Buffer.from(buf.buffer).toString("base64"); } else if (typeof btoa === "function") { let s = ""; for (let i = 0; i < buf.length; ++i) s += String.fromCharCode(buf[i]); str = btoa(s); } else { throw new Error("This environment does not support writing binary tags; either Buffer or btoa is required"); } type ?? (type = Scalar.Scalar.BLOCK_LITERAL); if (type !== Scalar.Scalar.QUOTE_DOUBLE) { const lineWidth = Math.max(ctx.options.lineWidth - ctx.indent.length, ctx.options.minContentWidth); const n = Math.ceil(str.length / lineWidth); const lines = new Array(n); for (let i = 0, o = 0; i < n; ++i, o += lineWidth) { lines[i] = str.substr(o, lineWidth); } str = lines.join(type === Scalar.Scalar.BLOCK_LITERAL ? "\n" : " "); } return stringifyString.stringifyString({ comment, type, value: str }, ctx, onComment, onChompKeep); } }; exports.binary = binary; } }); // ../../node_modules/yaml/dist/schema/yaml-1.1/pairs.js var require_pairs = __commonJS({ "../../node_modules/yaml/dist/schema/yaml-1.1/pairs.js"(exports) { "use strict"; var identity = require_identity(); var Pair = require_Pair(); var Scalar = require_Scalar(); var YAMLSeq = require_YAMLSeq(); function resolvePairs(seq, onError) { if (identity.isSeq(seq)) { for (let i = 0; i < seq.items.length; ++i) { let item = seq.items[i]; if (identity.isPair(item)) continue; else if (identity.isMap(item)) { if (item.items.length > 1) onError("Each pair must have its own sequence indicator"); const pair = item.items[0] || new Pair.Pair(new Scalar.Scalar(null)); if (item.commentBefore) pair.key.commentBefore = pair.key.commentBefore ? `${item.commentBefore} ${pair.key.commentBefore}` : item.commentBefore; if (item.comment) { const cn = pair.value ?? pair.key; cn.comment = cn.comment ? `${item.comment} ${cn.comment}` : item.comment; } item = pair; } seq.items[i] = identity.isPair(item) ? item : new Pair.Pair(item); } } else onError("Expected a sequence for this tag"); return seq; } function createPairs(schema, iterable, ctx) { const { replacer } = ctx; const pairs2 = new YAMLSeq.YAMLSeq(schema); pairs2.tag = "tag:yaml.org,2002:pairs"; let i = 0; if (iterable && Symbol.iterator in Object(iterable)) for (let it of iterable) { if (typeof replacer === "function") it = replacer.call(iterable, String(i++), it); 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 tuple with one key, not ${keys.length} keys`); } } else { key = it; } pairs2.items.push(Pair.createPair(key, value, ctx)); } return pairs2; } var pairs = { collection: "seq", default: false, tag: "tag:yaml.org,2002:pairs", resolve: resolvePairs, createNode: createPairs }; exports.createPairs = createPairs; exports.pairs = pairs; exports.resolvePairs = resolvePairs; } }); // ../../node_modules/yaml/dist/schema/yaml-1.1/omap.js var require_omap = __commonJS({ "../../node_modules/yaml/dist/schema/yaml-1.1/omap.js"(exports) { "use strict"; var identity = require_identity(); var toJS = require_toJS(); var YAMLMap = require_YAMLMap(); var YAMLSeq = require_YAMLSeq(); var pairs = require_pairs(); var YAMLOMap = class _YAMLOMap extends YAMLSeq.YAMLSeq { constructor() { super(); this.add = YAMLMap.YAMLMap.prototype.add.bind(this); this.delete = YAMLMap.YAMLMap.prototype.delete.bind(this); this.get = YAMLMap.YAMLMap.prototype.get.bind(this); this.has = YAMLMap.YAMLMap.prototype.has.bind(this); this.set = YAMLMap.YAMLMap.prototype.set.bind(this); this.tag = _YAMLOMap.tag; } /** * If `ctx` is given, the return type is actually `Map`, * but TypeScript won't allow widening the signature of a child method. */ toJSON(_, ctx) { if (!ctx) return super.toJSON(_); const map = /* @__PURE__ */ new Map(); if (ctx?.onCreate) ctx.onCreate(map); for (const pair of this.items) { let key, value; if (identity.isPair(pair)) { key = toJS.toJS(pair.key, "", ctx); value = toJS.toJS(pair.value, key, ctx); } else { key = toJS.toJS(pair, "", ctx); } if (map.has(key)) throw new Error("Ordered maps must not include duplicate keys"); map.set(key, value); } return map; } static from(schema, iterable, ctx) { const pairs$1 = pairs.createPairs(schema, iterable, ctx); const omap2 = new this(); omap2.items = pairs$1.items; return omap2; } }; YAMLOMap.tag = "tag:yaml.org,2002:omap"; var omap = { collection: "seq", identify: (value) => value instanceof Map, nodeClass: YAMLOMap, default: false, tag: "tag:yaml.org,2002:omap", resolve(seq, onError) { const pairs$1 = pairs.resolvePairs(seq, onError); const seenKeys = []; for (const { key } of pairs$1.items) { if (identity.isScalar(key)) { if (seenKeys.includes(key.value)) { onError(`Ordered maps must not include duplicate keys: ${key.value}`); } else { seenKeys.push(key.value); } } } return Object.assign(new YAMLOMap(), pairs$1); }, createNode: (schema, iterable, ctx) => YAMLOMap.from(schema, iterable, ctx) }; exports.YAMLOMap = YAMLOMap; exports.omap = omap; } }); // ../../node_modules/yaml/dist/schema/yaml-1.1/bool.js var require_bool2 = __commonJS({ "../../node_modules/yaml/dist/schema/yaml-1.1/bool.js"(exports) { "use strict"; var Scalar = require_Scalar(); function boolStringify({ value, source }, ctx) { const boolObj = value ? trueTag : falseTag; if (source && boolObj.test.test(source)) return source; return value ? ctx.options.trueStr : ctx.options.falseStr; } var trueTag = { identify: (value) => value === true, default: true, tag: "tag:yaml.org,2002:bool", test: /^(?:Y|y|[Yy]es|YES|[Tt]rue|TRUE|[Oo]n|ON)$/, resolve: () => new Scalar.Scalar(true), stringify: boolStringify }; var falseTag = { identify: (value) => value === false, default: true, tag: "tag:yaml.org,2002:bool", test: /^(?:N|n|[Nn]o|NO|[Ff]alse|FALSE|[Oo]ff|OFF)$/, resolve: () => new Scalar.Scalar(false), stringify: boolStringify }; exports.falseTag = falseTag; exports.trueTag = trueTag; } }); // ../../node_modules/yaml/dist/schema/yaml-1.1/float.js var require_float2 = __commonJS({ "../../node_modules/yaml/dist/schema/yaml-1.1/float.js"(exports) { "use strict"; var Scalar = require_Scalar(); var stringifyNumber = require_stringifyNumber(); var floatNaN = { identify: (value) => typeof value === "number", default: true, tag: "tag:yaml.org,2002:float", test: /^(?:[-+]?\.(?:inf|Inf|INF)|\.nan|\.NaN|\.NAN)$/, resolve: (str) => str.slice(-3).toLowerCase() === "nan" ? NaN : str[0] === "-" ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY, stringify: stringifyNumber.stringifyNumber }; var floatExp = { 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(node) { const num = Number(node.value); return isFinite(num) ? num.toExponential() : stringifyNumber.stringifyNumber(node); } }; var float = { identify: (value) => typeof value === "number", default: true, tag: "tag:yaml.org,2002:float", test: /^[-+]?(?:[0-9][0-9_]*)?\.[0-9_]*$/, resolve(str) { const node = new Scalar.Scalar(parseFloat(str.replace(/_/g, ""))); const dot = str.indexOf("."); if (dot !== -1) { const f = str.substring(dot + 1).replace(/_/g, ""); if (f[f.length - 1] === "0") node.minFractionDigits = f.length; } return node; }, stringify: stringifyNumber.stringifyNumber }; exports.float = float; exports.floatExp = floatExp; exports.floatNaN = floatNaN; } }); // ../../node_modules/yaml/dist/schema/yaml-1.1/int.js var require_int2 = __commonJS({ "../../node_modules/yaml/dist/schema/yaml-1.1/int.js"(exports) { "use strict"; var stringifyNumber = require_stringifyNumber(); var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value); function intResolve(str, offset, radix, { intAsBigInt }) { const sign = str[0]; if (sign === "-" || sign === "+") offset += 1; str = str.substring(offset).replace(/_/g, ""); if (intAsBigInt) { switch (radix) { case 2: str = `0b${str}`; break; case 8: str = `0o${str}`; break; case 16: str = `0x${str}`; break; } const n2 = BigInt(str); return sign === "-" ? BigInt(-1) * n2 : n2; } 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.stringifyNumber(node); } var intBin = { identify: intIdentify, default: true, tag: "tag:yaml.org,2002:int", format: "BIN", test: /^[-+]?0b[0-1_]+$/, resolve: (str, _onError, opt) => intResolve(str, 2, 2, opt), stringify: (node) => intStringify(node, 2, "0b") }; var intOct = { identify: intIdentify, default: true, tag: "tag:yaml.org,2002:int", format: "OCT", test: /^[-+]?0[0-7_]+$/, resolve: (str, _onError, opt) => intResolve(str, 1, 8, opt), stringify: (node) => intStringify(node, 8, "0") }; var int = { identify: intIdentify, default: true, tag: "tag:yaml.org,2002:int", test: /^[-+]?[0-9][0-9_]*$/, resolve: (str, _onError, opt) => intResolve(str, 0, 10, opt), stringify: stringifyNumber.stringifyNumber }; var intHex = { identify: intIdentify, default: true, tag: "tag:yaml.org,2002:int", format: "HEX", test: /^[-+]?0x[0-9a-fA-F_]+$/, resolve: (str, _onError, opt) => intResolve(str, 2, 16, opt), stringify: (node) => intStringify(node, 16, "0x") }; exports.int = int; exports.intBin = intBin; exports.intHex = intHex; exports.intOct = intOct; } }); // ../../node_modules/yaml/dist/schema/yaml-1.1/set.js var require_set = __commonJS({ "../../node_modules/yaml/dist/schema/yaml-1.1/set.js"(exports) { "use strict"; var identity = require_identity(); var Pair = require_Pair(); var YAMLMap = require_YAMLMap(); var YAMLSet = class _YAMLSet extends YAMLMap.YAMLMap { constructor(schema) { super(schema); this.tag = _YAMLSet.tag; } add(key) { let pair; if (identity.isPair(key)) pair = key; else if (key && typeof key === "object" && "key" in key && "value" in key && key.value === null) pair = new Pair.Pair(key.key, null); else pair = new Pair.Pair(key, null); const prev = YAMLMap.findPair(this.items, pair.key); if (!prev) this.items.push(pair); } /** * If `keepPair` is `true`, returns the Pair matching `key`. * Otherwise, returns the value of that Pair's key. */ get(key, keepPair) { const pair = YAMLMap.findPair(this.items, key); return !keepPair && identity.isPair(pair) ? identity.isScalar(pair.key) ? 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 = YAMLMap.findPair(this.items, key); if (prev && !value) { this.items.splice(this.items.indexOf(prev), 1); } else if (!prev && value) { this.items.push(new Pair.Pair(key)); } } toJSON(_, ctx) { return super.toJSON(_, ctx, Set); } toString(ctx, onComment, onChompKeep) { if (!ctx) return JSON.stringify(this); if (this.hasAllNullValues(true)) return super.toString(Object.assign({}, ctx, { allNullValues: true }), onComment, onChompKeep); else throw new Error("Set items must all have null values"); } static from(schema, iterable, ctx) { const { replacer } = ctx; const set2 = new this(schema); if (iterable && Symbol.iterator in Object(iterable)) for (let value of iterable) { if (typeof replacer === "function") value = replacer.call(iterable, value, value); set2.items.push(Pair.createPair(value, null, ctx)); } return set2; } }; YAMLSet.tag = "tag:yaml.org,2002:set"; var set = { collection: "map", identify: (value) => value instanceof Set, nodeClass: YAMLSet, default: false, tag: "tag:yaml.org,2002:set", createNode: (schema, iterable, ctx) => YAMLSet.from(schema, iterable, ctx), resolve(map, onError) { if (identity.isMap(map)) { if (map.hasAllNullValues(true)) return Object.assign(new YAMLSet(), map); else onError("Set items must all have null values"); } else onError("Expected a mapping for this tag"); return map; } }; exports.YAMLSet = YAMLSet; exports.set = set; } }); // ../../node_modules/yaml/dist/schema/yaml-1.1/timestamp.js var require_timestamp = __commonJS({ "../../node_modules/yaml/dist/schema/yaml-1.1/timestamp.js"(exports) { "use strict"; var stringifyNumber = require_stringifyNumber(); function parseSexagesimal(str, asBigInt) { const sign = str[0]; const parts = sign === "-" || sign === "+" ? str.substring(1) : str; const num = (n) => asBigInt ? BigInt(n) : Number(n); const res = parts.replace(/_/g, "").split(":").reduce((res2, p) => res2 * num(60) + num(p), num(0)); return sign === "-" ? num(-1) * res : res; } function stringifySexagesimal(node) { let { value } = node; let num = (n) => n; if (typeof value === "bigint") num = (n) => BigInt(n); else if (isNaN(value) || !isFinite(value)) return stringifyNumber.stringifyNumber(node); let sign = ""; if (value < 0) { sign = "-"; value *= num(-1); } const _60 = num(60); const parts = [value % _60]; if (value < 60) { parts.unshift(0); } else { value = (value - parts[0]) / _60; parts.unshift(value % _60); if (value >= 60) { value = (value - parts[0]) / _60; parts.unshift(value); } } return sign + parts.map((n) => String(n).padStart(2, "0")).join(":").replace(/000000\d*$/, ""); } var intTime = { identify: (value) => typeof value === "bigint" || Number.isInteger(value), default: true, tag: "tag:yaml.org,2002:int", format: "TIME", test: /^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+$/, resolve: (str, _onError, { intAsBigInt }) => parseSexagesimal(str, intAsBigInt), stringify: stringifySexagesimal }; var 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) => parseSexagesimal(str, false), stringify: stringifySexagesimal }; var 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})(?:(?:t|T|[ \\t]+)([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2}(\\.[0-9]+)?)(?:[ \\t]*(Z|[-+][012]?[0-9](?::[0-9]{2})?))?)?$"), resolve(str) { const match = str.match(timestamp.test); if (!match) throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd"); const [, year, month, day, hour, minute, second] = match.map(Number); const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0; let date = Date.UTC(year, month - 1, day, hour || 0, minute || 0, second || 0, millisec); const tz = match[8]; if (tz && tz !== "Z") { let d = parseSexagesimal(tz, false); if (Math.abs(d) < 30) d *= 60; date -= 6e4 * d; } return new Date(date); }, stringify: ({ value }) => value?.toISOString().replace(/(T00:00:00)?\.000Z$/, "") ?? "" }; exports.floatTime = floatTime; exports.intTime = intTime; exports.timestamp = timestamp; } }); // ../../node_modules/yaml/dist/schema/yaml-1.1/schema.js var require_schema3 = __commonJS({ "../../node_modules/yaml/dist/schema/yaml-1.1/schema.js"(exports) { "use strict"; var map = require_map(); var _null = require_null(); var seq = require_seq(); var string = require_string(); var binary = require_binary(); var bool = require_bool2(); var float = require_float2(); var int = require_int2(); var merge = require_merge(); var omap = require_omap(); var pairs = require_pairs(); var set = require_set(); var timestamp = require_timestamp(); var schema = [ map.map, seq.seq, string.string, _null.nullTag, bool.trueTag, bool.falseTag, int.intBin, int.intOct, int.int, int.intHex, float.floatNaN, float.floatExp, float.float, binary.binary, merge.merge, omap.omap, pairs.pairs, set.set, timestamp.intTime, timestamp.floatTime, timestamp.timestamp ]; exports.schema = schema; } }); // ../../node_modules/yaml/dist/schema/tags.js var require_tags = __commonJS({ "../../node_modules/yaml/dist/schema/tags.js"(exports) { "use strict"; var map = require_map(); var _null = require_null(); var seq = require_seq(); var string = require_string(); var bool = require_bool(); var float = require_float(); var int = require_int(); var schema = require_schema(); var schema$1 = require_schema2(); var binary = require_binary(); var merge = require_merge(); var omap = require_omap(); var pairs = require_pairs(); var schema$2 = require_schema3(); var set = require_set(); var timestamp = require_timestamp(); var schemas = /* @__PURE__ */ new Map([ ["core", schema.schema], ["failsafe", [map.map, seq.seq, string.string]], ["json", schema$1.schema], ["yaml11", schema$2.schema], ["yaml-1.1", schema$2.schema] ]); var tagsByName = { binary: binary.binary, bool: bool.boolTag, float: float.float, floatExp: float.floatExp, floatNaN: float.floatNaN, floatTime: timestamp.floatTime, int: int.int, intHex: int.intHex, intOct: int.intOct, intTime: timestamp.intTime, map: map.map, merge: merge.merge, null: _null.nullTag, omap: omap.omap, pairs: pairs.pairs, seq: seq.seq, set: set.set, timestamp: timestamp.timestamp }; var coreKnownTags = { "tag:yaml.org,2002:binary": binary.binary, "tag:yaml.org,2002:merge": merge.merge, "tag:yaml.org,2002:omap": omap.omap, "tag:yaml.org,2002:pairs": pairs.pairs, "tag:yaml.org,2002:set": set.set, "tag:yaml.org,2002:timestamp": timestamp.timestamp }; function getTags(customTags, schemaName, addMergeTag) { const schemaTags = schemas.get(schemaName); if (schemaTags && !customTags) { return addMergeTag && !schemaTags.includes(merge.merge) ? schemaTags.concat(merge.merge) : schemaTags.slice(); } let tags = schemaTags; if (!tags) { if (Array.isArray(customTags)) tags = []; else { const keys = Array.from(schemas.keys()).filter((key) => key !== "yaml11").map((key) => JSON.stringify(key)).join(", "); throw new Error(`Unknown schema "${schemaName}"; use one of ${keys} or define customTags array`); } } if (Array.isArray(customTags)) { for (const tag of customTags) tags = tags.concat(tag); } else if (typeof customTags === "function") { tags = customTags(tags.slice()); } if (addMergeTag) tags = tags.concat(merge.merge); return tags.reduce((tags2, tag) => { const tagObj = typeof tag === "string" ? tagsByName[tag] : tag; if (!tagObj) { const tagName = JSON.stringify(tag); const keys = Object.keys(tagsByName).map((key) => JSON.stringify(key)).join(", "); throw new Error(`Unknown custom tag ${tagName}; use one of ${keys}`); } if (!tags2.includes(tagObj)) tags2.push(tagObj); return tags2; }, []); } exports.coreKnownTags = coreKnownTags; exports.getTags = getTags; } }); // ../../node_modules/yaml/dist/schema/Schema.js var require_Schema = __commonJS({ "../../node_modules/yaml/dist/schema/Schema.js"(exports) { "use strict"; var identity = require_identity(); var map = require_map(); var seq = require_seq(); var string = require_string(); var tags = require_tags(); var sortMapEntriesByKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0; var Schema = class _Schema { constructor({ compat, customTags, merge, resolveKnownTags, schema, sortMapEntries, toStringDefaults }) { this.compat = Array.isArray(compat) ? tags.getTags(compat, "compat") : compat ? tags.getTags(null, compat) : null; this.name = typeof schema === "string" && schema || "core"; this.knownTags = resolveKnownTags ? tags.coreKnownTags : {}; this.tags = tags.getTags(customTags, this.name, merge); this.toStringOptions = toStringDefaults ?? null; Object.defineProperty(this, identity.MAP, { value: map.map }); Object.defineProperty(this, identity.SCALAR, { value: string.string }); Object.defineProperty(this, identity.SEQ, { value: seq.seq }); this.sortMapEntries = typeof sortMapEntries === "function" ? sortMapEntries : sortMapEntries === true ? sortMapEntriesByKey : null; } clone() { const copy = Object.create(_Schema.prototype, Object.getOwnPropertyDescriptors(this)); copy.tags = this.tags.slice(); return copy; } }; exports.Schema = Schema; } }); // ../../node_modules/yaml/dist/stringify/stringifyDocument.js var require_stringifyDocument = __commonJS({ "../../node_modules/yaml/dist/stringify/stringifyDocument.js"(exports) { "use strict"; var identity = require_identity(); var stringify = require_stringify(); var stringifyComment = require_stringifyComment(); function stringifyDocument(doc, options) { const lines = []; let hasDirectives = options.directives === true; if (options.directives !== false && doc.directives) { const dir = doc.directives.toString(doc); if (dir) { lines.push(dir); hasDirectives = true; } else if (doc.directives.docStart) hasDirectives = true; } if (hasDirectives) lines.push("---"); const ctx = stringify.createStringifyContext(doc, options); const { commentString } = ctx.options; if (doc.commentBefore) { if (lines.length !== 1) lines.unshift(""); const cs = commentString(doc.commentBefore); lines.unshift(stringifyComment.indentComment(cs, "")); } let chompKeep = false; let contentComment = null; if (doc.contents) { if (identity.isNode(doc.contents)) { if (doc.contents.spaceBefore && hasDirectives) lines.push(""); if (doc.contents.commentBefore) { const cs = commentString(doc.contents.commentBefore); lines.push(stringifyComment.indentComment(cs, "")); } ctx.forceBlockIndent = !!doc.comment; contentComment = doc.contents.comment; } const onChompKeep = contentComment ? void 0 : () => chompKeep = true; let body = stringify.stringify(doc.contents, ctx, () => contentComment = null, onChompKeep); if (contentComment) body += stringifyComment.lineComment(body, "", commentString(contentComment)); if ((body[0] === "|" || body[0] === ">") && lines[lines.length - 1] === "---") { lines[lines.length - 1] = `--- ${body}`; } else lines.push(body); } else { lines.push(stringify.stringify(doc.contents, ctx)); } if (doc.directives?.docEnd) { if (doc.comment) { const cs = commentString(doc.comment); if (cs.includes("\n")) { lines.push("..."); lines.push(stringifyComment.indentComment(cs, "")); } else { lines.push(`... ${cs}`); } } else { lines.push("..."); } } else { let dc = doc.comment; if (dc && chompKeep) dc = dc.replace(/^\n+/, ""); if (dc) { if ((!chompKeep || contentComment) && lines[lines.length - 1] !== "") lines.push(""); lines.push(stringifyComment.indentComment(commentString(dc), "")); } } return lines.join("\n") + "\n"; } exports.stringifyDocument = stringifyDocument; } }); // ../../node_modules/yaml/dist/doc/Document.js var require_Document = __commonJS({ "../../node_modules/yaml/dist/doc/Document.js"(exports) { "use strict"; var Alias = require_Alias(); var Collection = require_Collection(); var identity = require_identity(); var Pair = require_Pair(); var toJS = require_toJS(); var Schema = require_Schema(); var stringifyDocument = require_stringifyDocument(); var anchors = require_anchors(); var applyReviver = require_applyReviver(); var createNode = require_createNode(); var directives = require_directives(); var Document = class _Document { constructor(value, replacer, options) { this.commentBefore = null; this.comment = null; this.errors = []; this.warnings = []; Object.defineProperty(this, identity.NODE_TYPE, { value: identity.DOC }); let _replacer = null; if (typeof replacer === "function" || Array.isArray(replacer)) { _replacer = replacer; } else if (options === void 0 && replacer) { options = replacer; replacer = void 0; } const opt = Object.assign({ intAsBigInt: false, keepSourceTokens: false, logLevel: "warn", prettyErrors: true, strict: true, stringKeys: false, uniqueKeys: true, version: "1.2" }, options); this.options = opt; let { version } = opt; if (options?._directives) { this.directives = options._directives.atDocument(); if (this.directives.yaml.explicit) version = this.directives.yaml.version; } else this.directives = new directives.Directives({ version }); this.setSchema(version, options); this.contents = value === void 0 ? null : this.createNode(value, _replacer, options); } /** * Create a deep copy of this Document and its contents. * * Custom Node values that inherit from `Object` still refer to their original instances. */ clone() { const copy = Object.create(_Document.prototype, { [identity.NODE_TYPE]: { value: identity.DOC } }); copy.commentBefore = this.commentBefore; copy.comment = this.comment; copy.errors = this.errors.slice(); copy.warnings = this.warnings.slice(); copy.options = Object.assign({}, this.options); if (this.directives) copy.directives = this.directives.clone(); copy.schema = this.schema.clone(); copy.contents = identity.isNode(this.contents) ? this.contents.clone(copy.schema) : this.contents; if (this.range) copy.range = this.range.slice(); return copy; } /** Adds a value to the document. */ add(value) { if (assertCollection(this.contents)) this.contents.add(value); } /** Adds a value to the document. */ addIn(path, value) { if (assertCollection(this.contents)) this.contents.addIn(path, value); } /** * Create a new `Alias` node, ensuring that the target `node` has the required anchor. * * If `node` already has an anchor, `name` is ignored. * Otherwise, the `node.anchor` value will be set to `name`, * or if an anchor with that name is already present in the document, * `name` will be used as a prefix for a new unique anchor. * If `name` is undefined, the generated anchor will use 'a' as a prefix. */ createAlias(node, name) { if (!node.anchor) { const prev = anchors.anchorNames(this); node.anchor = // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing !name || prev.has(name) ? anchors.findNewAnchor(name || "a", prev) : name; } return new Alias.Alias(node.anchor); } createNode(value, replacer, options) { let _replacer = void 0; if (typeof replacer === "function") { value = replacer.call({ "": value }, "", value); _replacer = replacer; } else if (Array.isArray(replacer)) { const keyToStr = (v) => typeof v === "number" || v instanceof String || v instanceof Number; const asStr = replacer.filter(keyToStr).map(String); if (asStr.length > 0) replacer = replacer.concat(asStr); _replacer = replacer; } else if (options === void 0 && replacer) { options = replacer; replacer = void 0; } const { aliasDuplicateObjects, anchorPrefix, flow, keepUndefined, onTagObj, tag } = options ?? {}; const { onAnchor, setAnchors, sourceObjects } = anchors.createNodeAnchors( this, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing anchorPrefix || "a" ); const ctx = { aliasDuplicateObjects: aliasDuplicateObjects ?? true, keepUndefined: keepUndefined ?? false, onAnchor, onTagObj, replacer: _replacer, schema: this.schema, sourceObjects }; const node = createNode.createNode(value, tag, ctx); if (flow && identity.isCollection(node)) node.flow = true; setAnchors(); return node; } /** * Convert a key and a value into a `Pair` using the current schema, * recursively wrapping all values as `Scalar` or `Collection` nodes. */ createPair(key, value, options = {}) { const k = this.createNode(key, null, options); const v = this.createNode(value, null, options); return new Pair.Pair(k, v); } /** * Removes a value from the document. * @returns `true` if the item was found and removed. */ delete(key) { return assertCollection(this.contents) ? this.contents.delete(key) : false; } /** * Removes a value from the document. * @returns `true` if the item was found and removed. */ deleteIn(path) { if (Collection.isEmptyPath(path)) { if (this.contents == null) return false; this.contents = null; return true; } return assertCollection(this.contents) ? this.contents.deleteIn(path) : false; } /** * 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, keepScalar) { return identity.isCollection(this.contents) ? this.contents.get(key, keepScalar) : void 0; } /** * Returns item at `path`, 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). */ getIn(path, keepScalar) { if (Collection.isEmptyPath(path)) return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents; return identity.isCollection(this.contents) ? this.contents.getIn(path, keepScalar) : void 0; } /** * Checks if the document includes a value with the key `key`. */ has(key) { return identity.isCollection(this.contents) ? this.contents.has(key) : false; } /** * Checks if the document includes a value at `path`. */ hasIn(path) { if (Collection.isEmptyPath(path)) return this.contents !== void 0; return identity.isCollection(this.contents) ? this.contents.hasIn(path) : false; } /** * Sets a value in this document. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. */ set(key, value) { if (this.contents == null) { this.contents = Collection.collectionFromPath(this.schema, [key], value); } else if (assertCollection(this.contents)) { this.contents.set(key, value); } } /** * Sets a value in this document. For `!!set`, `value` needs to be a * boolean to add/remove the item from the set. */ setIn(path, value) { if (Collection.isEmptyPath(path)) { this.contents = value; } else if (this.contents == null) { this.contents = Collection.collectionFromPath(this.schema, Array.from(path), value); } else if (assertCollection(this.contents)) { this.contents.setIn(path, value); } } /** * Change the YAML version and schema used by the document. * A `null` version disables support for directives, explicit tags, anchors, and aliases. * It also requires the `schema` option to be given as a `Schema` instance value. * * Overrides all previously set schema options. */ setSchema(version, options = {}) { if (typeof version === "number") version = String(version); let opt; switch (version) { case "1.1": if (this.directives) this.directives.yaml.version = "1.1"; else this.directives = new directives.Directives({ version: "1.1" }); opt = { resolveKnownTags: false, schema: "yaml-1.1" }; break; case "1.2": case "next": if (this.directives) this.directives.yaml.version = version; else this.directives = new directives.Directives({ version }); opt = { resolveKnownTags: true, schema: "core" }; break; case null: if (this.directives) delete this.directives; opt = null; break; default: { const sv = JSON.stringify(version); throw new Error(`Expected '1.1', '1.2' or null as first argument, but found: ${sv}`); } } if (options.schema instanceof Object) this.schema = options.schema; else if (opt) this.schema = new Schema.Schema(Object.assign(opt, options)); else throw new Error(`With a null YAML version, the { schema: Schema } option is required`); } // json & jsonArg are only used from toJSON() toJS({ json: json2, jsonArg, mapAsMap, maxAliasCount, onAnchor, reviver } = {}) { const ctx = { anchors: /* @__PURE__ */ new Map(), doc: this, keep: !json2, mapAsMap: mapAsMap === true, mapKeyWarned: false, maxAliasCount: typeof maxAliasCount === "number" ? maxAliasCount : 100 }; const res = toJS.toJS(this.contents, jsonArg ?? "", ctx); if (typeof onAnchor === "function") for (const { count, res: res2 } of ctx.anchors.values()) onAnchor(res2, count); return typeof reviver === "function" ? applyReviver.applyReviver(reviver, { "": res }, "", res) : res; } /** * A JSON representation of the document `contents`. * * @param jsonArg Used by `JSON.stringify` to indicate the array index or * property name. */ toJSON(jsonArg, onAnchor) { return this.toJS({ json: true, jsonArg, mapAsMap: false, onAnchor }); } /** A YAML representation of the document. */ toString(options = {}) { if (this.errors.length > 0) throw new Error("Document with errors cannot be stringified"); if ("indent" in options && (!Number.isInteger(options.indent) || Number(options.indent) <= 0)) { const s = JSON.stringify(options.indent); throw new Error(`"indent" option must be a positive integer, not ${s}`); } return stringifyDocument.stringifyDocument(this, options); } }; function assertCollection(contents) { if (identity.isCollection(contents)) return true; throw new Error("Expected a YAML collection as document contents"); } exports.Document = Document; } }); // ../../node_modules/yaml/dist/errors.js var require_errors = __commonJS({ "../../node_modules/yaml/dist/errors.js"(exports) { "use strict"; var YAMLError = class extends Error { constructor(name, pos, code, message) { super(); this.name = name; this.code = code; this.message = message; this.pos = pos; } }; var YAMLParseError = class extends YAMLError { constructor(pos, code, message) { super("YAMLParseError", pos, code, message); } }; var YAMLWarning = class extends YAMLError { constructor(pos, code, message) { super("YAMLWarning", pos, code, message); } }; var prettifyError = (src, lc) => (error) => { if (error.pos[0] === -1) return; error.linePos = error.pos.map((pos) => lc.linePos(pos)); const { line, col } = error.linePos[0]; error.message += ` at line ${line}, column ${col}`; let ci = col - 1; let lineStr = src.substring(lc.lineStarts[line - 1], lc.lineStarts[line]).replace(/[\n\r]+$/, ""); if (ci >= 60 && lineStr.length > 80) { const trimStart = Math.min(ci - 39, lineStr.length - 79); lineStr = "\u2026" + lineStr.substring(trimStart); ci -= trimStart - 1; } if (lineStr.length > 80) lineStr = lineStr.substring(0, 79) + "\u2026"; if (line > 1 && /^ *$/.test(lineStr.substring(0, ci))) { let prev = src.substring(lc.lineStarts[line - 2], lc.lineStarts[line - 1]); if (prev.length > 80) prev = prev.substring(0, 79) + "\u2026\n"; lineStr = prev + lineStr; } if (/[^ ]/.test(lineStr)) { let count = 1; const end = error.linePos[1]; if (end?.line === line && end.col > col) { count = Math.max(1, Math.min(end.col - col, 80 - ci)); } const pointer = " ".repeat(ci) + "^".repeat(count); error.message += `: ${lineStr} ${pointer} `; } }; exports.YAMLError = YAMLError; exports.YAMLParseError = YAMLParseError; exports.YAMLWarning = YAMLWarning; exports.prettifyError = prettifyError; } }); // ../../node_modules/yaml/dist/compose/resolve-props.js var require_resolve_props = __commonJS({ "../../node_modules/yaml/dist/compose/resolve-props.js"(exports) { "use strict"; function resolveProps(tokens, { flow, indicator, next, offset, onError, parentIndent, startOnNewline }) { let spaceBefore = false; let atNewline = startOnNewline; let hasSpace = startOnNewline; let comment = ""; let commentSep = ""; let hasNewline = false; let reqSpace = false; let tab = null; let anchor = null; let tag = null; let newlineAfterProp = null; let comma = null; let found = null; let start = null; for (const token of tokens) { if (reqSpace) { if (token.type !== "space" && token.type !== "newline" && token.type !== "comma") onError(token.offset, "MISSING_CHAR", "Tags and anchors must be separated from the next token by white space"); reqSpace = false; } if (tab) { if (atNewline && token.type !== "comment" && token.type !== "newline") { onError(tab, "TAB_AS_INDENT", "Tabs are not allowed as indentation"); } tab = null; } switch (token.type) { case "space": if (!flow && (indicator !== "doc-start" || next?.type !== "flow-collection") && token.source.includes(" ")) { tab = token; } hasSpace = true; break; case "comment": { if (!hasSpace) onError(token, "MISSING_CHAR", "Comments must be separated from other tokens by white space characters"); const cb = token.source.substring(1) || " "; if (!comment) comment = cb; else comment += commentSep + cb; commentSep = ""; atNewline = false; break; } case "newline": if (atNewline) { if (comment) comment += token.source; else if (!found || indicator !== "seq-item-ind") spaceBefore = true; } else commentSep += token.source; atNewline = true; hasNewline = true; if (anchor || tag) newlineAfterProp = token; hasSpace = true; break; case "anchor": if (anchor) onError(token, "MULTIPLE_ANCHORS", "A node can have at most one anchor"); if (token.source.endsWith(":")) onError(token.offset + token.source.length - 1, "BAD_ALIAS", "Anchor ending in : is ambiguous", true); anchor = token; start ?? (start = token.offset); atNewline = false; hasSpace = false; reqSpace = true; break; case "tag": { if (tag) onError(token, "MULTIPLE_TAGS", "A node can have at most one tag"); tag = token; start ?? (start = token.offset); atNewline = false; hasSpace = false; reqSpace = true; break; } case indicator: if (anchor || tag) onError(token, "BAD_PROP_ORDER", `Anchors and tags must be after the ${token.source} indicator`); if (found) onError(token, "UNEXPECTED_TOKEN", `Unexpected ${token.source} in ${flow ?? "collection"}`); found = token; atNewline = indicator === "seq-item-ind" || indicator === "explicit-key-ind"; hasSpace = false; break; case "comma": if (flow) { if (comma) onError(token, "UNEXPECTED_TOKEN", `Unexpected , in ${flow}`); comma = token; atNewline = false; hasSpace = false; break; } // else fallthrough default: onError(token, "UNEXPECTED_TOKEN", `Unexpected ${token.type} token`); atNewline = false; hasSpace = false; } } const last = tokens[tokens.length - 1]; const end = last ? last.offset + last.source.length : offset; if (reqSpace && next && next.type !== "space" && next.type !== "newline" && next.type !== "comma" && (next.type !== "scalar" || next.source !== "")) { onError(next.offset, "MISSING_CHAR", "Tags and anchors must be separated from the next token by white space"); } if (tab && (atNewline && tab.indent <= parentIndent || next?.type === "block-map" || next?.type === "block-seq")) onError(tab, "TAB_AS_INDENT", "Tabs are not allowed as indentation"); return { comma, found, spaceBefore, comment, hasNewline, anchor, tag, newlineAfterProp, end, start: start ?? end }; } exports.resolveProps = resolveProps; } }); // ../../node_modules/yaml/dist/compose/util-contains-newline.js var require_util_contains_newline = __commonJS({ "../../node_modules/yaml/dist/compose/util-contains-newline.js"(exports) { "use strict"; function containsNewline(key) { if (!key) return null; switch (key.type) { case "alias": case "scalar": case "double-quoted-scalar": case "single-quoted-scalar": if (key.source.includes("\n")) return true; if (key.end) { for (const st of key.end) if (st.type === "newline") return true; } return false; case "flow-collection": for (const it of key.items) { for (const st of it.start) if (st.type === "newline") return true; if (it.sep) { for (const st of it.sep) if (st.type === "newline") return true; } if (containsNewline(it.key) || containsNewline(it.value)) return true; } return false; default: return true; } } exports.containsNewline = containsNewline; } }); // ../../node_modules/yaml/dist/compose/util-flow-indent-check.js var require_util_flow_indent_check = __commonJS({ "../../node_modules/yaml/dist/compose/util-flow-indent-check.js"(exports) { "use strict"; var utilContainsNewline = require_util_contains_newline(); function flowIndentCheck(indent, fc, onError) { if (fc?.type === "flow-collection") { const end = fc.end[0]; if (end.indent === indent && (end.source === "]" || end.source === "}") && utilContainsNewline.containsNewline(fc)) { const msg = "Flow end indicator should be more indented than parent"; onError(end, "BAD_INDENT", msg, true); } } } exports.flowIndentCheck = flowIndentCheck; } }); // ../../node_modules/yaml/dist/compose/util-map-includes.js var require_util_map_includes = __commonJS({ "../../node_modules/yaml/dist/compose/util-map-includes.js"(exports) { "use strict"; var identity = require_identity(); function mapIncludes(ctx, items, search) { const { uniqueKeys } = ctx.options; if (uniqueKeys === false) return false; const isEqual = typeof uniqueKeys === "function" ? uniqueKeys : (a, b) => a === b || identity.isScalar(a) && identity.isScalar(b) && a.value === b.value; return items.some((pair) => isEqual(pair.key, search)); } exports.mapIncludes = mapIncludes; } }); // ../../node_modules/yaml/dist/compose/resolve-block-map.js var require_resolve_block_map = __commonJS({ "../../node_modules/yaml/dist/compose/resolve-block-map.js"(exports) { "use strict"; var Pair = require_Pair(); var YAMLMap = require_YAMLMap(); var resolveProps = require_resolve_props(); var utilContainsNewline = require_util_contains_newline(); var utilFlowIndentCheck = require_util_flow_indent_check(); var utilMapIncludes = require_util_map_includes(); var startColMsg = "All mapping items must start at the same column"; function resolveBlockMap({ composeNode, composeEmptyNode }, ctx, bm, onError, tag) { const NodeClass = tag?.nodeClass ?? YAMLMap.YAMLMap; const map = new NodeClass(ctx.schema); if (ctx.atRoot) ctx.atRoot = false; let offset = bm.offset; let commentEnd = null; for (const collItem of bm.items) { const { start, key, sep, value } = collItem; const keyProps = resolveProps.resolveProps(start, { indicator: "explicit-key-ind", next: key ?? sep?.[0], offset, onError, parentIndent: bm.indent, startOnNewline: true }); const implicitKey = !keyProps.found; if (implicitKey) { if (key) { if (key.type === "block-seq") onError(offset, "BLOCK_AS_IMPLICIT_KEY", "A block sequence may not be used as an implicit map key"); else if ("indent" in key && key.indent !== bm.indent) onError(offset, "BAD_INDENT", startColMsg); } if (!keyProps.anchor && !keyProps.tag && !sep) { commentEnd = keyProps.end; if (keyProps.comment) { if (map.comment) map.comment += "\n" + keyProps.comment; else map.comment = keyProps.comment; } continue; } if (keyProps.newlineAfterProp || utilContainsNewline.containsNewline(key)) { onError(key ?? start[start.length - 1], "MULTILINE_IMPLICIT_KEY", "Implicit keys need to be on a single line"); } } else if (keyProps.found?.indent !== bm.indent) { onError(offset, "BAD_INDENT", startColMsg); } ctx.atKey = true; const keyStart = keyProps.end; const keyNode = key ? composeNode(ctx, key, keyProps, onError) : composeEmptyNode(ctx, keyStart, start, null, keyProps, onError); if (ctx.schema.compat) utilFlowIndentCheck.flowIndentCheck(bm.indent, key, onError); ctx.atKey = false; if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode)) onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique"); const valueProps = resolveProps.resolveProps(sep ?? [], { indicator: "map-value-ind", next: value, offset: keyNode.range[2], onError, parentIndent: bm.indent, startOnNewline: !key || key.type === "block-scalar" }); offset = valueProps.end; if (valueProps.found) { if (implicitKey) { if (value?.type === "block-map" && !valueProps.hasNewline) onError(offset, "BLOCK_AS_IMPLICIT_KEY", "Nested mappings are not allowed in compact mappings"); if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024) onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key"); } const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep, null, valueProps, onError); if (ctx.schema.compat) utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError); offset = valueNode.range[2]; const pair = new Pair.Pair(keyNode, valueNode); if (ctx.options.keepSourceTokens) pair.srcToken = collItem; map.items.push(pair); } else { if (implicitKey) onError(keyNode.range, "MISSING_CHAR", "Implicit map keys need to be followed by map values"); if (valueProps.comment) { if (keyNode.comment) keyNode.comment += "\n" + valueProps.comment; else keyNode.comment = valueProps.comment; } const pair = new Pair.Pair(keyNode); if (ctx.options.keepSourceTokens) pair.srcToken = collItem; map.items.push(pair); } } if (commentEnd && commentEnd < offset) onError(commentEnd, "IMPOSSIBLE", "Map comment with trailing content"); map.range = [bm.offset, offset, commentEnd ?? offset]; return map; } exports.resolveBlockMap = resolveBlockMap; } }); // ../../node_modules/yaml/dist/compose/resolve-block-seq.js var require_resolve_block_seq = __commonJS({ "../../node_modules/yaml/dist/compose/resolve-block-seq.js"(exports) { "use strict"; var YAMLSeq = require_YAMLSeq(); var resolveProps = require_resolve_props(); var utilFlowIndentCheck = require_util_flow_indent_check(); function resolveBlockSeq({ composeNode, composeEmptyNode }, ctx, bs, onError, tag) { const NodeClass = tag?.nodeClass ?? YAMLSeq.YAMLSeq; const seq = new NodeClass(ctx.schema); if (ctx.atRoot) ctx.atRoot = false; if (ctx.atKey) ctx.atKey = false; let offset = bs.offset; let commentEnd = null; for (const { start, value } of bs.items) { const props = resolveProps.resolveProps(start, { indicator: "seq-item-ind", next: value, offset, onError, parentIndent: bs.indent, startOnNewline: true }); if (!props.found) { if (props.anchor || props.tag || value) { if (value?.type === "block-seq") onError(props.end, "BAD_INDENT", "All sequence items must start at the same column"); else onError(offset, "MISSING_CHAR", "Sequence item without - indicator"); } else { commentEnd = props.end; if (props.comment) seq.comment = props.comment; continue; } } const node = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, start, null, props, onError); if (ctx.schema.compat) utilFlowIndentCheck.flowIndentCheck(bs.indent, value, onError); offset = node.range[2]; seq.items.push(node); } seq.range = [bs.offset, offset, commentEnd ?? offset]; return seq; } exports.resolveBlockSeq = resolveBlockSeq; } }); // ../../node_modules/yaml/dist/compose/resolve-end.js var require_resolve_end = __commonJS({ "../../node_modules/yaml/dist/compose/resolve-end.js"(exports) { "use strict"; function resolveEnd(end, offset, reqSpace, onError) { let comment = ""; if (end) { let hasSpace = false; let sep = ""; for (const token of end) { const { source, type } = token; switch (type) { case "space": hasSpace = true; break; case "comment": { if (reqSpace && !hasSpace) onError(token, "MISSING_CHAR", "Comments must be separated from other tokens by white space characters"); const cb = source.substring(1) || " "; if (!comment) comment = cb; else comment += sep + cb; sep = ""; break; } case "newline": if (comment) sep += source; hasSpace = true; break; default: onError(token, "UNEXPECTED_TOKEN", `Unexpected ${type} at node end`); } offset += source.length; } } return { comment, offset }; } exports.resolveEnd = resolveEnd; } }); // ../../node_modules/yaml/dist/compose/resolve-flow-collection.js var require_resolve_flow_collection = __commonJS({ "../../node_modules/yaml/dist/compose/resolve-flow-collection.js"(exports) { "use strict"; var identity = require_identity(); var Pair = require_Pair(); var YAMLMap = require_YAMLMap(); var YAMLSeq = require_YAMLSeq(); var resolveEnd = require_resolve_end(); var resolveProps = require_resolve_props(); var utilContainsNewline = require_util_contains_newline(); var utilMapIncludes = require_util_map_includes(); var blockMsg = "Block collections are not allowed within flow collections"; var isBlock = (token) => token && (token.type === "block-map" || token.type === "block-seq"); function resolveFlowCollection({ composeNode, composeEmptyNode }, ctx, fc, onError, tag) { const isMap = fc.start.source === "{"; const fcName = isMap ? "flow map" : "flow sequence"; const NodeClass = tag?.nodeClass ?? (isMap ? YAMLMap.YAMLMap : YAMLSeq.YAMLSeq); const coll = new NodeClass(ctx.schema); coll.flow = true; const atRoot = ctx.atRoot; if (atRoot) ctx.atRoot = false; if (ctx.atKey) ctx.atKey = false; let offset = fc.offset + fc.start.source.length; for (let i = 0; i < fc.items.length; ++i) { const collItem = fc.items[i]; const { start, key, sep, value } = collItem; const props = resolveProps.resolveProps(start, { flow: fcName, indicator: "explicit-key-ind", next: key ?? sep?.[0], offset, onError, parentIndent: fc.indent, startOnNewline: false }); if (!props.found) { if (!props.anchor && !props.tag && !sep && !value) { if (i === 0 && props.comma) onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`); else if (i < fc.items.length - 1) onError(props.start, "UNEXPECTED_TOKEN", `Unexpected empty item in ${fcName}`); if (props.comment) { if (coll.comment) coll.comment += "\n" + props.comment; else coll.comment = props.comment; } offset = props.end; continue; } if (!isMap && ctx.options.strict && utilContainsNewline.containsNewline(key)) onError( key, // checked by containsNewline() "MULTILINE_IMPLICIT_KEY", "Implicit keys of flow sequence pairs need to be on a single line" ); } if (i === 0) { if (props.comma) onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`); } else { if (!props.comma) onError(props.start, "MISSING_CHAR", `Missing , between ${fcName} items`); if (props.comment) { let prevItemComment = ""; loop: for (const st of start) { switch (st.type) { case "comma": case "space": break; case "comment": prevItemComment = st.source.substring(1); break loop; default: break loop; } } if (prevItemComment) { let prev = coll.items[coll.items.length - 1]; if (identity.isPair(prev)) prev = prev.value ?? prev.key; if (prev.comment) prev.comment += "\n" + prevItemComment; else prev.comment = prevItemComment; props.comment = props.comment.substring(prevItemComment.length + 1); } } } if (!isMap && !sep && !props.found) { const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep, null, props, onError); coll.items.push(valueNode); offset = valueNode.range[2]; if (isBlock(value)) onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg); } else { ctx.atKey = true; const keyStart = props.end; const keyNode = key ? composeNode(ctx, key, props, onError) : composeEmptyNode(ctx, keyStart, start, null, props, onError); if (isBlock(key)) onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg); ctx.atKey = false; const valueProps = resolveProps.resolveProps(sep ?? [], { flow: fcName, indicator: "map-value-ind", next: value, offset: keyNode.range[2], onError, parentIndent: fc.indent, startOnNewline: false }); if (valueProps.found) { if (!isMap && !props.found && ctx.options.strict) { if (sep) for (const st of sep) { if (st === valueProps.found) break; if (st.type === "newline") { onError(st, "MULTILINE_IMPLICIT_KEY", "Implicit keys of flow sequence pairs need to be on a single line"); break; } } if (props.start < valueProps.found.offset - 1024) onError(valueProps.found, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit flow sequence key"); } } else if (value) { if ("source" in value && value.source?.[0] === ":") onError(value, "MISSING_CHAR", `Missing space after : in ${fcName}`); else onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`); } const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep, null, valueProps, onError) : null; if (valueNode) { if (isBlock(value)) onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg); } else if (valueProps.comment) { if (keyNode.comment) keyNode.comment += "\n" + valueProps.comment; else keyNode.comment = valueProps.comment; } const pair = new Pair.Pair(keyNode, valueNode); if (ctx.options.keepSourceTokens) pair.srcToken = collItem; if (isMap) { const map = coll; if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode)) onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique"); map.items.push(pair); } else { const map = new YAMLMap.YAMLMap(ctx.schema); map.flow = true; map.items.push(pair); const endRange = (valueNode ?? keyNode).range; map.range = [keyNode.range[0], endRange[1], endRange[2]]; coll.items.push(map); } offset = valueNode ? valueNode.range[2] : valueProps.end; } } const expectedEnd = isMap ? "}" : "]"; const [ce, ...ee] = fc.end; let cePos = offset; if (ce?.source === expectedEnd) cePos = ce.offset + ce.source.length; else { const name = fcName[0].toUpperCase() + fcName.substring(1); const msg = atRoot ? `${name} must end with a ${expectedEnd}` : `${name} in block collection must be sufficiently indented and end with a ${expectedEnd}`; onError(offset, atRoot ? "MISSING_CHAR" : "BAD_INDENT", msg); if (ce && ce.source.length !== 1) ee.unshift(ce); } if (ee.length > 0) { const end = resolveEnd.resolveEnd(ee, cePos, ctx.options.strict, onError); if (end.comment) { if (coll.comment) coll.comment += "\n" + end.comment; else coll.comment = end.comment; } coll.range = [fc.offset, cePos, end.offset]; } else { coll.range = [fc.offset, cePos, cePos]; } return coll; } exports.resolveFlowCollection = resolveFlowCollection; } }); // ../../node_modules/yaml/dist/compose/compose-collection.js var require_compose_collection = __commonJS({ "../../node_modules/yaml/dist/compose/compose-collection.js"(exports) { "use strict"; var identity = require_identity(); var Scalar = require_Scalar(); var YAMLMap = require_YAMLMap(); var YAMLSeq = require_YAMLSeq(); var resolveBlockMap = require_resolve_block_map(); var resolveBlockSeq = require_resolve_block_seq(); var resolveFlowCollection = require_resolve_flow_collection(); function resolveCollection(CN, ctx, token, onError, tagName, tag) { const coll = token.type === "block-map" ? resolveBlockMap.resolveBlockMap(CN, ctx, token, onError, tag) : token.type === "block-seq" ? resolveBlockSeq.resolveBlockSeq(CN, ctx, token, onError, tag) : resolveFlowCollection.resolveFlowCollection(CN, ctx, token, onError, tag); const Coll = coll.constructor; if (tagName === "!" || tagName === Coll.tagName) { coll.tag = Coll.tagName; return coll; } if (tagName) coll.tag = tagName; return coll; } function composeCollection(CN, ctx, token, props, onError) { const tagToken = props.tag; const tagName = !tagToken ? null : ctx.directives.tagName(tagToken.source, (msg) => onError(tagToken, "TAG_RESOLVE_FAILED", msg)); if (token.type === "block-seq") { const { anchor, newlineAfterProp: nl } = props; const lastProp = anchor && tagToken ? anchor.offset > tagToken.offset ? anchor : tagToken : anchor ?? tagToken; if (lastProp && (!nl || nl.offset < lastProp.offset)) { const message = "Missing newline after block sequence props"; onError(lastProp, "MISSING_CHAR", message); } } const expType = token.type === "block-map" ? "map" : token.type === "block-seq" ? "seq" : token.start.source === "{" ? "map" : "seq"; if (!tagToken || !tagName || tagName === "!" || tagName === YAMLMap.YAMLMap.tagName && expType === "map" || tagName === YAMLSeq.YAMLSeq.tagName && expType === "seq") { return resolveCollection(CN, ctx, token, onError, tagName); } let tag = ctx.schema.tags.find((t) => t.tag === tagName && t.collection === expType); if (!tag) { const kt = ctx.schema.knownTags[tagName]; if (kt?.collection === expType) { ctx.schema.tags.push(Object.assign({}, kt, { default: false })); tag = kt; } else { if (kt) { onError(tagToken, "BAD_COLLECTION_TYPE", `${kt.tag} used for ${expType} collection, but expects ${kt.collection ?? "scalar"}`, true); } else { onError(tagToken, "TAG_RESOLVE_FAILED", `Unresolved tag: ${tagName}`, true); } return resolveCollection(CN, ctx, token, onError, tagName); } } const coll = resolveCollection(CN, ctx, token, onError, tagName, tag); const res = tag.resolve?.(coll, (msg) => onError(tagToken, "TAG_RESOLVE_FAILED", msg), ctx.options) ?? coll; const node = identity.isNode(res) ? res : new Scalar.Scalar(res); node.range = coll.range; node.tag = tagName; if (tag?.format) node.format = tag.format; return node; } exports.composeCollection = composeCollection; } }); // ../../node_modules/yaml/dist/compose/resolve-block-scalar.js var require_resolve_block_scalar = __commonJS({ "../../node_modules/yaml/dist/compose/resolve-block-scalar.js"(exports) { "use strict"; var Scalar = require_Scalar(); function resolveBlockScalar(ctx, scalar, onError) { const start = scalar.offset; const header = parseBlockScalarHeader(scalar, ctx.options.strict, onError); if (!header) return { value: "", type: null, comment: "", range: [start, start, start] }; const type = header.mode === ">" ? Scalar.Scalar.BLOCK_FOLDED : Scalar.Scalar.BLOCK_LITERAL; const lines = scalar.source ? splitLines(scalar.source) : []; let chompStart = lines.length; for (let i = lines.length - 1; i >= 0; --i) { const content = lines[i][1]; if (content === "" || content === "\r") chompStart = i; else break; } if (chompStart === 0) { const value2 = header.chomp === "+" && lines.length > 0 ? "\n".repeat(Math.max(1, lines.length - 1)) : ""; let end2 = start + header.length; if (scalar.source) end2 += scalar.source.length; return { value: value2, type, comment: header.comment, range: [start, end2, end2] }; } let trimIndent = scalar.indent + header.indent; let offset = scalar.offset + header.length; let contentStart = 0; for (let i = 0; i < chompStart; ++i) { const [indent, content] = lines[i]; if (content === "" || content === "\r") { if (header.indent === 0 && indent.length > trimIndent) trimIndent = indent.length; } else { if (indent.length < trimIndent) { const message = "Block scalars with more-indented leading empty lines must use an explicit indentation indicator"; onError(offset + indent.length, "MISSING_CHAR", message); } if (header.indent === 0) trimIndent = indent.length; contentStart = i; if (trimIndent === 0 && !ctx.atRoot) { const message = "Block scalar values in collections must be indented"; onError(offset, "BAD_INDENT", message); } break; } offset += indent.length + content.length + 1; } for (let i = lines.length - 1; i >= chompStart; --i) { if (lines[i][0].length > trimIndent) chompStart = i + 1; } let value = ""; let sep = ""; let prevMoreIndented = false; for (let i = 0; i < contentStart; ++i) value += lines[i][0].slice(trimIndent) + "\n"; for (let i = contentStart; i < chompStart; ++i) { let [indent, content] = lines[i]; offset += indent.length + content.length + 1; const crlf = content[content.length - 1] === "\r"; if (crlf) content = content.slice(0, -1); if (content && indent.length < trimIndent) { const src = header.indent ? "explicit indentation indicator" : "first line"; const message = `Block scalar lines must not be less indented than their ${src}`; onError(offset - content.length - (crlf ? 2 : 1), "BAD_INDENT", message); indent = ""; } if (type === Scalar.Scalar.BLOCK_LITERAL) { value += sep + indent.slice(trimIndent) + content; sep = "\n"; } else if (indent.length > trimIndent || content[0] === " ") { if (sep === " ") sep = "\n"; else if (!prevMoreIndented && sep === "\n") sep = "\n\n"; value += sep + indent.slice(trimIndent) + content; sep = "\n"; prevMoreIndented = true; } else if (content === "") { if (sep === "\n") value += "\n"; else sep = "\n"; } else { value += sep + content; sep = " "; prevMoreIndented = false; } } switch (header.chomp) { case "-": break; case "+": for (let i = chompStart; i < lines.length; ++i) value += "\n" + lines[i][0].slice(trimIndent); if (value[value.length - 1] !== "\n") value += "\n"; break; default: value += "\n"; } const end = start + header.length + scalar.source.length; return { value, type, comment: header.comment, range: [start, end, end] }; } function parseBlockScalarHeader({ offset, props }, strict, onError) { if (props[0].type !== "block-scalar-header") { onError(props[0], "IMPOSSIBLE", "Block scalar header not found"); return null; } const { source } = props[0]; const mode = source[0]; let indent = 0; let chomp = ""; let error = -1; for (let i = 1; i < source.length; ++i) { const ch = source[i]; if (!chomp && (ch === "-" || ch === "+")) chomp = ch; else { const n = Number(ch); if (!indent && n) indent = n; else if (error === -1) error = offset + i; } } if (error !== -1) onError(error, "UNEXPECTED_TOKEN", `Block scalar header includes extra characters: ${source}`); let hasSpace = false; let comment = ""; let length = source.length; for (let i = 1; i < props.length; ++i) { const token = props[i]; switch (token.type) { case "space": hasSpace = true; // fallthrough case "newline": length += token.source.length; break; case "comment": if (strict && !hasSpace) { const message = "Comments must be separated from other tokens by white space characters"; onError(token, "MISSING_CHAR", message); } length += token.source.length; comment = token.source.substring(1); break; case "error": onError(token, "UNEXPECTED_TOKEN", token.message); length += token.source.length; break; /* istanbul ignore next should not happen */ default: { const message = `Unexpected token in block scalar header: ${token.type}`; onError(token, "UNEXPECTED_TOKEN", message); const ts = token.source; if (ts && typeof ts === "string") length += ts.length; } } } return { mode, indent, chomp, comment, length }; } function splitLines(source) { const split = source.split(/\n( *)/); const first = split[0]; const m = first.match(/^( *)/); const line0 = m?.[1] ? [m[1], first.slice(m[1].length)] : ["", first]; const lines = [line0]; for (let i = 1; i < split.length; i += 2) lines.push([split[i], split[i + 1]]); return lines; } exports.resolveBlockScalar = resolveBlockScalar; } }); // ../../node_modules/yaml/dist/compose/resolve-flow-scalar.js var require_resolve_flow_scalar = __commonJS({ "../../node_modules/yaml/dist/compose/resolve-flow-scalar.js"(exports) { "use strict"; var Scalar = require_Scalar(); var resolveEnd = require_resolve_end(); function resolveFlowScalar(scalar, strict, onError) { const { offset, type, source, end } = scalar; let _type; let value; const _onError = (rel, code, msg) => onError(offset + rel, code, msg); switch (type) { case "scalar": _type = Scalar.Scalar.PLAIN; value = plainValue(source, _onError); break; case "single-quoted-scalar": _type = Scalar.Scalar.QUOTE_SINGLE; value = singleQuotedValue(source, _onError); break; case "double-quoted-scalar": _type = Scalar.Scalar.QUOTE_DOUBLE; value = doubleQuotedValue(source, _onError); break; /* istanbul ignore next should not happen */ default: onError(scalar, "UNEXPECTED_TOKEN", `Expected a flow scalar value, but found: ${type}`); return { value: "", type: null, comment: "", range: [offset, offset + source.length, offset + source.length] }; } const valueEnd = offset + source.length; const re = resolveEnd.resolveEnd(end, valueEnd, strict, onError); return { value, type: _type, comment: re.comment, range: [offset, valueEnd, re.offset] }; } function plainValue(source, onError) { let badChar = ""; switch (source[0]) { /* istanbul ignore next should not happen */ case " ": badChar = "a tab character"; break; case ",": badChar = "flow indicator character ,"; break; case "%": badChar = "directive indicator character %"; break; case "|": case ">": { badChar = `block scalar indicator ${source[0]}`; break; } case "@": case "`": { badChar = `reserved character ${source[0]}`; break; } } if (badChar) onError(0, "BAD_SCALAR_START", `Plain value cannot start with ${badChar}`); return unfoldLines(source); } function singleQuotedValue(source, onError) { if (source[source.length - 1] !== "'" || source.length === 1) onError(source.length, "MISSING_CHAR", "Missing closing 'quote"); return unfoldLines(source.slice(1, -1)).replace(/''/g, "'"); } function unfoldLines(source) { const line = /(.*?)\r?\n/sy; let match = line.exec(source); if (!match) return source; let trimEnd, trimBoth; try { trimEnd = new RegExp("(? wsStart ? source.slice(wsStart, i + 1) : ch; } else { res += ch; } } if (source[source.length - 1] !== '"' || source.length === 1) onError(source.length, "MISSING_CHAR", 'Missing closing "quote'); return res; } function foldNewline(source, offset) { let fold = ""; let ch = source[offset + 1]; while (ch === " " || ch === " " || ch === "\n" || ch === "\r") { if (ch === "\r" && source[offset + 2] !== "\n") break; if (ch === "\n") fold += "\n"; offset += 1; ch = source[offset + 1]; } if (!fold) fold = " "; return { fold, offset }; } var escapeCodes = { "0": "\0", // null character a: "\x07", // bell character b: "\b", // backspace e: "\x1B", // escape character f: "\f", // form feed n: "\n", // line feed r: "\r", // carriage return t: " ", // horizontal tab v: "\v", // vertical tab N: "\x85", // Unicode next line _: "\xA0", // Unicode non-breaking space L: "\u2028", // Unicode line separator P: "\u2029", // Unicode paragraph separator " ": " ", '"': '"', "/": "/", "\\": "\\", " ": " " }; function parseCharCode(source, offset, length, onError) { const cc = source.substr(offset, length); const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc); const code = ok ? parseInt(cc, 16) : NaN; try { return String.fromCodePoint(code); } catch { const raw = source.substr(offset - 2, length + 2); onError(offset - 2, "BAD_DQ_ESCAPE", `Invalid escape sequence ${raw}`); return raw; } } exports.resolveFlowScalar = resolveFlowScalar; } }); // ../../node_modules/yaml/dist/compose/compose-scalar.js var require_compose_scalar = __commonJS({ "../../node_modules/yaml/dist/compose/compose-scalar.js"(exports) { "use strict"; var identity = require_identity(); var Scalar = require_Scalar(); var resolveBlockScalar = require_resolve_block_scalar(); var resolveFlowScalar = require_resolve_flow_scalar(); function composeScalar(ctx, token, tagToken, onError) { const { value, type, comment, range } = token.type === "block-scalar" ? resolveBlockScalar.resolveBlockScalar(ctx, token, onError) : resolveFlowScalar.resolveFlowScalar(token, ctx.options.strict, onError); const tagName = tagToken ? ctx.directives.tagName(tagToken.source, (msg) => onError(tagToken, "TAG_RESOLVE_FAILED", msg)) : null; let tag; if (ctx.options.stringKeys && ctx.atKey) { tag = ctx.schema[identity.SCALAR]; } else if (tagName) tag = findScalarTagByName(ctx.schema, value, tagName, tagToken, onError); else if (token.type === "scalar") tag = findScalarTagByTest(ctx, value, token, onError); else tag = ctx.schema[identity.SCALAR]; let scalar; try { const res = tag.resolve(value, (msg) => onError(tagToken ?? token, "TAG_RESOLVE_FAILED", msg), ctx.options); scalar = identity.isScalar(res) ? res : new Scalar.Scalar(res); } catch (error) { const msg = error instanceof Error ? error.message : String(error); onError(tagToken ?? token, "TAG_RESOLVE_FAILED", msg); scalar = new Scalar.Scalar(value); } scalar.range = range; scalar.source = value; if (type) scalar.type = type; if (tagName) scalar.tag = tagName; if (tag.format) scalar.format = tag.format; if (comment) scalar.comment = comment; return scalar; } function findScalarTagByName(schema, value, tagName, tagToken, onError) { if (tagName === "!") return schema[identity.SCALAR]; const matchWithTest = []; for (const tag of schema.tags) { if (!tag.collection && tag.tag === tagName) { if (tag.default && tag.test) matchWithTest.push(tag); else return tag; } } for (const tag of matchWithTest) if (tag.test?.test(value)) return tag; const kt = schema.knownTags[tagName]; if (kt && !kt.collection) { schema.tags.push(Object.assign({}, kt, { default: false, test: void 0 })); return kt; } onError(tagToken, "TAG_RESOLVE_FAILED", `Unresolved tag: ${tagName}`, tagName !== "tag:yaml.org,2002:str"); return schema[identity.SCALAR]; } function findScalarTagByTest({ atKey, directives, schema }, value, token, onError) { const tag = schema.tags.find((tag2) => (tag2.default === true || atKey && tag2.default === "key") && tag2.test?.test(value)) || schema[identity.SCALAR]; if (schema.compat) { const compat = schema.compat.find((tag2) => tag2.default && tag2.test?.test(value)) ?? schema[identity.SCALAR]; if (tag.tag !== compat.tag) { const ts = directives.tagString(tag.tag); const cs = directives.tagString(compat.tag); const msg = `Value may be parsed as either ${ts} or ${cs}`; onError(token, "TAG_RESOLVE_FAILED", msg, true); } } return tag; } exports.composeScalar = composeScalar; } }); // ../../node_modules/yaml/dist/compose/util-empty-scalar-position.js var require_util_empty_scalar_position = __commonJS({ "../../node_modules/yaml/dist/compose/util-empty-scalar-position.js"(exports) { "use strict"; function emptyScalarPosition(offset, before, pos) { if (before) { pos ?? (pos = before.length); for (let i = pos - 1; i >= 0; --i) { let st = before[i]; switch (st.type) { case "space": case "comment": case "newline": offset -= st.source.length; continue; } st = before[++i]; while (st?.type === "space") { offset += st.source.length; st = before[++i]; } break; } } return offset; } exports.emptyScalarPosition = emptyScalarPosition; } }); // ../../node_modules/yaml/dist/compose/compose-node.js var require_compose_node = __commonJS({ "../../node_modules/yaml/dist/compose/compose-node.js"(exports) { "use strict"; var Alias = require_Alias(); var identity = require_identity(); var composeCollection = require_compose_collection(); var composeScalar = require_compose_scalar(); var resolveEnd = require_resolve_end(); var utilEmptyScalarPosition = require_util_empty_scalar_position(); var CN = { composeNode, composeEmptyNode }; function composeNode(ctx, token, props, onError) { const atKey = ctx.atKey; const { spaceBefore, comment, anchor, tag } = props; let node; let isSrcToken = true; switch (token.type) { case "alias": node = composeAlias(ctx, token, onError); if (anchor || tag) onError(token, "ALIAS_PROPS", "An alias node must not specify any properties"); break; case "scalar": case "single-quoted-scalar": case "double-quoted-scalar": case "block-scalar": node = composeScalar.composeScalar(ctx, token, tag, onError); if (anchor) node.anchor = anchor.source.substring(1); break; case "block-map": case "block-seq": case "flow-collection": try { node = composeCollection.composeCollection(CN, ctx, token, props, onError); if (anchor) node.anchor = anchor.source.substring(1); } catch (error) { const message = error instanceof Error ? error.message : String(error); onError(token, "RESOURCE_EXHAUSTION", message); } break; default: { const message = token.type === "error" ? token.message : `Unsupported token (type: ${token.type})`; onError(token, "UNEXPECTED_TOKEN", message); isSrcToken = false; } } node ?? (node = composeEmptyNode(ctx, token.offset, void 0, null, props, onError)); if (anchor && node.anchor === "") onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string"); if (atKey && ctx.options.stringKeys && (!identity.isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) { const msg = "With stringKeys, all keys must be strings"; onError(tag ?? token, "NON_STRING_KEY", msg); } if (spaceBefore) node.spaceBefore = true; if (comment) { if (token.type === "scalar" && token.source === "") node.comment = comment; else node.commentBefore = comment; } if (ctx.options.keepSourceTokens && isSrcToken) node.srcToken = token; return node; } function composeEmptyNode(ctx, offset, before, pos, { spaceBefore, comment, anchor, tag, end }, onError) { const token = { type: "scalar", offset: utilEmptyScalarPosition.emptyScalarPosition(offset, before, pos), indent: -1, source: "" }; const node = composeScalar.composeScalar(ctx, token, tag, onError); if (anchor) { node.anchor = anchor.source.substring(1); if (node.anchor === "") onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string"); } if (spaceBefore) node.spaceBefore = true; if (comment) { node.comment = comment; node.range[2] = end; } return node; } function composeAlias({ options }, { offset, source, end }, onError) { const alias = new Alias.Alias(source.substring(1)); if (alias.source === "") onError(offset, "BAD_ALIAS", "Alias cannot be an empty string"); if (alias.source.endsWith(":")) onError(offset + source.length - 1, "BAD_ALIAS", "Alias ending in : is ambiguous", true); const valueEnd = offset + source.length; const re = resolveEnd.resolveEnd(end, valueEnd, options.strict, onError); alias.range = [offset, valueEnd, re.offset]; if (re.comment) alias.comment = re.comment; return alias; } exports.composeEmptyNode = composeEmptyNode; exports.composeNode = composeNode; } }); // ../../node_modules/yaml/dist/compose/compose-doc.js var require_compose_doc = __commonJS({ "../../node_modules/yaml/dist/compose/compose-doc.js"(exports) { "use strict"; var Document = require_Document(); var composeNode = require_compose_node(); var resolveEnd = require_resolve_end(); var resolveProps = require_resolve_props(); function composeDoc(options, directives, { offset, start, value, end }, onError) { const opts = Object.assign({ _directives: directives }, options); const doc = new Document.Document(void 0, opts); const ctx = { atKey: false, atRoot: true, directives: doc.directives, options: doc.options, schema: doc.schema }; const props = resolveProps.resolveProps(start, { indicator: "doc-start", next: value ?? end?.[0], offset, onError, parentIndent: 0, startOnNewline: true }); if (props.found) { doc.directives.docStart = true; if (value && (value.type === "block-map" || value.type === "block-seq") && !props.hasNewline) onError(props.end, "MISSING_CHAR", "Block collection cannot start on same line with directives-end marker"); } doc.contents = value ? composeNode.composeNode(ctx, value, props, onError) : composeNode.composeEmptyNode(ctx, props.end, start, null, props, onError); const contentEnd = doc.contents.range[2]; const re = resolveEnd.resolveEnd(end, contentEnd, false, onError); if (re.comment) doc.comment = re.comment; doc.range = [offset, contentEnd, re.offset]; return doc; } exports.composeDoc = composeDoc; } }); // ../../node_modules/yaml/dist/compose/composer.js var require_composer = __commonJS({ "../../node_modules/yaml/dist/compose/composer.js"(exports) { "use strict"; var node_process = __require("process"); var directives = require_directives(); var Document = require_Document(); var errors = require_errors(); var identity = require_identity(); var composeDoc = require_compose_doc(); var resolveEnd = require_resolve_end(); function getErrorPos(src) { if (typeof src === "number") return [src, src + 1]; if (Array.isArray(src)) return src.length === 2 ? src : [src[0], src[1]]; const { offset, source } = src; return [offset, offset + (typeof source === "string" ? source.length : 1)]; } function parsePrelude(prelude) { let comment = ""; let atComment = false; let afterEmptyLine = false; for (let i = 0; i < prelude.length; ++i) { const source = prelude[i]; switch (source[0]) { case "#": comment += (comment === "" ? "" : afterEmptyLine ? "\n\n" : "\n") + (source.substring(1) || " "); atComment = true; afterEmptyLine = false; break; case "%": if (prelude[i + 1]?.[0] !== "#") i += 1; atComment = false; break; default: if (!atComment) afterEmptyLine = true; atComment = false; } } return { comment, afterEmptyLine }; } var Composer = class { constructor(options = {}) { this.doc = null; this.atDirectives = false; this.prelude = []; this.errors = []; this.warnings = []; this.onError = (source, code, message, warning) => { const pos = getErrorPos(source); if (warning) this.warnings.push(new errors.YAMLWarning(pos, code, message)); else this.errors.push(new errors.YAMLParseError(pos, code, message)); }; this.directives = new directives.Directives({ version: options.version || "1.2" }); this.options = options; } decorate(doc, afterDoc) { const { comment, afterEmptyLine } = parsePrelude(this.prelude); if (comment) { const dc = doc.contents; if (afterDoc) { doc.comment = doc.comment ? `${doc.comment} ${comment}` : comment; } else if (afterEmptyLine || doc.directives.docStart || !dc) { doc.commentBefore = comment; } else if (identity.isCollection(dc) && !dc.flow && dc.items.length > 0) { let it = dc.items[0]; if (identity.isPair(it)) it = it.key; const cb = it.commentBefore; it.commentBefore = cb ? `${comment} ${cb}` : comment; } else { const cb = dc.commentBefore; dc.commentBefore = cb ? `${comment} ${cb}` : comment; } } if (afterDoc) { for (let i = 0; i < this.errors.length; ++i) doc.errors.push(this.errors[i]); for (let i = 0; i < this.warnings.length; ++i) doc.warnings.push(this.warnings[i]); } else { doc.errors = this.errors; doc.warnings = this.warnings; } this.prelude = []; this.errors = []; this.warnings = []; } /** * Current stream status information. * * Mostly useful at the end of input for an empty stream. */ streamInfo() { return { comment: parsePrelude(this.prelude).comment, directives: this.directives, errors: this.errors, warnings: this.warnings }; } /** * Compose tokens into documents. * * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. */ *compose(tokens, forceDoc = false, endOffset = -1) { for (const token of tokens) yield* this.next(token); yield* this.end(forceDoc, endOffset); } /** Advance the composer by one CST token. */ *next(token) { if (node_process.env.LOG_STREAM) console.dir(token, { depth: null }); switch (token.type) { case "directive": this.directives.add(token.source, (offset, message, warning) => { const pos = getErrorPos(token); pos[0] += offset; this.onError(pos, "BAD_DIRECTIVE", message, warning); }); this.prelude.push(token.source); this.atDirectives = true; break; case "document": { const doc = composeDoc.composeDoc(this.options, this.directives, token, this.onError); if (this.atDirectives && !doc.directives.docStart) this.onError(token, "MISSING_CHAR", "Missing directives-end/doc-start indicator line"); this.decorate(doc, false); if (this.doc) yield this.doc; this.doc = doc; this.atDirectives = false; break; } case "byte-order-mark": case "space": break; case "comment": case "newline": this.prelude.push(token.source); break; case "error": { const msg = token.source ? `${token.message}: ${JSON.stringify(token.source)}` : token.message; const error = new errors.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg); if (this.atDirectives || !this.doc) this.errors.push(error); else this.doc.errors.push(error); break; } case "doc-end": { if (!this.doc) { const msg = "Unexpected doc-end without preceding document"; this.errors.push(new errors.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", msg)); break; } this.doc.directives.docEnd = true; const end = resolveEnd.resolveEnd(token.end, token.offset + token.source.length, this.doc.options.strict, this.onError); this.decorate(this.doc, true); if (end.comment) { const dc = this.doc.comment; this.doc.comment = dc ? `${dc} ${end.comment}` : end.comment; } this.doc.range[2] = end.offset; break; } default: this.errors.push(new errors.YAMLParseError(getErrorPos(token), "UNEXPECTED_TOKEN", `Unsupported token ${token.type}`)); } } /** * Call at end of input to yield any remaining document. * * @param forceDoc - If the stream contains no document, still emit a final document including any comments and directives that would be applied to a subsequent document. * @param endOffset - Should be set if `forceDoc` is also set, to set the document range end and to indicate errors correctly. */ *end(forceDoc = false, endOffset = -1) { if (this.doc) { this.decorate(this.doc, true); yield this.doc; this.doc = null; } else if (forceDoc) { const opts = Object.assign({ _directives: this.directives }, this.options); const doc = new Document.Document(void 0, opts); if (this.atDirectives) this.onError(endOffset, "MISSING_CHAR", "Missing directives-end indicator line"); doc.range = [0, endOffset, endOffset]; this.decorate(doc, false); yield doc; } } }; exports.Composer = Composer; } }); // ../../node_modules/yaml/dist/parse/cst-scalar.js var require_cst_scalar = __commonJS({ "../../node_modules/yaml/dist/parse/cst-scalar.js"(exports) { "use strict"; var resolveBlockScalar = require_resolve_block_scalar(); var resolveFlowScalar = require_resolve_flow_scalar(); var errors = require_errors(); var stringifyString = require_stringifyString(); function resolveAsScalar(token, strict = true, onError) { if (token) { const _onError = (pos, code, message) => { const offset = typeof pos === "number" ? pos : Array.isArray(pos) ? pos[0] : pos.offset; if (onError) onError(offset, code, message); else throw new errors.YAMLParseError([offset, offset + 1], code, message); }; switch (token.type) { case "scalar": case "single-quoted-scalar": case "double-quoted-scalar": return resolveFlowScalar.resolveFlowScalar(token, strict, _onError); case "block-scalar": return resolveBlockScalar.resolveBlockScalar({ options: { strict } }, token, _onError); } } return null; } function createScalarToken(value, context) { const { implicitKey = false, indent, inFlow = false, offset = -1, type = "PLAIN" } = context; const source = stringifyString.stringifyString({ type, value }, { implicitKey, indent: indent > 0 ? " ".repeat(indent) : "", inFlow, options: { blockQuote: true, lineWidth: -1 } }); const end = context.end ?? [ { type: "newline", offset: -1, indent, source: "\n" } ]; switch (source[0]) { case "|": case ">": { const he = source.indexOf("\n"); const head = source.substring(0, he); const body = source.substring(he + 1) + "\n"; const props = [ { type: "block-scalar-header", offset, indent, source: head } ]; if (!addEndtoBlockProps(props, end)) props.push({ type: "newline", offset: -1, indent, source: "\n" }); return { type: "block-scalar", offset, indent, props, source: body }; } case '"': return { type: "double-quoted-scalar", offset, indent, source, end }; case "'": return { type: "single-quoted-scalar", offset, indent, source, end }; default: return { type: "scalar", offset, indent, source, end }; } } function setScalarValue(token, value, context = {}) { let { afterKey = false, implicitKey = false, inFlow = false, type } = context; let indent = "indent" in token ? token.indent : null; if (afterKey && typeof indent === "number") indent += 2; if (!type) switch (token.type) { case "single-quoted-scalar": type = "QUOTE_SINGLE"; break; case "double-quoted-scalar": type = "QUOTE_DOUBLE"; break; case "block-scalar": { const header = token.props[0]; if (header.type !== "block-scalar-header") throw new Error("Invalid block scalar header"); type = header.source[0] === ">" ? "BLOCK_FOLDED" : "BLOCK_LITERAL"; break; } default: type = "PLAIN"; } const source = stringifyString.stringifyString({ type, value }, { implicitKey: implicitKey || indent === null, indent: indent !== null && indent > 0 ? " ".repeat(indent) : "", inFlow, options: { blockQuote: true, lineWidth: -1 } }); switch (source[0]) { case "|": case ">": setBlockScalarValue(token, source); break; case '"': setFlowScalarValue(token, source, "double-quoted-scalar"); break; case "'": setFlowScalarValue(token, source, "single-quoted-scalar"); break; default: setFlowScalarValue(token, source, "scalar"); } } function setBlockScalarValue(token, source) { const he = source.indexOf("\n"); const head = source.substring(0, he); const body = source.substring(he + 1) + "\n"; if (token.type === "block-scalar") { const header = token.props[0]; if (header.type !== "block-scalar-header") throw new Error("Invalid block scalar header"); header.source = head; token.source = body; } else { const { offset } = token; const indent = "indent" in token ? token.indent : -1; const props = [ { type: "block-scalar-header", offset, indent, source: head } ]; if (!addEndtoBlockProps(props, "end" in token ? token.end : void 0)) props.push({ type: "newline", offset: -1, indent, source: "\n" }); for (const key of Object.keys(token)) if (key !== "type" && key !== "offset") delete token[key]; Object.assign(token, { type: "block-scalar", indent, props, source: body }); } } function addEndtoBlockProps(props, end) { if (end) for (const st of end) switch (st.type) { case "space": case "comment": props.push(st); break; case "newline": props.push(st); return true; } return false; } function setFlowScalarValue(token, source, type) { switch (token.type) { case "scalar": case "double-quoted-scalar": case "single-quoted-scalar": token.type = type; token.source = source; break; case "block-scalar": { const end = token.props.slice(1); let oa = source.length; if (token.props[0].type === "block-scalar-header") oa -= token.props[0].source.length; for (const tok of end) tok.offset += oa; delete token.props; Object.assign(token, { type, source, end }); break; } case "block-map": case "block-seq": { const offset = token.offset + source.length; const nl = { type: "newline", offset, indent: token.indent, source: "\n" }; delete token.items; Object.assign(token, { type, source, end: [nl] }); break; } default: { const indent = "indent" in token ? token.indent : -1; const end = "end" in token && Array.isArray(token.end) ? token.end.filter((st) => st.type === "space" || st.type === "comment" || st.type === "newline") : []; for (const key of Object.keys(token)) if (key !== "type" && key !== "offset") delete token[key]; Object.assign(token, { type, indent, source, end }); } } } exports.createScalarToken = createScalarToken; exports.resolveAsScalar = resolveAsScalar; exports.setScalarValue = setScalarValue; } }); // ../../node_modules/yaml/dist/parse/cst-stringify.js var require_cst_stringify = __commonJS({ "../../node_modules/yaml/dist/parse/cst-stringify.js"(exports) { "use strict"; var stringify = (cst) => "type" in cst ? stringifyToken(cst) : stringifyItem(cst); function stringifyToken(token) { switch (token.type) { case "block-scalar": { let res = ""; for (const tok of token.props) res += stringifyToken(tok); return res + token.source; } case "block-map": case "block-seq": { let res = ""; for (const item of token.items) res += stringifyItem(item); return res; } case "flow-collection": { let res = token.start.source; for (const item of token.items) res += stringifyItem(item); for (const st of token.end) res += st.source; return res; } case "document": { let res = stringifyItem(token); if (token.end) for (const st of token.end) res += st.source; return res; } default: { let res = token.source; if ("end" in token && token.end) for (const st of token.end) res += st.source; return res; } } } function stringifyItem({ start, key, sep, value }) { let res = ""; for (const st of start) res += st.source; if (key) res += stringifyToken(key); if (sep) for (const st of sep) res += st.source; if (value) res += stringifyToken(value); return res; } exports.stringify = stringify; } }); // ../../node_modules/yaml/dist/parse/cst-visit.js var require_cst_visit = __commonJS({ "../../node_modules/yaml/dist/parse/cst-visit.js"(exports) { "use strict"; var BREAK = /* @__PURE__ */ Symbol("break visit"); var SKIP = /* @__PURE__ */ Symbol("skip children"); var REMOVE = /* @__PURE__ */ Symbol("remove item"); function visit(cst, visitor) { if ("type" in cst && cst.type === "document") cst = { start: cst.start, value: cst.value }; _visit(Object.freeze([]), cst, visitor); } visit.BREAK = BREAK; visit.SKIP = SKIP; visit.REMOVE = REMOVE; visit.itemAtPath = (cst, path) => { let item = cst; for (const [field, index] of path) { const tok = item?.[field]; if (tok && "items" in tok) { item = tok.items[index]; } else return void 0; } return item; }; visit.parentCollection = (cst, path) => { const parent = visit.itemAtPath(cst, path.slice(0, -1)); const field = path[path.length - 1][0]; const coll = parent?.[field]; if (coll && "items" in coll) return coll; throw new Error("Parent collection not found"); }; function _visit(path, item, visitor) { let ctrl = visitor(item, path); if (typeof ctrl === "symbol") return ctrl; for (const field of ["key", "value"]) { const token = item[field]; if (token && "items" in token) { for (let i = 0; i < token.items.length; ++i) { const ci = _visit(Object.freeze(path.concat([[field, i]])), token.items[i], visitor); if (typeof ci === "number") i = ci - 1; else if (ci === BREAK) return BREAK; else if (ci === REMOVE) { token.items.splice(i, 1); i -= 1; } } if (typeof ctrl === "function" && field === "key") ctrl = ctrl(item, path); } } return typeof ctrl === "function" ? ctrl(item, path) : ctrl; } exports.visit = visit; } }); // ../../node_modules/yaml/dist/parse/cst.js var require_cst = __commonJS({ "../../node_modules/yaml/dist/parse/cst.js"(exports) { "use strict"; var cstScalar = require_cst_scalar(); var cstStringify = require_cst_stringify(); var cstVisit = require_cst_visit(); var BOM = "\uFEFF"; var DOCUMENT = ""; var FLOW_END = ""; var SCALAR = ""; var isCollection = (token) => !!token && "items" in token; var isScalar = (token) => !!token && (token.type === "scalar" || token.type === "single-quoted-scalar" || token.type === "double-quoted-scalar" || token.type === "block-scalar"); function prettyToken(token) { switch (token) { case BOM: return ""; case DOCUMENT: return ""; case FLOW_END: return ""; case SCALAR: return ""; default: return JSON.stringify(token); } } function tokenType(source) { switch (source) { case BOM: return "byte-order-mark"; case DOCUMENT: return "doc-mode"; case FLOW_END: return "flow-error-end"; case SCALAR: return "scalar"; case "---": return "doc-start"; case "...": return "doc-end"; case "": case "\n": case "\r\n": return "newline"; case "-": return "seq-item-ind"; case "?": return "explicit-key-ind"; case ":": return "map-value-ind"; case "{": return "flow-map-start"; case "}": return "flow-map-end"; case "[": return "flow-seq-start"; case "]": return "flow-seq-end"; case ",": return "comma"; } switch (source[0]) { case " ": case " ": return "space"; case "#": return "comment"; case "%": return "directive-line"; case "*": return "alias"; case "&": return "anchor"; case "!": return "tag"; case "'": return "single-quoted-scalar"; case '"': return "double-quoted-scalar"; case "|": case ">": return "block-scalar-header"; } return null; } exports.createScalarToken = cstScalar.createScalarToken; exports.resolveAsScalar = cstScalar.resolveAsScalar; exports.setScalarValue = cstScalar.setScalarValue; exports.stringify = cstStringify.stringify; exports.visit = cstVisit.visit; exports.BOM = BOM; exports.DOCUMENT = DOCUMENT; exports.FLOW_END = FLOW_END; exports.SCALAR = SCALAR; exports.isCollection = isCollection; exports.isScalar = isScalar; exports.prettyToken = prettyToken; exports.tokenType = tokenType; } }); // ../../node_modules/yaml/dist/parse/lexer.js var require_lexer = __commonJS({ "../../node_modules/yaml/dist/parse/lexer.js"(exports) { "use strict"; var cst = require_cst(); function isEmpty(ch) { switch (ch) { case void 0: case " ": case "\n": case "\r": case " ": return true; default: return false; } } var hexDigits = new Set("0123456789ABCDEFabcdef"); var tagChars = new Set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()"); var flowIndicatorChars = new Set(",[]{}"); var invalidAnchorChars = new Set(" ,[]{}\n\r "); var isNotAnchorChar = (ch) => !ch || invalidAnchorChars.has(ch); var Lexer = class { constructor() { this.atEnd = false; this.blockScalarIndent = -1; this.blockScalarKeep = false; this.buffer = ""; this.flowKey = false; this.flowLevel = 0; this.indentNext = 0; this.indentValue = 0; this.lineEndPos = null; this.next = null; this.pos = 0; } /** * Generate YAML tokens from the `source` string. If `incomplete`, * a part of the last line may be left as a buffer for the next call. * * @returns A generator of lexical tokens */ *lex(source, incomplete = false) { if (source) { if (typeof source !== "string") throw TypeError("source is not a string"); this.buffer = this.buffer ? this.buffer + source : source; this.lineEndPos = null; } this.atEnd = !incomplete; let next = this.next ?? "stream"; while (next && (incomplete || this.hasChars(1))) next = yield* this.parseNext(next); } atLineEnd() { let i = this.pos; let ch = this.buffer[i]; while (ch === " " || ch === " ") ch = this.buffer[++i]; if (!ch || ch === "#" || ch === "\n") return true; if (ch === "\r") return this.buffer[i + 1] === "\n"; return false; } charAt(n) { return this.buffer[this.pos + n]; } continueScalar(offset) { let ch = this.buffer[offset]; if (this.indentNext > 0) { let indent = 0; while (ch === " ") ch = this.buffer[++indent + offset]; if (ch === "\r") { const next = this.buffer[indent + offset + 1]; if (next === "\n" || !next && !this.atEnd) return offset + indent + 1; } return ch === "\n" || indent >= this.indentNext || !ch && !this.atEnd ? offset + indent : -1; } if (ch === "-" || ch === ".") { const dt = this.buffer.substr(offset, 3); if ((dt === "---" || dt === "...") && isEmpty(this.buffer[offset + 3])) return -1; } return offset; } getLine() { let end = this.lineEndPos; if (typeof end !== "number" || end !== -1 && end < this.pos) { end = this.buffer.indexOf("\n", this.pos); this.lineEndPos = end; } if (end === -1) return this.atEnd ? this.buffer.substring(this.pos) : null; if (this.buffer[end - 1] === "\r") end -= 1; return this.buffer.substring(this.pos, end); } hasChars(n) { return this.pos + n <= this.buffer.length; } setNext(state) { this.buffer = this.buffer.substring(this.pos); this.pos = 0; this.lineEndPos = null; this.next = state; return null; } peek(n) { return this.buffer.substr(this.pos, n); } *parseNext(next) { switch (next) { case "stream": return yield* this.parseStream(); case "line-start": return yield* this.parseLineStart(); case "block-start": return yield* this.parseBlockStart(); case "doc": return yield* this.parseDocument(); case "flow": return yield* this.parseFlowCollection(); case "quoted-scalar": return yield* this.parseQuotedScalar(); case "block-scalar": return yield* this.parseBlockScalar(); case "plain-scalar": return yield* this.parsePlainScalar(); } } *parseStream() { let line = this.getLine(); if (line === null) return this.setNext("stream"); if (line[0] === cst.BOM) { yield* this.pushCount(1); line = line.substring(1); } if (line[0] === "%") { let dirEnd = line.length; let cs = line.indexOf("#"); while (cs !== -1) { const ch = line[cs - 1]; if (ch === " " || ch === " ") { dirEnd = cs - 1; break; } else { cs = line.indexOf("#", cs + 1); } } while (true) { const ch = line[dirEnd - 1]; if (ch === " " || ch === " ") dirEnd -= 1; else break; } const n = (yield* this.pushCount(dirEnd)) + (yield* this.pushSpaces(true)); yield* this.pushCount(line.length - n); this.pushNewline(); return "stream"; } if (this.atLineEnd()) { const sp = yield* this.pushSpaces(true); yield* this.pushCount(line.length - sp); yield* this.pushNewline(); return "stream"; } yield cst.DOCUMENT; return yield* this.parseLineStart(); } *parseLineStart() { const ch = this.charAt(0); if (!ch && !this.atEnd) return this.setNext("line-start"); if (ch === "-" || ch === ".") { if (!this.atEnd && !this.hasChars(4)) return this.setNext("line-start"); const s = this.peek(3); if ((s === "---" || s === "...") && isEmpty(this.charAt(3))) { yield* this.pushCount(3); this.indentValue = 0; this.indentNext = 0; return s === "---" ? "doc" : "stream"; } } this.indentValue = yield* this.pushSpaces(false); if (this.indentNext > this.indentValue && !isEmpty(this.charAt(1))) this.indentNext = this.indentValue; return yield* this.parseBlockStart(); } *parseBlockStart() { const [ch0, ch1] = this.peek(2); if (!ch1 && !this.atEnd) return this.setNext("block-start"); if ((ch0 === "-" || ch0 === "?" || ch0 === ":") && isEmpty(ch1)) { const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true)); this.indentNext = this.indentValue + 1; this.indentValue += n; return "block-start"; } return "doc"; } *parseDocument() { yield* this.pushSpaces(true); const line = this.getLine(); if (line === null) return this.setNext("doc"); let n = yield* this.pushIndicators(); switch (line[n]) { case "#": yield* this.pushCount(line.length - n); // fallthrough case void 0: yield* this.pushNewline(); return yield* this.parseLineStart(); case "{": case "[": yield* this.pushCount(1); this.flowKey = false; this.flowLevel = 1; return "flow"; case "}": case "]": yield* this.pushCount(1); return "doc"; case "*": yield* this.pushUntil(isNotAnchorChar); return "doc"; case '"': case "'": return yield* this.parseQuotedScalar(); case "|": case ">": n += yield* this.parseBlockScalarHeader(); n += yield* this.pushSpaces(true); yield* this.pushCount(line.length - n); yield* this.pushNewline(); return yield* this.parseBlockScalar(); default: return yield* this.parsePlainScalar(); } } *parseFlowCollection() { let nl, sp; let indent = -1; do { nl = yield* this.pushNewline(); if (nl > 0) { sp = yield* this.pushSpaces(false); this.indentValue = indent = sp; } else { sp = 0; } sp += yield* this.pushSpaces(true); } while (nl + sp > 0); const line = this.getLine(); if (line === null) return this.setNext("flow"); if (indent !== -1 && indent < this.indentNext && line[0] !== "#" || indent === 0 && (line.startsWith("---") || line.startsWith("...")) && isEmpty(line[3])) { const atFlowEndMarker = indent === this.indentNext - 1 && this.flowLevel === 1 && (line[0] === "]" || line[0] === "}"); if (!atFlowEndMarker) { this.flowLevel = 0; yield cst.FLOW_END; return yield* this.parseLineStart(); } } let n = 0; while (line[n] === ",") { n += yield* this.pushCount(1); n += yield* this.pushSpaces(true); this.flowKey = false; } n += yield* this.pushIndicators(); switch (line[n]) { case void 0: return "flow"; case "#": yield* this.pushCount(line.length - n); return "flow"; case "{": case "[": yield* this.pushCount(1); this.flowKey = false; this.flowLevel += 1; return "flow"; case "}": case "]": yield* this.pushCount(1); this.flowKey = true; this.flowLevel -= 1; return this.flowLevel ? "flow" : "doc"; case "*": yield* this.pushUntil(isNotAnchorChar); return "flow"; case '"': case "'": this.flowKey = true; return yield* this.parseQuotedScalar(); case ":": { const next = this.charAt(1); if (this.flowKey || isEmpty(next) || next === ",") { this.flowKey = false; yield* this.pushCount(1); yield* this.pushSpaces(true); return "flow"; } } // fallthrough default: this.flowKey = false; return yield* this.parsePlainScalar(); } } *parseQuotedScalar() { const quote = this.charAt(0); let end = this.buffer.indexOf(quote, this.pos + 1); if (quote === "'") { while (end !== -1 && this.buffer[end + 1] === "'") end = this.buffer.indexOf("'", end + 2); } else { while (end !== -1) { let n = 0; while (this.buffer[end - 1 - n] === "\\") n += 1; if (n % 2 === 0) break; end = this.buffer.indexOf('"', end + 1); } } const qb = this.buffer.substring(0, end); let nl = qb.indexOf("\n", this.pos); if (nl !== -1) { while (nl !== -1) { const cs = this.continueScalar(nl + 1); if (cs === -1) break; nl = qb.indexOf("\n", cs); } if (nl !== -1) { end = nl - (qb[nl - 1] === "\r" ? 2 : 1); } } if (end === -1) { if (!this.atEnd) return this.setNext("quoted-scalar"); end = this.buffer.length; } yield* this.pushToIndex(end + 1, false); return this.flowLevel ? "flow" : "doc"; } *parseBlockScalarHeader() { this.blockScalarIndent = -1; this.blockScalarKeep = false; let i = this.pos; while (true) { const ch = this.buffer[++i]; if (ch === "+") this.blockScalarKeep = true; else if (ch > "0" && ch <= "9") this.blockScalarIndent = Number(ch) - 1; else if (ch !== "-") break; } return yield* this.pushUntil((ch) => isEmpty(ch) || ch === "#"); } *parseBlockScalar() { let nl = this.pos - 1; let indent = 0; let ch; loop: for (let i2 = this.pos; ch = this.buffer[i2]; ++i2) { switch (ch) { case " ": indent += 1; break; case "\n": nl = i2; indent = 0; break; case "\r": { const next = this.buffer[i2 + 1]; if (!next && !this.atEnd) return this.setNext("block-scalar"); if (next === "\n") break; } // fallthrough default: break loop; } } if (!ch && !this.atEnd) return this.setNext("block-scalar"); if (indent >= this.indentNext) { if (this.blockScalarIndent === -1) this.indentNext = indent; else { this.indentNext = this.blockScalarIndent + (this.indentNext === 0 ? 1 : this.indentNext); } do { const cs = this.continueScalar(nl + 1); if (cs === -1) break; nl = this.buffer.indexOf("\n", cs); } while (nl !== -1); if (nl === -1) { if (!this.atEnd) return this.setNext("block-scalar"); nl = this.buffer.length; } } let i = nl + 1; ch = this.buffer[i]; while (ch === " ") ch = this.buffer[++i]; if (ch === " ") { while (ch === " " || ch === " " || ch === "\r" || ch === "\n") ch = this.buffer[++i]; nl = i - 1; } else if (!this.blockScalarKeep) { do { let i2 = nl - 1; let ch2 = this.buffer[i2]; if (ch2 === "\r") ch2 = this.buffer[--i2]; const lastChar = i2; while (ch2 === " ") ch2 = this.buffer[--i2]; if (ch2 === "\n" && i2 >= this.pos && i2 + 1 + indent > lastChar) nl = i2; else break; } while (true); } yield cst.SCALAR; yield* this.pushToIndex(nl + 1, true); return yield* this.parseLineStart(); } *parsePlainScalar() { const inFlow = this.flowLevel > 0; let end = this.pos - 1; let i = this.pos - 1; let ch; while (ch = this.buffer[++i]) { if (ch === ":") { const next = this.buffer[i + 1]; if (isEmpty(next) || inFlow && flowIndicatorChars.has(next)) break; end = i; } else if (isEmpty(ch)) { let next = this.buffer[i + 1]; if (ch === "\r") { if (next === "\n") { i += 1; ch = "\n"; next = this.buffer[i + 1]; } else end = i; } if (next === "#" || inFlow && flowIndicatorChars.has(next)) break; if (ch === "\n") { const cs = this.continueScalar(i + 1); if (cs === -1) break; i = Math.max(i, cs - 2); } } else { if (inFlow && flowIndicatorChars.has(ch)) break; end = i; } } if (!ch && !this.atEnd) return this.setNext("plain-scalar"); yield cst.SCALAR; yield* this.pushToIndex(end + 1, true); return inFlow ? "flow" : "doc"; } *pushCount(n) { if (n > 0) { yield this.buffer.substr(this.pos, n); this.pos += n; return n; } return 0; } *pushToIndex(i, allowEmpty) { const s = this.buffer.slice(this.pos, i); if (s) { yield s; this.pos += s.length; return s.length; } else if (allowEmpty) yield ""; return 0; } *pushIndicators() { let n = 0; loop: while (true) { switch (this.charAt(0)) { case "!": n += yield* this.pushTag(); n += yield* this.pushSpaces(true); continue loop; case "&": n += yield* this.pushUntil(isNotAnchorChar); n += yield* this.pushSpaces(true); continue loop; case "-": // this is an error case "?": // this is an error outside flow collections case ":": { const inFlow = this.flowLevel > 0; const ch1 = this.charAt(1); if (isEmpty(ch1) || inFlow && flowIndicatorChars.has(ch1)) { if (!inFlow) this.indentNext = this.indentValue + 1; else if (this.flowKey) this.flowKey = false; n += yield* this.pushCount(1); n += yield* this.pushSpaces(true); continue loop; } } } break loop; } return n; } *pushTag() { if (this.charAt(1) === "<") { let i = this.pos + 2; let ch = this.buffer[i]; while (!isEmpty(ch) && ch !== ">") ch = this.buffer[++i]; return yield* this.pushToIndex(ch === ">" ? i + 1 : i, false); } else { let i = this.pos + 1; let ch = this.buffer[i]; while (ch) { if (tagChars.has(ch)) ch = this.buffer[++i]; else if (ch === "%" && hexDigits.has(this.buffer[i + 1]) && hexDigits.has(this.buffer[i + 2])) { ch = this.buffer[i += 3]; } else break; } return yield* this.pushToIndex(i, false); } } *pushNewline() { const ch = this.buffer[this.pos]; if (ch === "\n") return yield* this.pushCount(1); else if (ch === "\r" && this.charAt(1) === "\n") return yield* this.pushCount(2); else return 0; } *pushSpaces(allowTabs) { let i = this.pos - 1; let ch; do { ch = this.buffer[++i]; } while (ch === " " || allowTabs && ch === " "); const n = i - this.pos; if (n > 0) { yield this.buffer.substr(this.pos, n); this.pos = i; } return n; } *pushUntil(test) { let i = this.pos; let ch = this.buffer[i]; while (!test(ch)) ch = this.buffer[++i]; return yield* this.pushToIndex(i, false); } }; exports.Lexer = Lexer; } }); // ../../node_modules/yaml/dist/parse/line-counter.js var require_line_counter = __commonJS({ "../../node_modules/yaml/dist/parse/line-counter.js"(exports) { "use strict"; var LineCounter = class { constructor() { this.lineStarts = []; this.addNewLine = (offset) => this.lineStarts.push(offset); this.linePos = (offset) => { let low = 0; let high = this.lineStarts.length; while (low < high) { const mid = low + high >> 1; if (this.lineStarts[mid] < offset) low = mid + 1; else high = mid; } if (this.lineStarts[low] === offset) return { line: low + 1, col: 1 }; if (low === 0) return { line: 0, col: offset }; const start = this.lineStarts[low - 1]; return { line: low, col: offset - start + 1 }; }; } }; exports.LineCounter = LineCounter; } }); // ../../node_modules/yaml/dist/parse/parser.js var require_parser = __commonJS({ "../../node_modules/yaml/dist/parse/parser.js"(exports) { "use strict"; var node_process = __require("process"); var cst = require_cst(); var lexer = require_lexer(); function includesToken(list, type) { for (let i = 0; i < list.length; ++i) if (list[i].type === type) return true; return false; } function findNonEmptyIndex(list) { for (let i = 0; i < list.length; ++i) { switch (list[i].type) { case "space": case "comment": case "newline": break; default: return i; } } return -1; } function isFlowToken(token) { switch (token?.type) { case "alias": case "scalar": case "single-quoted-scalar": case "double-quoted-scalar": case "flow-collection": return true; default: return false; } } function getPrevProps(parent) { switch (parent.type) { case "document": return parent.start; case "block-map": { const it = parent.items[parent.items.length - 1]; return it.sep ?? it.start; } case "block-seq": return parent.items[parent.items.length - 1].start; /* istanbul ignore next should not happen */ default: return []; } } function getFirstKeyStartProps(prev) { if (prev.length === 0) return []; let i = prev.length; loop: while (--i >= 0) { switch (prev[i].type) { case "doc-start": case "explicit-key-ind": case "map-value-ind": case "seq-item-ind": case "newline": break loop; } } while (prev[++i]?.type === "space") { } return prev.splice(i, prev.length); } function arrayPushArray(target, source) { if (source.length < 1e5) Array.prototype.push.apply(target, source); else for (let i = 0; i < source.length; ++i) target.push(source[i]); } function fixFlowSeqItems(fc) { if (fc.start.type === "flow-seq-start") { for (const it of fc.items) { if (it.sep && !it.value && !includesToken(it.start, "explicit-key-ind") && !includesToken(it.sep, "map-value-ind")) { if (it.key) it.value = it.key; delete it.key; if (isFlowToken(it.value)) { if (it.value.end) arrayPushArray(it.value.end, it.sep); else it.value.end = it.sep; } else arrayPushArray(it.start, it.sep); delete it.sep; } } } } var Parser = class { /** * @param onNewLine - If defined, called separately with the start position of * each new line (in `parse()`, including the start of input). */ constructor(onNewLine) { this.atNewLine = true; this.atScalar = false; this.indent = 0; this.offset = 0; this.onKeyLine = false; this.stack = []; this.source = ""; this.type = ""; this.lexer = new lexer.Lexer(); this.onNewLine = onNewLine; } /** * Parse `source` as a YAML stream. * If `incomplete`, a part of the last line may be left as a buffer for the next call. * * Errors are not thrown, but yielded as `{ type: 'error', message }` tokens. * * @returns A generator of tokens representing each directive, document, and other structure. */ *parse(source, incomplete = false) { if (this.onNewLine && this.offset === 0) this.onNewLine(0); for (const lexeme of this.lexer.lex(source, incomplete)) yield* this.next(lexeme); if (!incomplete) yield* this.end(); } /** * Advance the parser by the `source` of one lexical token. */ *next(source) { this.source = source; if (node_process.env.LOG_TOKENS) console.log("|", cst.prettyToken(source)); if (this.atScalar) { this.atScalar = false; yield* this.step(); this.offset += source.length; return; } const type = cst.tokenType(source); if (!type) { const message = `Not a YAML token: ${source}`; yield* this.pop({ type: "error", offset: this.offset, message, source }); this.offset += source.length; } else if (type === "scalar") { this.atNewLine = false; this.atScalar = true; this.type = "scalar"; } else { this.type = type; yield* this.step(); switch (type) { case "newline": this.atNewLine = true; this.indent = 0; if (this.onNewLine) this.onNewLine(this.offset + source.length); break; case "space": if (this.atNewLine && source[0] === " ") this.indent += source.length; break; case "explicit-key-ind": case "map-value-ind": case "seq-item-ind": if (this.atNewLine) this.indent += source.length; break; case "doc-mode": case "flow-error-end": return; default: this.atNewLine = false; } this.offset += source.length; } } /** Call at end of input to push out any remaining constructions */ *end() { while (this.stack.length > 0) yield* this.pop(); } get sourceToken() { const st = { type: this.type, offset: this.offset, indent: this.indent, source: this.source }; return st; } *step() { const top = this.peek(1); if (this.type === "doc-end" && top?.type !== "doc-end") { while (this.stack.length > 0) yield* this.pop(); this.stack.push({ type: "doc-end", offset: this.offset, source: this.source }); return; } if (!top) return yield* this.stream(); switch (top.type) { case "document": return yield* this.document(top); case "alias": case "scalar": case "single-quoted-scalar": case "double-quoted-scalar": return yield* this.scalar(top); case "block-scalar": return yield* this.blockScalar(top); case "block-map": return yield* this.blockMap(top); case "block-seq": return yield* this.blockSequence(top); case "flow-collection": return yield* this.flowCollection(top); case "doc-end": return yield* this.documentEnd(top); } yield* this.pop(); } peek(n) { return this.stack[this.stack.length - n]; } *pop(error) { const token = error ?? this.stack.pop(); if (!token) { const message = "Tried to pop an empty stack"; yield { type: "error", offset: this.offset, source: "", message }; } else if (this.stack.length === 0) { yield token; } else { const top = this.peek(1); if (token.type === "block-scalar") { token.indent = "indent" in top ? top.indent : 0; } else if (token.type === "flow-collection" && top.type === "document") { token.indent = 0; } if (token.type === "flow-collection") fixFlowSeqItems(token); switch (top.type) { case "document": top.value = token; break; case "block-scalar": top.props.push(token); break; case "block-map": { const it = top.items[top.items.length - 1]; if (it.value) { top.items.push({ start: [], key: token, sep: [] }); this.onKeyLine = true; return; } else if (it.sep) { it.value = token; } else { Object.assign(it, { key: token, sep: [] }); this.onKeyLine = !it.explicitKey; return; } break; } case "block-seq": { const it = top.items[top.items.length - 1]; if (it.value) top.items.push({ start: [], value: token }); else it.value = token; break; } case "flow-collection": { const it = top.items[top.items.length - 1]; if (!it || it.value) top.items.push({ start: [], key: token, sep: [] }); else if (it.sep) it.value = token; else Object.assign(it, { key: token, sep: [] }); return; } /* istanbul ignore next should not happen */ default: yield* this.pop(); yield* this.pop(token); } if ((top.type === "document" || top.type === "block-map" || top.type === "block-seq") && (token.type === "block-map" || token.type === "block-seq")) { const last = token.items[token.items.length - 1]; if (last && !last.sep && !last.value && last.start.length > 0 && findNonEmptyIndex(last.start) === -1 && (token.indent === 0 || last.start.every((st) => st.type !== "comment" || st.indent < token.indent))) { if (top.type === "document") top.end = last.start; else top.items.push({ start: last.start }); token.items.splice(-1, 1); } } } } *stream() { switch (this.type) { case "directive-line": yield { type: "directive", offset: this.offset, source: this.source }; return; case "byte-order-mark": case "space": case "comment": case "newline": yield this.sourceToken; return; case "doc-mode": case "doc-start": { const doc = { type: "document", offset: this.offset, start: [] }; if (this.type === "doc-start") doc.start.push(this.sourceToken); this.stack.push(doc); return; } } yield { type: "error", offset: this.offset, message: `Unexpected ${this.type} token in YAML stream`, source: this.source }; } *document(doc) { if (doc.value) return yield* this.lineEnd(doc); switch (this.type) { case "doc-start": { if (findNonEmptyIndex(doc.start) !== -1) { yield* this.pop(); yield* this.step(); } else doc.start.push(this.sourceToken); return; } case "anchor": case "tag": case "space": case "comment": case "newline": doc.start.push(this.sourceToken); return; } const bv = this.startBlockValue(doc); if (bv) this.stack.push(bv); else { yield { type: "error", offset: this.offset, message: `Unexpected ${this.type} token in YAML document`, source: this.source }; } } *scalar(scalar) { if (this.type === "map-value-ind") { const prev = getPrevProps(this.peek(2)); const start = getFirstKeyStartProps(prev); let sep; if (scalar.end) { sep = scalar.end; sep.push(this.sourceToken); delete scalar.end; } else sep = [this.sourceToken]; const map = { type: "block-map", offset: scalar.offset, indent: scalar.indent, items: [{ start, key: scalar, sep }] }; this.onKeyLine = true; this.stack[this.stack.length - 1] = map; } else yield* this.lineEnd(scalar); } *blockScalar(scalar) { switch (this.type) { case "space": case "comment": case "newline": scalar.props.push(this.sourceToken); return; case "scalar": scalar.source = this.source; this.atNewLine = true; this.indent = 0; if (this.onNewLine) { let nl = this.source.indexOf("\n") + 1; while (nl !== 0) { this.onNewLine(this.offset + nl); nl = this.source.indexOf("\n", nl) + 1; } } yield* this.pop(); break; /* istanbul ignore next should not happen */ default: yield* this.pop(); yield* this.step(); } } *blockMap(map) { const it = map.items[map.items.length - 1]; switch (this.type) { case "newline": this.onKeyLine = false; if (it.value) { const end = "end" in it.value ? it.value.end : void 0; const last = Array.isArray(end) ? end[end.length - 1] : void 0; if (last?.type === "comment") end?.push(this.sourceToken); else map.items.push({ start: [this.sourceToken] }); } else if (it.sep) { it.sep.push(this.sourceToken); } else { it.start.push(this.sourceToken); } return; case "space": case "comment": if (it.value) { map.items.push({ start: [this.sourceToken] }); } else if (it.sep) { it.sep.push(this.sourceToken); } else { if (this.atIndentedComment(it.start, map.indent)) { const prev = map.items[map.items.length - 2]; const end = prev?.value?.end; if (Array.isArray(end)) { arrayPushArray(end, it.start); end.push(this.sourceToken); map.items.pop(); return; } } it.start.push(this.sourceToken); } return; } if (this.indent >= map.indent) { const atMapIndent = !this.onKeyLine && this.indent === map.indent; const atNextItem = atMapIndent && (it.sep || it.explicitKey) && this.type !== "seq-item-ind"; let start = []; if (atNextItem && it.sep && !it.value) { const nl = []; for (let i = 0; i < it.sep.length; ++i) { const st = it.sep[i]; switch (st.type) { case "newline": nl.push(i); break; case "space": break; case "comment": if (st.indent > map.indent) nl.length = 0; break; default: nl.length = 0; } } if (nl.length >= 2) start = it.sep.splice(nl[1]); } switch (this.type) { case "anchor": case "tag": if (atNextItem || it.value) { start.push(this.sourceToken); map.items.push({ start }); this.onKeyLine = true; } else if (it.sep) { it.sep.push(this.sourceToken); } else { it.start.push(this.sourceToken); } return; case "explicit-key-ind": if (!it.sep && !it.explicitKey) { it.start.push(this.sourceToken); it.explicitKey = true; } else if (atNextItem || it.value) { start.push(this.sourceToken); map.items.push({ start, explicitKey: true }); } else { this.stack.push({ type: "block-map", offset: this.offset, indent: this.indent, items: [{ start: [this.sourceToken], explicitKey: true }] }); } this.onKeyLine = true; return; case "map-value-ind": if (it.explicitKey) { if (!it.sep) { if (includesToken(it.start, "newline")) { Object.assign(it, { key: null, sep: [this.sourceToken] }); } else { const start2 = getFirstKeyStartProps(it.start); this.stack.push({ type: "block-map", offset: this.offset, indent: this.indent, items: [{ start: start2, key: null, sep: [this.sourceToken] }] }); } } else if (it.value) { map.items.push({ start: [], key: null, sep: [this.sourceToken] }); } else if (includesToken(it.sep, "map-value-ind")) { this.stack.push({ type: "block-map", offset: this.offset, indent: this.indent, items: [{ start, key: null, sep: [this.sourceToken] }] }); } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) { const start2 = getFirstKeyStartProps(it.start); const key = it.key; const sep = it.sep; sep.push(this.sourceToken); delete it.key; delete it.sep; this.stack.push({ type: "block-map", offset: this.offset, indent: this.indent, items: [{ start: start2, key, sep }] }); } else if (start.length > 0) { it.sep = it.sep.concat(start, this.sourceToken); } else { it.sep.push(this.sourceToken); } } else { if (!it.sep) { Object.assign(it, { key: null, sep: [this.sourceToken] }); } else if (it.value || atNextItem) { map.items.push({ start, key: null, sep: [this.sourceToken] }); } else if (includesToken(it.sep, "map-value-ind")) { this.stack.push({ type: "block-map", offset: this.offset, indent: this.indent, items: [{ start: [], key: null, sep: [this.sourceToken] }] }); } else { it.sep.push(this.sourceToken); } } this.onKeyLine = true; return; case "alias": case "scalar": case "single-quoted-scalar": case "double-quoted-scalar": { const fs = this.flowScalar(this.type); if (atNextItem || it.value) { map.items.push({ start, key: fs, sep: [] }); this.onKeyLine = true; } else if (it.sep) { this.stack.push(fs); } else { Object.assign(it, { key: fs, sep: [] }); this.onKeyLine = true; } return; } default: { const bv = this.startBlockValue(map); if (bv) { if (bv.type === "block-seq") { if (!it.explicitKey && it.sep && !includesToken(it.sep, "newline")) { yield* this.pop({ type: "error", offset: this.offset, message: "Unexpected block-seq-ind on same line with key", source: this.source }); return; } } else if (atMapIndent) { map.items.push({ start }); } this.stack.push(bv); return; } } } } yield* this.pop(); yield* this.step(); } *blockSequence(seq) { const it = seq.items[seq.items.length - 1]; switch (this.type) { case "newline": if (it.value) { const end = "end" in it.value ? it.value.end : void 0; const last = Array.isArray(end) ? end[end.length - 1] : void 0; if (last?.type === "comment") end?.push(this.sourceToken); else seq.items.push({ start: [this.sourceToken] }); } else it.start.push(this.sourceToken); return; case "space": case "comment": if (it.value) seq.items.push({ start: [this.sourceToken] }); else { if (this.atIndentedComment(it.start, seq.indent)) { const prev = seq.items[seq.items.length - 2]; const end = prev?.value?.end; if (Array.isArray(end)) { arrayPushArray(end, it.start); end.push(this.sourceToken); seq.items.pop(); return; } } it.start.push(this.sourceToken); } return; case "anchor": case "tag": if (it.value || this.indent <= seq.indent) break; it.start.push(this.sourceToken); return; case "seq-item-ind": if (this.indent !== seq.indent) break; if (it.value || includesToken(it.start, "seq-item-ind")) seq.items.push({ start: [this.sourceToken] }); else it.start.push(this.sourceToken); return; } if (this.indent > seq.indent) { const bv = this.startBlockValue(seq); if (bv) { this.stack.push(bv); return; } } yield* this.pop(); yield* this.step(); } *flowCollection(fc) { const it = fc.items[fc.items.length - 1]; if (this.type === "flow-error-end") { let top; do { yield* this.pop(); top = this.peek(1); } while (top?.type === "flow-collection"); } else if (fc.end.length === 0) { switch (this.type) { case "comma": case "explicit-key-ind": if (!it || it.sep) fc.items.push({ start: [this.sourceToken] }); else it.start.push(this.sourceToken); return; case "map-value-ind": if (!it || it.value) fc.items.push({ start: [], key: null, sep: [this.sourceToken] }); else if (it.sep) it.sep.push(this.sourceToken); else Object.assign(it, { key: null, sep: [this.sourceToken] }); return; case "space": case "comment": case "newline": case "anchor": case "tag": if (!it || it.value) fc.items.push({ start: [this.sourceToken] }); else if (it.sep) it.sep.push(this.sourceToken); else it.start.push(this.sourceToken); return; case "alias": case "scalar": case "single-quoted-scalar": case "double-quoted-scalar": { const fs = this.flowScalar(this.type); if (!it || it.value) fc.items.push({ start: [], key: fs, sep: [] }); else if (it.sep) this.stack.push(fs); else Object.assign(it, { key: fs, sep: [] }); return; } case "flow-map-end": case "flow-seq-end": fc.end.push(this.sourceToken); return; } const bv = this.startBlockValue(fc); if (bv) this.stack.push(bv); else { yield* this.pop(); yield* this.step(); } } else { const parent = this.peek(2); if (parent.type === "block-map" && (this.type === "map-value-ind" && parent.indent === fc.indent || this.type === "newline" && !parent.items[parent.items.length - 1].sep)) { yield* this.pop(); yield* this.step(); } else if (this.type === "map-value-ind" && parent.type !== "flow-collection") { const prev = getPrevProps(parent); const start = getFirstKeyStartProps(prev); fixFlowSeqItems(fc); const sep = fc.end.splice(1, fc.end.length); sep.push(this.sourceToken); const map = { type: "block-map", offset: fc.offset, indent: fc.indent, items: [{ start, key: fc, sep }] }; this.onKeyLine = true; this.stack[this.stack.length - 1] = map; } else { yield* this.lineEnd(fc); } } } flowScalar(type) { if (this.onNewLine) { let nl = this.source.indexOf("\n") + 1; while (nl !== 0) { this.onNewLine(this.offset + nl); nl = this.source.indexOf("\n", nl) + 1; } } return { type, offset: this.offset, indent: this.indent, source: this.source }; } startBlockValue(parent) { switch (this.type) { case "alias": case "scalar": case "single-quoted-scalar": case "double-quoted-scalar": return this.flowScalar(this.type); case "block-scalar-header": return { type: "block-scalar", offset: this.offset, indent: this.indent, props: [this.sourceToken], source: "" }; case "flow-map-start": case "flow-seq-start": return { type: "flow-collection", offset: this.offset, indent: this.indent, start: this.sourceToken, items: [], end: [] }; case "seq-item-ind": return { type: "block-seq", offset: this.offset, indent: this.indent, items: [{ start: [this.sourceToken] }] }; case "explicit-key-ind": { this.onKeyLine = true; const prev = getPrevProps(parent); const start = getFirstKeyStartProps(prev); start.push(this.sourceToken); return { type: "block-map", offset: this.offset, indent: this.indent, items: [{ start, explicitKey: true }] }; } case "map-value-ind": { this.onKeyLine = true; const prev = getPrevProps(parent); const start = getFirstKeyStartProps(prev); return { type: "block-map", offset: this.offset, indent: this.indent, items: [{ start, key: null, sep: [this.sourceToken] }] }; } } return null; } atIndentedComment(start, indent) { if (this.type !== "comment") return false; if (this.indent <= indent) return false; return start.every((st) => st.type === "newline" || st.type === "space"); } *documentEnd(docEnd) { if (this.type !== "doc-mode") { if (docEnd.end) docEnd.end.push(this.sourceToken); else docEnd.end = [this.sourceToken]; if (this.type === "newline") yield* this.pop(); } } *lineEnd(token) { switch (this.type) { case "comma": case "doc-start": case "doc-end": case "flow-seq-end": case "flow-map-end": case "map-value-ind": yield* this.pop(); yield* this.step(); break; case "newline": this.onKeyLine = false; // fallthrough case "space": case "comment": default: if (token.end) token.end.push(this.sourceToken); else token.end = [this.sourceToken]; if (this.type === "newline") yield* this.pop(); } } }; exports.Parser = Parser; } }); // ../../node_modules/yaml/dist/public-api.js var require_public_api = __commonJS({ "../../node_modules/yaml/dist/public-api.js"(exports) { "use strict"; var composer = require_composer(); var Document = require_Document(); var errors = require_errors(); var log2 = require_log(); var identity = require_identity(); var lineCounter = require_line_counter(); var parser = require_parser(); function parseOptions(options) { const prettyErrors = options.prettyErrors !== false; const lineCounter$1 = options.lineCounter || prettyErrors && new lineCounter.LineCounter() || null; return { lineCounter: lineCounter$1, prettyErrors }; } function parseAllDocuments2(source, options = {}) { const { lineCounter: lineCounter2, prettyErrors } = parseOptions(options); const parser$1 = new parser.Parser(lineCounter2?.addNewLine); const composer$1 = new composer.Composer(options); const docs = Array.from(composer$1.compose(parser$1.parse(source))); if (prettyErrors && lineCounter2) for (const doc of docs) { doc.errors.forEach(errors.prettifyError(source, lineCounter2)); doc.warnings.forEach(errors.prettifyError(source, lineCounter2)); } if (docs.length > 0) return docs; return Object.assign([], { empty: true }, composer$1.streamInfo()); } function parseDocument(source, options = {}) { const { lineCounter: lineCounter2, prettyErrors } = parseOptions(options); const parser$1 = new parser.Parser(lineCounter2?.addNewLine); const composer$1 = new composer.Composer(options); let doc = null; for (const _doc of composer$1.compose(parser$1.parse(source), true, source.length)) { if (!doc) doc = _doc; else if (doc.options.logLevel !== "silent") { doc.errors.push(new errors.YAMLParseError(_doc.range.slice(0, 2), "MULTIPLE_DOCS", "Source contains multiple documents; please use YAML.parseAllDocuments()")); break; } } if (prettyErrors && lineCounter2) { doc.errors.forEach(errors.prettifyError(source, lineCounter2)); doc.warnings.forEach(errors.prettifyError(source, lineCounter2)); } return doc; } function parse(src, reviver, options) { let _reviver = void 0; if (typeof reviver === "function") { _reviver = reviver; } else if (options === void 0 && reviver && typeof reviver === "object") { options = reviver; } const doc = parseDocument(src, options); if (!doc) return null; doc.warnings.forEach((warning) => log2.warn(doc.options.logLevel, warning)); if (doc.errors.length > 0) { if (doc.options.logLevel !== "silent") throw doc.errors[0]; else doc.errors = []; } return doc.toJS(Object.assign({ reviver: _reviver }, options)); } function stringify(value, replacer, options) { let _replacer = null; if (typeof replacer === "function" || Array.isArray(replacer)) { _replacer = replacer; } else if (options === void 0 && replacer) { options = replacer; } if (typeof options === "string") options = options.length; if (typeof options === "number") { const indent = Math.round(options); options = indent < 1 ? void 0 : indent > 8 ? { indent: 8 } : { indent }; } if (value === void 0) { const { keepUndefined } = options ?? replacer ?? {}; if (!keepUndefined) return void 0; } if (identity.isDocument(value) && !_replacer) return value.toString(options); return new Document.Document(value, _replacer, options).toString(options); } exports.parse = parse; exports.parseAllDocuments = parseAllDocuments2; exports.parseDocument = parseDocument; exports.stringify = stringify; } }); // ../../node_modules/yaml/dist/index.js var require_dist = __commonJS({ "../../node_modules/yaml/dist/index.js"(exports) { "use strict"; var composer = require_composer(); var Document = require_Document(); var Schema = require_Schema(); var errors = require_errors(); var Alias = require_Alias(); var identity = require_identity(); var Pair = require_Pair(); var Scalar = require_Scalar(); var YAMLMap = require_YAMLMap(); var YAMLSeq = require_YAMLSeq(); var cst = require_cst(); var lexer = require_lexer(); var lineCounter = require_line_counter(); var parser = require_parser(); var publicApi = require_public_api(); var visit = require_visit(); exports.Composer = composer.Composer; exports.Document = Document.Document; exports.Schema = Schema.Schema; exports.YAMLError = errors.YAMLError; exports.YAMLParseError = errors.YAMLParseError; exports.YAMLWarning = errors.YAMLWarning; exports.Alias = Alias.Alias; exports.isAlias = identity.isAlias; exports.isCollection = identity.isCollection; exports.isDocument = identity.isDocument; exports.isMap = identity.isMap; exports.isNode = identity.isNode; exports.isPair = identity.isPair; exports.isScalar = identity.isScalar; exports.isSeq = identity.isSeq; exports.Pair = Pair.Pair; exports.Scalar = Scalar.Scalar; exports.YAMLMap = YAMLMap.YAMLMap; exports.YAMLSeq = YAMLSeq.YAMLSeq; exports.CST = cst; exports.Lexer = lexer.Lexer; exports.LineCounter = lineCounter.LineCounter; exports.Parser = parser.Parser; exports.parse = publicApi.parse; exports.parseAllDocuments = publicApi.parseAllDocuments; exports.parseDocument = publicApi.parseDocument; exports.stringify = publicApi.stringify; exports.visit = visit.visit; exports.visitAsync = visit.visitAsync; } }); // src/server.ts import { createServer } from "node:http"; import { createReadStream, existsSync, statSync } from "node:fs"; import { extname, join, normalize, resolve } from "node:path"; // ../../packages/engine/src/search.ts var STOP = new Set( "a an and are as at be but by can do does for from how i if in into is it its me my of on or so than that the their them then there these they this to was what when where which who why will with you your".split( " " ) ); function tokenize(text) { return text.toLowerCase().replace(/[`*_#>()[\]{}"',;:!?|=]/g, " ").split(/\s+/).map((t) => t.replace(/^[.-]+|[.-]+$/g, "")).filter((t) => t.length > 1 && !STOP.has(t)).flatMap((t) => t.includes("-") || t.includes("/") ? [t, ...t.split(/[-/]/).filter((p) => p.length > 1)] : [t]).map(stem); } function stem(t) { if (t.length > 4 && t.endsWith("ies")) return t.slice(0, -3) + "y"; if (t.length > 3 && t.endsWith("es") && /(ss|x|ch|sh)es$/.test(t)) return t.slice(0, -2); if (t.length > 3 && t.endsWith("s") && !t.endsWith("ss") && !t.endsWith("us")) return t.slice(0, -1); return t; } var KIND_BOOST = { lesson: 1.3, section: 1, faq: 1, glossary: 1.2, comparison: 1.15, question: 0.8, lab: 0.9, flashcard: 0.85 }; var SearchIndex = class { docs = []; tf = []; titleTokens = []; len = []; df = /* @__PURE__ */ new Map(); avgLen = 0; constructor(docs) { for (const d of docs) this.add(d); this.avgLen = this.len.reduce((a, b) => a + b, 0) / Math.max(1, this.len.length); } add(doc) { const tokens = tokenize(`${doc.title} ${doc.title} ${doc.text}`); const tf = /* @__PURE__ */ new Map(); for (const t of tokens) tf.set(t, (tf.get(t) ?? 0) + 1); for (const t of tf.keys()) this.df.set(t, (this.df.get(t) ?? 0) + 1); this.docs.push(doc); this.tf.push(tf); this.titleTokens.push(new Set(tokenize(doc.title))); this.len.push(tokens.length); } get size() { return this.docs.length; } search(query, opts = {}) { const q = [...new Set(tokenize(query))]; if (!q.length) return []; const N = this.docs.length; const k1 = 1.4; const b = 0.75; const hits = []; for (let i = 0; i < N; i++) { const doc = this.docs[i]; if (opts.kinds && !opts.kinds.includes(doc.kind)) continue; const tf = this.tf[i]; let score = 0; let matched = 0; for (const term of q) { const f = tf.get(term); if (!f) continue; matched++; const df = this.df.get(term) ?? 0; const idf = Math.log(1 + (N - df + 0.5) / (df + 0.5)); score += idf * (f * (k1 + 1) / (f + k1 * (1 - b + b * this.len[i] / this.avgLen))); if (this.titleTokens[i].has(term)) score += idf * 0.6; } if (!score) continue; score *= KIND_BOOST[doc.kind] * (0.6 + 0.4 * matched / q.length); if (opts.boostLesson && doc.lessonId === opts.boostLesson) score *= 1.5; hits.push({ doc, score }); } hits.sort((a, b2) => b2.score - a.score); return hits.slice(0, opts.limit ?? 10); } }; // ../../packages/content/src/lessons/a.ts var lessonsA = [ // ───────────────────────────── Stage 0 · Foundations ───────────────────────────── { id: "linux-essentials", title: "Linux essentials for containers", stage: 0, order: 1, objectives: ["kf-containerization"], scope: "foundation", estMinutes: 20, summary: "A container is an ordinary Linux process wrapped in kernel namespaces (what it can see), cgroups (what it can use) and its own root filesystem. Signals decide how it stops.", what: `Containers are not a kernel feature of their own. They are a **combination of Linux kernel features** applied to a normal process: - **Processes** \u2013 every running program has a PID, a parent, a user ID and open files. A container's main process is just a process on the host. - **Namespaces** \u2013 give a process its own private *view* of a system resource: process IDs, network stack, mount points, hostname, IPC and users. - **cgroups (control groups)** \u2013 limit and account for what a group of processes may *consume*: CPU time, memory, PIDs, I/O. - **Root filesystem** \u2013 the process sees a directory tree (unpacked from an image) as its \`/\`, instead of the host's. - **Signals** \u2013 asynchronous notifications like \`SIGTERM\` (please stop) and \`SIGKILL\` (stop now, cannot be caught) that the runtime and Kubernetes use to shut containers down.`, why: `Kubernetes settings map almost one-to-one onto these primitives. A memory **limit** becomes a cgroup memory limit; exceeding it gets the process killed by the kernel (\`OOMKilled\`). Pods sharing \`localhost\` works because the containers in a Pod share one **network namespace**. A graceful shutdown works only if your process handles \`SIGTERM\`. When you debug a container you are debugging a Linux process. Knowing that removes most of the "magic".`, problem: `Before containers, running two applications on one server meant they shared everything: the same PID space, the same ports, the same \`/etc\`, the same library versions. One runaway process could eat all memory and take the neighbours down. The options were to buy another machine or run a full virtual machine per app. Namespaces and cgroups solve this cheaply: each app gets an isolated view and a resource budget, while all apps still share one kernel.`, how: `What a runtime such as runc does when it starts a container, step by step: - **Prepare the filesystem.** Image layers are stacked (usually with an overlay filesystem) into one directory that will become the container's root. - **Create namespaces.** The runtime asks the kernel (via \`clone\`/\`unshare\`) for new PID, mount, network, UTS (hostname), IPC and optionally user namespaces. Inside the new PID namespace the first process sees itself as **PID 1**. - **Place the process in a cgroup.** The runtime writes limits such as \`memory.max\` and \`cpu.max\` (cgroup v2) for that cgroup. The kernel enforces them: CPU over the limit is **throttled**; memory over the limit triggers the **OOM killer**. - **Switch the root.** The process's root is changed to the prepared directory (\`pivot_root\`), so the host filesystem is invisible. - **Drop privileges.** Linux capabilities are reduced, and seccomp/AppArmor/SELinux profiles may restrict system calls. - **Exec the entrypoint.** The image's command replaces the runtime's setup code. From here on it is just your program. - **Stop.** To stop it, the runtime sends \`SIGTERM\` to PID 1, waits, then sends \`SIGKILL\`. In Kubernetes the wait is the Pod's \`terminationGracePeriodSeconds\` (default 30).`, components: [ { name: "Namespaces", description: "Isolate what a process can **see**: `pid`, `net`, `mnt`, `uts`, `ipc`, `user`, `cgroup` (and `time` on newer kernels)." }, { name: "cgroups", description: "Limit and meter what a process can **use**: CPU, memory, PIDs, block I/O. Modern distributions use the unified cgroup v2 hierarchy." }, { name: "Root filesystem / overlayfs", description: "Read-only image layers plus a thin writable layer, merged into one view that becomes the container's `/`." }, { name: "PID 1", description: "The first process in a PID namespace. It receives signals from the runtime and must reap zombie children; the kernel does not apply default signal actions to it, so an unhandled `SIGTERM` is simply ignored." }, { name: "Signals", description: "`SIGTERM` (15) asks for graceful shutdown and can be handled; `SIGKILL` (9) is enforced by the kernel and cannot be caught." }, { name: "Capabilities & seccomp", description: "Split root privileges into small units (e.g. `NET_BIND_SERVICE`) and filter which syscalls are allowed." } ], misconceptions: [ { myth: "A container has its own kernel.", reality: "All containers on a host share the **host kernel**. Only the view (namespaces) and budget (cgroups) differ. That is why a Linux container cannot run natively on a Windows kernel and why a kernel exploit affects every container on the node." }, { myth: "A CPU limit kills the container when exceeded, just like memory.", reality: "CPU is **compressible**: exceeding the limit causes **throttling** (the process waits). Memory is **incompressible**: exceeding the limit causes the kernel OOM killer to terminate a process, which Kubernetes reports as `OOMKilled`." }, { myth: 'root inside a container is harmless because it is "inside".', reality: "Without user namespaces, UID 0 in the container is UID 0 on the host, only constrained by capabilities, seccomp and the other layers. Escaping the namespace gives host root. That is why running as non-root is a security baseline." } ], example: { title: "See that a container is just a process", description: `On a Linux host with Docker (or any runtime), start a container and look at it from both sides. The same process has two PIDs: one inside its PID namespace and one on the host.`, language: "bash", code: `# start a long-running container docker run -d --name demo --memory 64m busybox sleep 3600 # inside: the process believes it is PID 1 docker exec demo ps # PID USER TIME COMMAND # 1 root 0:00 sleep 3600 # outside: the same sleep is an ordinary host process ps -ef | grep "sleep 3600" # compare namespaces of the host shell and the container process HOSTPID=$(docker inspect -f '{{.State.Pid}}' demo) sudo ls -l /proc/$HOSTPID/ns/ # net, pid, mnt ... differ from /proc/self/ns/ # graceful stop: SIGTERM, then SIGKILL after the timeout docker stop -t 10 demo` }, exercise: { prompt: `A Pod's container is configured with \`limits.memory: 128Mi\` and \`limits.cpu: 500m\`. Under load it uses 300m CPU and grows to 140Mi of memory. Describe what the kernel does for each resource, and what Kubernetes will report.`, solution: `CPU at 300m is under the 500m limit, so nothing happens. (Above 500m it would be throttled, never killed.) Memory at 140Mi exceeds the 128Mi cgroup limit. The kernel's OOM killer terminates the process in that cgroup. The kubelet reports the container's last state as **Terminated** with reason \`OOMKilled\` (exit code 137 = 128 + signal 9), and the container is restarted according to the Pod's \`restartPolicy\`. Repeated failures produce \`CrashLoopBackOff\`.` }, faq: [ { q: "Why does my container take exactly 30 seconds to stop?", a: "Your process probably ignores `SIGTERM` (common when a shell script is PID 1 and does not forward signals). Kubernetes waits `terminationGracePeriodSeconds` (default 30) and then sends `SIGKILL`. Handle `SIGTERM` in the app, or use `exec` in the entrypoint script so the app itself becomes PID 1." }, { q: "Do I need to memorize every namespace type for the KCNA?", a: "No. Know the concept: namespaces isolate what a process sees, cgroups limit what it uses, and containers share the host kernel. Recognizing `net` and `pid` namespaces helps explain why containers in a Pod share `localhost`." }, { q: "Are Linux namespaces the same as Kubernetes namespaces?", a: "No. A Kubernetes Namespace is an API-level grouping of objects (names, RBAC, quotas). It has nothing to do with kernel namespaces, which isolate processes on one host." } ], keyTerms: ["process", "PID 1", "Linux namespace", "cgroup", "OOM killer", "SIGTERM", "SIGKILL", "capabilities"], deeper: `Exit code 137 means "killed by signal 9" (128 + 9); 143 means \`SIGTERM\` (128 + 15). Tiny init processes such as \`tini\` exist solely to forward signals and reap zombies when your app is not written to be PID 1. Kubernetes can also share a single PID namespace across a Pod's containers with \`shareProcessNamespace: true\`. User namespaces for Pods (\`hostUsers: false\`) map container root to an unprivileged host UID; check its feature state for your cluster version before relying on it.`, references: [ { title: "namespaces(7) \u2013 Linux manual page", url: "https://man7.org/linux/man-pages/man7/namespaces.7.html" }, { title: "cgroups(7) \u2013 Linux manual page", url: "https://man7.org/linux/man-pages/man7/cgroups.7.html" }, { title: "Pod lifecycle \u2013 termination of Pods", url: "https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/" } ] }, { id: "networking-fundamentals", title: "Networking fundamentals", stage: 0, order: 2, objectives: ["co-networking"], scope: "foundation", estMinutes: 25, summary: "IP addresses identify hosts, ports identify processes, TCP and UDP carry data, DNS turns names into addresses, NAT rewrites addresses at boundaries, and load balancers spread traffic across backends.", what: `Kubernetes networking is built from a handful of classic ideas: - **IP address and CIDR** \u2013 \`10.244.1.17\` identifies an interface; \`10.244.0.0/16\` describes a range (the first 16 bits are the network part). - **Port** \u2013 a 16-bit number (0\u201365535) that identifies which process on a host gets a connection. An endpoint is \`IP:port\`. - **TCP vs UDP** \u2013 TCP is connection-oriented, ordered and retransmits lost data. UDP sends independent datagrams with no delivery guarantee but less overhead (DNS queries usually use UDP). - **DNS** \u2013 a distributed directory mapping names to records: \`A\` (IPv4), \`AAAA\` (IPv6), \`CNAME\` (alias), \`SRV\` (service + port). - **HTTP** \u2013 an application-layer (L7) request/response protocol with methods (\`GET\`, \`POST\`), paths, headers and status codes. - **NAT** \u2013 rewriting source (SNAT) or destination (DNAT) addresses as packets cross a boundary. - **Load balancing** \u2013 distributing connections or requests over several backends, at L4 (IP/port) or L7 (HTTP paths, headers).`, why: `Every Kubernetes networking object is one of these ideas wearing a new name. A **Service** is a stable virtual IP plus DNAT and L4 load balancing. **CoreDNS** is DNS. An **Ingress** or **Gateway** is an L7 HTTP load balancer. A **NodePort** is "open this port on every node". **NetworkPolicy** is a firewall on IPs and ports. If you can trace a plain \`curl\` across a network, you can trace a request into a cluster.`, problem: `Applications need to find and talk to each other while the machines underneath change. Hard-coding \`192.168.1.20:8080\` breaks the moment that server is replaced. DNS gives stable names, load balancers give one entry point for many replicas, and NAT lets many private addresses share fewer public ones. Kubernetes leans on exactly these tools because its backends (Pods) are replaced constantly.`, how: `Trace what happens when you run \`curl http://shop.example.com/cart\`: - **Resolve the name.** The client asks its configured resolver (see \`/etc/resolv.conf\`) for the \`A\` record of \`shop.example.com\`. The answer, say \`203.0.113.10\`, is cached for the record's TTL. - **Pick a source port.** The OS picks an ephemeral source port, e.g. \`54012\`. The connection is identified by the 5-tuple: protocol, source IP, source port, destination IP, destination port (80). - **Route the packet.** The host checks its routing table; the destination is not local, so the packet goes to the default gateway. - **NAT at the edge.** A home or cloud router rewrites the private source IP to a public one (**SNAT**) and remembers the mapping so replies come back. - **TCP handshake.** \`SYN\` \u2192 \`SYN-ACK\` \u2192 \`ACK\` establishes the connection. - **Load balancer.** \`203.0.113.10\` is a load balancer. An L4 balancer picks a backend by IP/port and forwards the connection (often using **DNAT**). An L7 balancer terminates the connection, reads the HTTP request, and can route \`/cart\` to a different backend pool than \`/search\`. - **HTTP exchange.** The backend receives \`GET /cart\`, returns \`200 OK\` (or \`404\`, \`503\`...), and the reply follows the NAT mappings back to the client.`, components: [ { name: "IP & CIDR", description: "Addresses and ranges. Private ranges such as `10.0.0.0/8`, `172.16.0.0/12` and `192.168.0.0/16` are not routable on the internet." }, { name: "Ports & sockets", description: "Ports below 1024 are privileged on Linux. Only one process can listen on a given IP:port at a time." }, { name: "DNS resolver", description: "Turns names into records. Search domains in `/etc/resolv.conf` let short names like `db` expand to longer ones; Kubernetes uses this heavily." }, { name: "NAT (SNAT/DNAT)", description: "Rewrites source or destination addresses. kube-proxy uses DNAT to send Service traffic to a Pod IP." }, { name: "L4 vs L7 load balancer", description: "L4 sees only IPs, ports and protocol; L7 understands HTTP and can route by host, path or header." } ], misconceptions: [ { myth: "DNS is only for websites on the internet.", reality: "Internal service discovery is DNS too. Inside Kubernetes, `my-svc.my-namespace.svc.cluster.local` is answered by the cluster DNS server (CoreDNS)." }, { myth: 'UDP is "unreliable" so nothing important uses it.', reality: "UDP simply leaves reliability to the application. DNS, many VPNs, video streaming and HTTP/3 (QUIC) all run on UDP." }, { myth: "A load balancer always understands HTTP.", reality: "Only L7 balancers do. An L4 balancer (like a Kubernetes Service of type `LoadBalancer` in most clouds) forwards TCP/UDP connections without looking at paths or headers." } ], example: { title: "Inspecting each layer from a shell", description: "These standard tools show the layers in order: name resolution, routing, and the HTTP exchange.", language: "bash", code: `# DNS: which IP does the name resolve to? dig +short example.com A nslookup example.com # Routing: which interface and gateway would be used? ip route get 1.1.1.1 # Listening sockets on this host (which process owns which port) ss -tlnp # HTTP: verbose output shows the TCP connect, request headers and status code curl -v http://example.com/` }, exercise: { prompt: `Three Pods run the same web app at \`10.244.1.5:8080\`, \`10.244.2.9:8080\` and \`10.244.3.4:8080\`. Clients should use one stable address and should not care when Pods are replaced. Using only the vocabulary of this lesson, describe the pieces you would need.`, solution: `- A **stable virtual IP** (and port) that clients connect to. - A **DNS name** that resolves to that virtual IP, so clients use a name rather than an address. - **DNAT + L4 load balancing** that rewrites each new connection's destination from the virtual IP to one of the healthy backend \`IP:8080\` endpoints. - A way to **update the backend list** whenever Pods come and go. That is exactly what a Kubernetes Service (ClusterIP), CoreDNS, kube-proxy and EndpointSlices provide together.` }, faq: [ { q: "What is the difference between a port and a protocol?", a: "The protocol (TCP or UDP) decides how data is carried; the port decides which process receives it. `53/UDP` and `53/TCP` are different sockets, which is why Kubernetes Service ports declare a `protocol` field (default TCP)." }, { q: "Why does /etc/resolv.conf matter in Kubernetes?", a: "The kubelet writes a `resolv.conf` into each Pod pointing at the cluster DNS Service and adding search domains like `.svc.cluster.local`. That is how a Pod can reach another Service by its short name." }, { q: "Is NAT the same as a firewall?", a: "No. NAT rewrites addresses; a firewall allows or drops traffic. They often run on the same box (and both use Linux netfilter), but they are separate functions." } ], keyTerms: ["CIDR", "port", "TCP", "UDP", "DNS A record", "SNAT/DNAT", "L4 load balancer", "L7 load balancer"], deeper: `Linux implements NAT and packet filtering in the kernel's netfilter framework, programmed by \`iptables\` or the newer \`nftables\`. kube-proxy writes such rules to implement Services. Some CNI plugins (e.g. Cilium) replace this path with eBPF programs attached in the kernel, which avoids long rule chains in very large clusters.`, references: [ { title: "Kubernetes \u2013 Services, Load Balancing, and Networking", url: "https://kubernetes.io/docs/concepts/services-networking/" }, { title: "MDN \u2013 An overview of HTTP", url: "https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview" } ] }, { id: "virtualization-and-containers", title: "Virtual machines vs containers", stage: 0, order: 3, objectives: ["kf-containerization"], scope: "kcna", estMinutes: 15, summary: "A VM virtualizes hardware and runs its own kernel; a container virtualizes the operating system view and shares the host kernel. That one difference explains their size, speed and isolation trade-offs.", what: `Think of a building. A **virtual machine** is a detached house: its own foundation, plumbing and walls. A **hypervisor** carves physical hardware into virtual hardware, and every VM boots a full **guest operating system with its own kernel**. A **container** is an apartment in a block: private rooms and a front door, but shared foundation and plumbing. Containers share the **host kernel** and get isolation from kernel features (namespaces, cgroups), not from virtual hardware. - **Type 1 hypervisor** \u2013 runs directly on hardware (e.g. KVM, Xen, ESXi, Hyper-V). Used by cloud providers. - **Type 2 hypervisor** \u2013 runs as an application on a host OS (e.g. VirtualBox). Common on laptops. - **Container runtime** \u2013 sets up namespaces, cgroups and the root filesystem and starts a process (e.g. containerd + runc).`, why: `The KCNA expects you to explain why the industry moved application packaging to containers and where VMs still fit. In practice they are combined: almost every managed Kubernetes cluster runs its **nodes as VMs**, and containers inside them. VMs give strong tenant isolation between customers; containers give fast, dense, portable packaging of apps.`, problem: `VMs solved "one app per physical server" waste, but each VM still carries a whole OS: gigabytes of disk, a boot sequence measured in tens of seconds, its own patching. Teams that wanted to deploy many small services dozens of times a day found that too heavy. They also suffered "works on my machine" bugs because the app's dependencies were not packaged with it. Containers package the app with its userland dependencies, start in well under a second (no kernel to boot), and pack many workloads onto one host.`, how: `Compare how each one starts an nginx web server: - **VM:** the hypervisor allocates virtual CPUs, RAM and disks \u2192 virtual firmware runs \u2192 the guest kernel boots \u2192 init system starts services \u2192 nginx starts. Isolation boundary: **virtual hardware**. A guest kernel bug stays inside the VM. - **Container:** the runtime unpacks the image layers \u2192 creates namespaces and a cgroup \u2192 switches the root filesystem \u2192 execs \`nginx\` as a process on the host kernel. Isolation boundary: **kernel features**. A host kernel bug can affect every container. Middle ground exists for untrusted workloads: - **Sandboxed runtimes** such as **gVisor** (a user-space kernel that intercepts syscalls) and **Kata Containers** (each Pod in a lightweight VM) keep the container workflow but add a stronger boundary. Kubernetes selects them per Pod with a **RuntimeClass**.`, components: [ { name: "Hypervisor", description: "Software that presents virtual hardware to guest OSes. Type 1 runs on bare metal; type 2 runs on top of a host OS." }, { name: "Guest OS / kernel", description: "Each VM boots its own kernel, so VMs can run different OS families (Linux and Windows) side by side." }, { name: "Container runtime", description: "Creates isolated processes on the shared host kernel; e.g. containerd or CRI-O with runc underneath." }, { name: "Sandboxed runtime", description: "gVisor or Kata Containers: container interface, VM-like isolation, some performance cost." } ], misconceptions: [ { myth: "Containers are just lightweight VMs.", reality: 'They are a different mechanism. A VM emulates hardware and boots a kernel; a container is an isolated **process** on an already-running kernel. That is why there is no "boot" when a container starts.' }, { myth: "Kubernetes replaces virtual machines.", reality: "Kubernetes schedules containers onto **nodes**, and nodes are very often VMs. The layers are complementary." }, { myth: "A Linux container image runs on any OS.", reality: "Because the kernel is shared, a Linux image needs a Linux kernel. Docker Desktop on macOS or Windows quietly runs a small Linux VM to provide one. Windows containers likewise need a Windows host." } ], example: { title: "Side-by-side comparison", description: "Typical, order-of-magnitude characteristics. Exact numbers vary by workload and platform.", language: "text", code: ` Virtual machine Container Isolation boundary virtual hardware kernel namespaces + cgroups Kernel own guest kernel shared host kernel Typical size gigabytes (full OS) megabytes (app + userland libs) Start time seconds to minutes milliseconds to seconds Density per host tens hundreds Mixed OS families yes (Linux + Windows) no (must match host kernel) Typical use tenant isolation, nodes packaging and running apps` }, exercise: { prompt: `A SaaS company runs code uploaded by untrusted customers. They want container-style packaging and Kubernetes scheduling, but are worried that one customer could exploit a kernel bug to reach another customer's workload. What options does this lesson give them?`, solution: `- Run those Pods with a **sandboxed runtime** (gVisor or Kata Containers) selected through a **RuntimeClass**, adding a boundary beyond the shared kernel. - Or give each customer **separate nodes (VMs)** or even separate clusters, so the hypervisor is the boundary between tenants. Plain containers alone share the host kernel, so they are a weaker boundary for hostile multi-tenancy.` }, faq: [ { q: "If containers share the kernel, how can an Alpine container run on an Ubuntu host?", a: "Distributions differ in **userland** (libraries, package manager, shell), not in the kernel interface. The Alpine image brings its own userland files; it only borrows the Ubuntu host's Linux kernel." }, { q: "Are containers secure enough for production?", a: "Yes for most workloads, when combined with good practices: non-root users, dropped capabilities, seccomp, read-only root filesystems and Pod Security Admission. For hostile multi-tenant code, add sandboxed runtimes or VM boundaries." }, { q: "What is RuntimeClass on the exam?", a: "Know it exists: a Kubernetes object that lets a Pod choose which runtime configuration (for example a gVisor or Kata handler) the node should use, via `spec.runtimeClassName`." } ], keyTerms: ["hypervisor", "virtual machine", "guest kernel", "container", "shared kernel", "gVisor", "Kata Containers", "RuntimeClass"], deeper: `Hardware virtualization extensions (Intel VT-x, AMD-V) make type 1 hypervisors fast. MicroVM projects such as Firecracker shrink the VM to the essentials so it boots in a fraction of a second; serverless platforms use them to get VM isolation with near-container speed.`, references: [ { title: "Kubernetes \u2013 Containers", url: "https://kubernetes.io/docs/concepts/containers/" }, { title: "Kubernetes \u2013 Runtime Class", url: "https://kubernetes.io/docs/concepts/containers/runtime-class/" } ] }, { id: "container-images-and-oci", title: "Container images, OCI and registries", stage: 0, order: 4, objectives: ["kf-containerization"], scope: "kcna", estMinutes: 25, summary: "An image is a stack of read-only filesystem layers plus a config, described by a manifest and addressed by content digests. The OCI specifications standardize image format, runtime behaviour and registry distribution so any compliant tool can build, push, pull and run it.", what: `A **container image** is a portable, read-only template for a container. Concretely it is: - a set of **layers** \u2013 compressed tarballs, each holding the filesystem changes of one build step; - an **image config** \u2013 JSON with the default command, environment, working directory, user and the ordered list of layer digests; - a **manifest** \u2013 JSON that points to the config and the layers by **digest** (\`sha256:...\`). An **image index** can point to several manifests, one per platform (e.g. \`linux/amd64\`, \`linux/arm64\`). **Docker** popularized this format and the \`Dockerfile\` build recipe. The **Open Container Initiative (OCI)**, a Linux Foundation project, now standardizes it in three specs: - **Image spec** \u2013 the manifest, config and layer formats. - **Runtime spec** \u2013 how to run an unpacked "bundle" (filesystem + \`config.json\`); \`runc\` is the reference implementation. - **Distribution spec** \u2013 the HTTP API registries use for push and pull.`, why: `Kubernetes never builds images; it only **pulls** them by reference and asks a runtime to run them. Because of OCI, an image built with Docker, Buildah, kaniko or BuildKit runs on containerd or CRI-O and can live in any compliant registry (Docker Hub, GHCR, Harbor, cloud registries). That is also why removing dockershim in Kubernetes 1.24 did **not** break Docker-built images. Tags vs digests is a frequent exam and production topic: it decides whether a rollout is reproducible.`, problem: `"It works on my machine" usually means the machine had a library, a config file or a runtime version the server did not. Images freeze the userland into an artifact you can version and ship. Layering then fixes the next problem: shipping a 500 MB file for every one-line change would be wasteful, so only changed layers are rebuilt, pushed and pulled.`, how: `Trace a build and a pull: - **Build.** Each filesystem-changing Dockerfile instruction (\`FROM\`, \`COPY\`, \`RUN\`, \`ADD\`) produces a layer. Metadata instructions (\`CMD\`, \`ENV\`, \`EXPOSE\`, \`USER\`) only change the config. - **Cache.** If an instruction and its inputs are unchanged, the builder reuses the cached layer. A change invalidates that layer **and every layer after it** \u2013 so put rarely-changing steps (installing dependencies) before frequently-changing ones (copying source). - **Hash.** Every layer and the config get a SHA-256 **digest** of their content. The manifest lists these digests; the manifest's own digest identifies the whole image. - **Push.** The client uploads blobs the registry does not already have, then uploads the manifest under a **tag** such as \`v1.4.2\`. - **Pull.** The node resolves the tag to a manifest digest, downloads only missing layers, and verifies every blob against its digest. - **Run.** The runtime stacks the read-only layers with overlayfs and adds a thin **writable container layer** on top. Files written there disappear with the container.`, components: [ { name: "Layer", description: "Read-only, content-addressed tarball of filesystem changes. Shared between images that use the same base." }, { name: "Manifest / image index", description: `Manifest lists config + layers for one platform; an index (multi-arch "manifest list") picks the right manifest for the node's OS/CPU.` }, { name: "Tag", description: "Human-friendly, **mutable** pointer like `nginx:1.27` or `latest`. Can be moved to a different image at any time." }, { name: "Digest", description: "**Immutable** content hash like `nginx@sha256:...`. The same digest always means exactly the same bytes." }, { name: "Registry", description: "Server implementing the OCI distribution API, organized into repositories. Public or private (credentials via `imagePullSecrets`)." }, { name: "OCI runtime (runc)", description: "Low-level runtime that takes an unpacked bundle and creates the container process with namespaces and cgroups." } ], misconceptions: [ { myth: "Kubernetes needs Docker installed on every node.", reality: "Since 1.24 the kubelet talks only to CRI runtimes such as **containerd** or **CRI-O**. Docker-built images are OCI images, so they still run unchanged." }, { myth: "The tag `latest` means the newest version.", reality: "`latest` is just the default tag name when none is given. It points wherever someone last pushed it. In Kubernetes, `:latest` (or no tag) also makes the default `imagePullPolicy` `Always`." }, { myth: "Deleting a file in a later Dockerfile step makes the image smaller.", reality: 'Earlier layers are immutable; the deletion is only a "whiteout" marker in a new layer. The bytes still ship. Clean up in the **same** `RUN` step, or use a multi-stage build.' } ], example: { title: "A cache-friendly, multi-stage Dockerfile", description: `Dependencies are copied and installed before the source code, so a code-only change reuses the dependency layer. The final stage copies only the built binary into a small base, leaving compilers behind.`, language: "text", code: `# ---- build stage ---- FROM golang:1.23 AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download # cached until go.mod/go.sum change COPY . . RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app # ---- runtime stage ---- FROM gcr.io/distroless/static:nonroot COPY --from=build /out/app /app USER nonroot ENTRYPOINT ["/app"] # Build, tag and push; then deploy by digest for reproducibility: # docker build -t registry.example.com/shop/app:1.4.2 . # docker push registry.example.com/shop/app:1.4.2 # image: registry.example.com/shop/app@sha256:` }, exercise: { prompt: `Your Deployment uses \`image: shop/api:prod\`. Someone re-pushes the \`prod\` tag with a broken build. Two nodes already have the old image cached; a third node pulls fresh. Explain what happens and how to prevent it.`, solution: `With an explicit non-\`latest\` tag, the default \`imagePullPolicy\` is \`IfNotPresent\`. The two nodes reuse their cached (old) image; the new node pulls the moved tag and gets the broken build. The same Deployment now runs **two different images**. Prevent it by deploying **immutable references**: a unique tag per build (e.g. a version or commit SHA) that is never re-pushed, or better, pin the **digest** (\`shop/api@sha256:...\`). Many registries can also enforce tag immutability.` }, faq: [ { q: "Is a Dockerfile part of the OCI standard?", a: "No. OCI standardizes the **resulting** image, the runtime bundle and the registry API. The Dockerfile is just one popular build recipe; Buildpacks or other tools can produce OCI images without one." }, { q: "What is the difference between containerd and runc?", a: "**containerd** is a high-level runtime: it pulls images, manages storage and snapshots, and exposes the CRI to the kubelet. **runc** is the low-level OCI runtime that containerd invokes to actually create each container process." }, { q: "How does a node pull from a private registry?", a: "Create a Secret of type `kubernetes.io/dockerconfigjson` and reference it in the Pod's `imagePullSecrets` (directly or through the ServiceAccount). Some clouds also provide node-level credential plugins." } ], keyTerms: ["image layer", "manifest", "digest", "tag", "registry", "OCI image spec", "OCI runtime spec", "OCI distribution spec"], deeper: `Because OCI registries store arbitrary content-addressed blobs, they now hold more than images: Helm charts, signatures and SBOMs (OCI "artifacts" and the \`subject\`/referrers mechanism). Supply-chain tools such as Sigstore cosign attach signatures to an image digest this way, so a signature can never silently follow a moved tag.`, references: [ { title: "Open Container Initiative", url: "https://opencontainers.org/" }, { title: "Kubernetes \u2013 Images", url: "https://kubernetes.io/docs/concepts/containers/images/" }, { title: "Kubernetes \u2013 Container Runtime Interface", url: "https://kubernetes.io/docs/concepts/architecture/cri/" } ], diagram: "container-layers" }, { id: "yaml-for-kubernetes", title: "YAML for Kubernetes manifests", stage: 0, order: 5, objectives: ["kf-core"], scope: "foundation", estMinutes: 20, summary: "YAML expresses maps, lists and scalars through indentation. Every Kubernetes manifest uses the same top-level shape: apiVersion, kind, metadata, spec \u2013 and the cluster writes back status.", what: `**YAML** is a human-friendly data format (a superset of JSON). Kubernetes reads it, converts it to JSON, and sends it to the API server. You only need three building blocks: - **Mapping** (key: value) \u2013 \`name: web\` - **Sequence** (list) \u2013 items start with \`- \` - **Scalar** \u2013 strings, numbers, booleans, null Indentation with **spaces** (never tabs) shows nesting. \`#\` starts a comment. \`---\` separates multiple documents in one file. Every Kubernetes object has the same skeleton: - \`apiVersion\` \u2013 API group and version, e.g. \`v1\` (core group) or \`apps/v1\` - \`kind\` \u2013 the type, e.g. \`Pod\`, \`Deployment\` - \`metadata\` \u2013 \`name\`, \`namespace\`, \`labels\`, \`annotations\` - \`spec\` \u2013 the **desired state** you write - \`status\` \u2013 the **observed state** the cluster writes; you do not author it`, why: `Declarative Kubernetes is "write YAML, apply it". Most beginner errors are YAML errors, not Kubernetes errors: wrong indentation turns a field into a sibling of the field it belonged to, a missing \`-\` turns a list into a map, and an unquoted value becomes the wrong type. Reading YAML fluently is a prerequisite for every later lesson and for the exam's manifest-reading questions.`, problem: `Raw JSON is noisy to read and cannot hold comments; ad-hoc command flags cannot be reviewed or versioned. YAML gives a readable, diffable text form for API objects that can live in Git. The cost is that whitespace and typing rules are strict and sometimes surprising.`, how: `Read any manifest in this order: - **Find the type.** \`apiVersion\` + \`kind\` tell you which schema applies. \`kubectl explain .\` shows that schema. - **Find the identity.** \`metadata.name\` (+ \`namespace\`) identifies the object; labels are for selection, annotations for extra notes. - **Walk the spec by indentation.** Children are indented further than their parent. Items at the same indentation are siblings. - **Spot lists.** A \`- \` begins a new list item; every key indented under it belongs to that item. \`containers:\` is always a list, even with one container. - **Check scalar types.** \`8080\` is a number, \`"8080"\` a string, \`true\` a boolean. Fields typed as strings (like env \`value\`) need quotes around numbers or booleans. - **Submit.** \`kubectl apply -f file.yaml\` parses the YAML, validates it against the schema on the server, and stores it.`, components: [ { name: "apiVersion", description: "`/` such as `apps/v1`, or just `v1` for the core group (Pods, Services, ConfigMaps)." }, { name: "kind", description: "Object type, CamelCase: `Pod`, `Deployment`, `Service`." }, { name: "metadata", description: "Identity and tags: `name`, `namespace`, `labels`, `annotations`. The server adds `uid`, `resourceVersion`, `creationTimestamp`." }, { name: "spec", description: "Desired state; structure depends on `kind`." }, { name: "status", description: "Observed state written by controllers and the kubelet. Ignored if you include it in `apply`." } ], misconceptions: [ { myth: "Tabs and spaces are interchangeable in YAML.", reality: "Tabs are **not allowed** for indentation. Use spaces (2 per level is the Kubernetes convention)." }, { myth: "Every value is a string, so quoting does not matter.", reality: 'YAML infers types. `value: 8080` in an env var is a number and the API rejects it because the field must be a string; write `value: "8080"`. Words like `yes`, `no`, `on`, `true` can also be read as booleans.' }, { myth: "You should copy the `status` section when saving a manifest.", reality: "`status` is owned by the cluster. Strip it (and server fields like `uid`, `resourceVersion`) from manifests you keep in Git." } ], example: { title: "Spot the three bugs", description: `This Pod manifest is broken in three ways: the \`env\` value is an unquoted number, \`image\` is indented as a sibling of \`containers\` instead of inside the list item, and \`ports\` is a map where a list is required. The corrected version follows.`, language: "yaml", code: `# BROKEN apiVersion: v1 kind: Pod metadata: name: web spec: containers: - name: web image: nginx:1.27 # wrong level: belongs to the container item ports: containerPort: 80 # must be a list item env: - name: PORT value: 8080 # must be a string --- # FIXED apiVersion: v1 kind: Pod metadata: name: web labels: app: web spec: containers: - name: web image: nginx:1.27 ports: - containerPort: 80 env: - name: PORT value: "8080"` }, exercise: { prompt: `Without running anything, answer: in the FIXED manifest above, (1) how many containers are defined, (2) what is the full field path to the container port, and (3) which API group does \`v1\` refer to?`, solution: `(1) One container \u2013 \`containers\` is a list with a single \`- name: web\` item. (2) \`spec.containers[0].ports[0].containerPort\`. (3) The **core** (legacy) API group, which has no group name \u2013 hence just \`v1\` rather than \`/v1\`.` }, faq: [ { q: "How do I find which fields a kind supports?", a: "Use `kubectl explain`, e.g. `kubectl explain pod.spec.containers` or add `--recursive` for the whole tree. It reads the schema from your cluster, so it matches your version." }, { q: "Can I write manifests in JSON instead?", a: "Yes. kubectl accepts JSON, and `kubectl get -o json` prints objects that way. YAML is the convention because it is easier to read and comment." }, { q: "Where do apiVersion values like apps/v1 come from?", a: "From the API groups your cluster serves. `kubectl api-resources` lists each resource with its group/version and whether it is namespaced." } ], keyTerms: ["mapping", "sequence", "scalar", "apiVersion", "kind", "metadata", "spec", "status"], deeper: `Kubernetes converts YAML to JSON before validation, so anything YAML can express beyond JSON (anchors \`&\`/\`*\`, merge keys) is resolved client-side and never reaches the API server. Block scalars \`|\` (keep newlines) and \`>\` (fold lines) are handy for embedding scripts or config files in ConfigMaps.`, references: [ { title: "Kubernetes \u2013 Objects in Kubernetes", url: "https://kubernetes.io/docs/concepts/overview/working-with-objects/" }, { title: "YAML 1.2.2 specification", url: "https://yaml.org/spec/1.2.2/" }, { title: "kubectl reference", url: "https://kubernetes.io/docs/reference/kubectl/" } ] }, { id: "distributed-systems-basics", title: "Distributed systems basics", stage: 0, order: 6, objectives: ["cna-ecosystem"], scope: "foundation", estMinutes: 20, summary: "In a distributed system, networks drop messages and machines die. Kubernetes copes with consensus (Raft in etcd), idempotent operations, retries, and eventual consistency driven by control loops.", what: `A **distributed system** is several computers cooperating over a network so that they appear as one service. Four ideas carry most of the weight: - **Partial failure** \u2013 some parts fail while others keep running; a slow node and a dead node can look identical. - **Consensus** \u2013 getting a group of machines to agree on one value (or one ordered log) despite failures. **Raft** does this with an elected leader and a **majority quorum**. - **Idempotency** \u2013 an operation that has the same effect whether it is applied once or many times ("set replicas to 3" vs "add 1 replica"). Idempotent operations make retries safe. - **Eventual consistency** \u2013 the system may be temporarily out of sync but converges to the correct state if no new changes arrive.`, why: `A Kubernetes cluster *is* a distributed system. etcd uses Raft, so you must run an odd number of members and keep a majority alive. Controllers converge **eventually** \u2013 a new Deployment is not "done" when \`kubectl apply\` returns. Rolling updates, retries and self-healing only work because operations are expressed as idempotent desired state. These ideas explain dozens of exam answers that otherwise look arbitrary.`, problem: `Three short failure stories: - **The double charge.** A payment request times out; the client retries; both requests actually succeeded. The fix: an **idempotency key** so the second request is recognized and ignored. - **The split brain.** A network splits two database servers; each thinks the other is dead and both accept writes. The fix: require a **majority quorum** \u2013 at most one side can have a majority, so only it may write. - **The stale read.** A cache still shows a deleted item for a few seconds. Often acceptable \u2013 that is **eventual consistency** \u2013 as long as the system converges.`, how: `How Raft (used by etcd) keeps one consistent log: - **Roles.** Each member is a **follower**, **candidate** or **leader**. Normally there is exactly one leader. - **Election.** If a follower hears no heartbeat within a randomized timeout, it becomes a candidate, increments the **term**, and asks for votes. A candidate with votes from a **majority** becomes leader. - **Replication.** All writes go to the leader, which appends them to its log and sends them to followers. - **Commit.** Once a **majority** has stored the entry, it is committed and applied; the client gets success. - **Fault tolerance.** With *n* members, quorum is \u230An/2\u230B+1. Three members tolerate one failure; five tolerate two. Four members still tolerate only one, which is why odd sizes are recommended. - **Losing quorum.** If a majority is unreachable, the cluster refuses writes rather than risk divergence \u2013 it chooses **consistency over availability** during the partition (the CAP trade-off).`, components: [ { name: "Raft leader", description: "Single member that orders all writes; re-elected automatically if it fails." }, { name: "Quorum", description: "A majority of members, \u230An/2\u230B+1. Needed both to elect a leader and to commit a write." }, { name: "Idempotent operation", description: 'Safe to repeat. Declarative "make it look like this" requests are naturally idempotent.' }, { name: "Optimistic concurrency", description: "Writers include the version they read; the write fails if someone changed it meanwhile. Kubernetes uses `metadata.resourceVersion` for this." }, { name: "Eventual consistency", description: "Components converge to the desired state over time instead of all at once." } ], misconceptions: [ { myth: "More etcd members always means more resilience.", reality: "Fault tolerance only increases at odd sizes (3 \u2192 1 failure, 5 \u2192 2). Every extra member also adds replication latency to every write. 3 or 5 is typical." }, { myth: "If an API call succeeded, the change is already in effect everywhere.", reality: "A successful `kubectl apply` means the desired state was **stored in etcd**. Scheduling, image pulls and container starts happen afterwards, asynchronously." }, { myth: "Retries are always safe.", reality: 'Only for **idempotent** operations. Retrying a non-idempotent "increment" or "charge" can apply it twice.' } ], example: { title: "Optimistic concurrency in Kubernetes", description: `Every object carries a \`resourceVersion\`. If two clients read version 4812 and both try to update, the first update wins and the second gets an HTTP **409 Conflict**; it must re-read and retry. Nothing is locked, and no update is silently lost.`, language: "bash", code: `kubectl get deployment web -o jsonpath='{.metadata.resourceVersion}' # 4812 # client A and client B both edit based on 4812 # A's update is accepted -> resourceVersion becomes 4830 # B's update still claims 4812 -> rejected: # Error from server (Conflict): Operation cannot be fulfilled on # deployments.apps "web": the object has been modified; # please apply your changes to the latest version and try again` }, exercise: { prompt: `An etcd cluster has 5 members spread across 3 zones as 2 + 2 + 1. Zone A (2 members) goes offline. Can the cluster still accept writes? What if zone B also fails?`, solution: `Quorum for 5 members is 3. After zone A fails, 3 members remain (zone B: 2, zone C: 1) \u2013 still a majority, so the cluster elects a leader if needed and keeps accepting writes. If zone B also fails, only 1 member remains. It cannot form a majority, so the cluster **stops accepting writes** (reads of possibly-stale data aside) until quorum is restored. Running Pods keep running \u2013 the kubelets just cannot get new instructions.` }, faq: [ { q: "What is the difference between level-triggered and edge-triggered?", a: 'Edge-triggered logic reacts to **events** ("a Pod was deleted"); if an event is missed, the system is wrong forever. Level-triggered logic looks at the **current state** each time ("are there 3 Pods?"). Kubernetes controllers are level-triggered, which makes them robust to missed events and restarts.' }, { q: "Does the KCNA test the Raft algorithm in detail?", a: "No. Know that etcd uses Raft-based consensus, needs a majority quorum, and is usually run with an odd number of members (3 or 5)." }, { q: 'Is Kubernetes "eventually consistent" in its storage?', a: "etcd itself is strongly consistent (linearizable writes via Raft). The **cluster as a whole** \u2013 Pods, endpoints, DNS \u2013 is eventually consistent, because many independent controllers converge the actual state towards the stored desired state." } ], keyTerms: ["partial failure", "consensus", "Raft", "quorum", "idempotency", "eventual consistency", "CAP theorem", "resourceVersion"], deeper: `The CAP theorem says that during a network partition a system must choose between consistency and availability. etcd chooses consistency (a minority partition rejects writes). Many caches and DNS systems choose availability and accept stale answers. The "fallacies of distributed computing" (the network is reliable, latency is zero, ...) are a useful checklist when designing cloud-native services.`, references: [ { title: "The Raft consensus algorithm", url: "https://raft.github.io/" }, { title: "etcd documentation", url: "https://etcd.io/docs/" }, { title: "Kubernetes \u2013 Controllers", url: "https://kubernetes.io/docs/concepts/architecture/controller/" } ] }, // ───────────────────────────── Stage 1 · Architecture ───────────────────────────── { id: "cluster-architecture", title: "Cluster architecture: control plane and nodes", stage: 1, order: 1, objectives: ["kf-core"], scope: "kcna", estMinutes: 20, summary: "A Kubernetes cluster is a control plane that stores and decides, plus worker nodes that run containers. Every component talks through the API server; only the API server talks to etcd.", what: `A **cluster** has two halves. The **control plane** is the brain. It stores the desired state and makes decisions: - **kube-apiserver** \u2013 the front door; every read and write goes through it. - **etcd** \u2013 consistent key-value store holding all cluster data. - **kube-scheduler** \u2013 picks a node for each new Pod. - **kube-controller-manager** \u2013 runs the built-in control loops (Deployments, ReplicaSets, Nodes, Jobs...). - **cloud-controller-manager** \u2013 optional; integrates with a cloud provider (load balancers, node lifecycle, routes). **Worker nodes** are the muscle. Each one runs: - **kubelet** \u2013 the node agent that makes the containers for its Pods actually run. - **container runtime** \u2013 containerd or CRI-O, behind the CRI. - **kube-proxy** \u2013 programs node networking for Services (optional with some CNI plugins). Cluster **add-ons** such as CoreDNS and a CNI network plugin run on top, usually as Pods themselves.`, why: `The KCNA's largest domain starts here: you will be asked which component does what, which component stores state, and what happens when one fails. The picture also explains the design principle that runs through all of Kubernetes: components are **decoupled** and communicate only through the API, so each can be replaced, scaled or restarted independently.`, problem: `Running containers by hand on many servers raises questions no single machine can answer: which server has room? What happens when a server dies at 3 a.m.? Who notices that only 2 of 3 copies are running? Kubernetes centralizes the **record of what should exist** and distributes the **work of making it so** to small, specialized components and node agents.`, how: `Think of it as a company: - **etcd** is the filing cabinet: the only place the truth is written down. - **kube-apiserver** is the front desk. Nobody opens the cabinet directly; everyone files and reads through the desk, which checks identity and permissions. - **Controllers** are department managers who keep reading the files and issuing work orders: "the Deployment wants 3 Pods, I see 2, file one more Pod". - **kube-scheduler** is the dispatcher who assigns each new Pod to a node by writing the node's name onto it. - **kubelets** are site supervisors on each node. Each watches the front desk for Pods assigned to its node, gets them running through the container runtime, and reports status back. - **kube-proxy** on each node maintains the routing so traffic for a Service reaches one of its Pods. Important consequences: - **Hub and spoke.** Components do not call each other; they all **watch** the API server. Only the API server talks to etcd. - **Running Pods survive control-plane outages.** If the control plane is down, existing containers keep running; you just cannot change anything, and failed Pods are not replaced. - **High availability** means running several API servers behind a load balancer, an odd number of etcd members, and leader election for the scheduler and controller manager (one active instance each).`, components: [ { name: "kube-apiserver", description: "REST front end and the only component that reads/writes etcd. Horizontally scalable." }, { name: "etcd", description: "Raft-replicated key-value store; the cluster's single source of truth. Back it up." }, { name: "kube-scheduler", description: "Assigns unscheduled Pods to nodes; does not start containers itself." }, { name: "kube-controller-manager", description: "One binary running many controllers (Deployment, ReplicaSet, Node, Job, EndpointSlice, ServiceAccount...)." }, { name: "kubelet", description: "Node agent: registers the node, runs Pods assigned to it via the CRI, executes probes, reports status." }, { name: "kube-proxy", description: "Implements Service virtual IPs on each node using iptables, IPVS or nftables rules." } ], misconceptions: [ { myth: "The scheduler starts containers on the chosen node.", reality: "The scheduler only **records the decision** (it binds the Pod, setting `spec.nodeName`). The kubelet on that node notices and starts the containers." }, { myth: "If the control plane goes down, all applications stop.", reality: "Running containers keep running and kube-proxy rules stay in place. What stops is change: no new scheduling, no self-healing, no `kubectl` operations." }, { myth: "The kubelet runs only on worker nodes.", reality: "In kubeadm-style clusters the control-plane machines also run a kubelet, which starts the control-plane components as **static Pods** from manifest files on disk." } ], example: { title: "Look at the architecture of a running cluster", description: "In a kubeadm or kind cluster the control-plane components are visible as Pods in `kube-system`. In managed services (EKS, GKE, AKS) the control plane is hidden and you see only nodes and add-ons.", language: "bash", code: `kubectl get nodes -o wide # NAME STATUS ROLES VERSION # kind-control-plane Ready control-plane v1.31.0 # kind-worker Ready v1.31.0 kubectl get pods -n kube-system # etcd-kind-control-plane 1/1 Running # kube-apiserver-kind-control-plane 1/1 Running # kube-controller-manager-kind-control-plane 1/1 Running # kube-scheduler-kind-control-plane 1/1 Running # kube-proxy-7xk2p 1/1 Running # coredns-6f6b679f8f-9q2lw 1/1 Running kubectl cluster-info` }, exercise: { prompt: `For each event, name the component that reacts first: (a) a user runs \`kubectl apply\`; (b) a new Pod object exists with no node assigned; (c) a Pod has been assigned to \`node-3\`; (d) a ReplicaSet wants 3 Pods but only 2 exist; (e) the cluster needs a cloud load balancer for a Service.`, solution: `(a) **kube-apiserver** \u2013 authenticates, authorizes, admits and stores the object in etcd. (b) **kube-scheduler** \u2013 selects a node and binds the Pod. (c) The **kubelet on node-3** \u2013 asks the container runtime (via CRI) to start the containers. (d) The **ReplicaSet controller** in kube-controller-manager \u2013 creates one more Pod object. (e) The service controller in the **cloud-controller-manager** \u2013 provisions the load balancer through the cloud API.` }, faq: [ { q: "How many control-plane nodes should production have?", a: "Commonly **3** (with stacked etcd, matching an odd etcd member count) so one can fail. Managed Kubernetes services run and scale the control plane for you." }, { q: "Is CoreDNS part of the control plane?", a: "It is a **cluster add-on**, not one of the core control-plane components, but nearly every cluster runs it because Service discovery depends on DNS." }, { q: "Can I run my own Pods on control-plane nodes?", a: "Control-plane nodes are normally **tainted** (`node-role.kubernetes.io/control-plane:NoSchedule`) so ordinary Pods avoid them. Single-node test clusters often remove that taint." } ], keyTerms: ["control plane", "worker node", "kube-apiserver", "etcd", "kube-scheduler", "kube-controller-manager", "kubelet", "kube-proxy"], deeper: `Nodes send heartbeats by renewing **Lease** objects in the \`kube-node-lease\` namespace; the node controller marks a node \`NotReady\`/\`Unknown\` when leases stop and, after a grace period, Pods on it are evicted via taints. etcd can be **stacked** (on the control-plane nodes) or **external** (its own machines), trading simplicity against failure isolation.`, references: [ { title: "Kubernetes Components", url: "https://kubernetes.io/docs/concepts/overview/components/" }, { title: "Cluster Architecture", url: "https://kubernetes.io/docs/concepts/architecture/" } ], diagram: "cluster-architecture" }, { id: "api-server-and-etcd", title: "The API server and etcd", stage: 1, order: 2, objectives: ["kf-core"], scope: "kcna", estMinutes: 25, summary: "Every request to Kubernetes passes through the API server: authentication, authorization, mutating admission, validation, validating admission, then persistence in etcd. Clients then learn about changes through watches.", what: `**kube-apiserver** exposes the Kubernetes API as RESTful HTTP resources such as \`/api/v1/namespaces/default/pods\` and \`/apis/apps/v1/namespaces/default/deployments\`. kubectl, controllers, the scheduler, kubelets and your own tools are all just clients of it. **etcd** is a distributed, strongly consistent key-value store. The API server serializes every object and stores it under keys (by default with the prefix \`/registry/\`). etcd is the **single source of truth**: if it is not in etcd, the cluster does not know about it. The API server is the **only** component that talks to etcd. Everything else goes through the API server, which enforces security and validation consistently.`, why: `Exam questions love the request chain ("which step rejects a request with an invalid token?", "where would a policy that injects a sidecar run?"). Operationally, etcd is the one stateful thing whose loss destroys the cluster's memory \u2013 which is why backing it up is a core admin task.`, problem: `If every component wrote directly to the database, each would need to re-implement authentication, permission checks, schema validation and defaults, and one buggy component could corrupt everything. Centralizing access in one API server gives one place for security policy, one place for validation, and one consistent change feed that every client can watch.`, how: `Follow \`kubectl apply -f deploy.yaml\` through the API server: - **Transport.** kubectl reads kubeconfig and sends an HTTPS request with credentials (client certificate, bearer token, or OIDC token). - **Authentication (who are you?).** Authenticator modules identify the user or ServiceAccount. Failure \u2192 \`401 Unauthorized\`. - **Authorization (may you do this?).** Usually **RBAC** (with the Node authorizer for kubelets) checks verb + resource + namespace. Denied \u2192 \`403 Forbidden\`. - **Mutating admission.** Admission controllers and mutating webhooks may **change** the object \u2013 e.g. set defaults, inject a sidecar, add a default StorageClass. - **Schema validation.** The object is checked against the API schema (field types, required fields). - **Validating admission.** Controllers, webhooks and ValidatingAdmissionPolicies may **reject** the object \u2013 e.g. Pod Security Admission, ResourceQuota. They cannot modify it. - **Persist.** The object is written to etcd; \`resourceVersion\` is updated. The client gets \`200\`/\`201\`. - **Notify.** Every client **watching** that resource type receives an event (ADDED/MODIFIED/DELETED) \u2013 this is how the Deployment controller learns it has work to do.`, components: [ { name: "Authentication", description: "Identifies the caller: X.509 client certs, bearer/ServiceAccount tokens, OIDC. Kubernetes has no User objects; users are external identities." }, { name: "Authorization", description: "Decides allow/deny, typically via RBAC and the Node authorizer. Default is deny." }, { name: "Admission control", description: "Mutating phase (may modify) then validating phase (may only reject). Built-in plugins plus webhooks and CEL-based ValidatingAdmissionPolicy." }, { name: "Watch", description: "Long-lived request that streams changes after a given `resourceVersion`; the basis of every controller." }, { name: "etcd", description: "Raft-replicated key-value store holding all API objects. Run 3 or 5 members; take regular snapshots." } ], misconceptions: [ { myth: "The scheduler and controllers read etcd directly for speed.", reality: "Only the API server reads and writes etcd. Other components use the API (with local caches fed by watches) \u2013 which keeps security and validation in one place." }, { myth: "Admission controllers run before authorization.", reality: "Order is **authentication \u2192 authorization \u2192 mutating admission \u2192 schema validation \u2192 validating admission \u2192 etcd**. Admission only sees requests that are already authenticated and authorized." }, { myth: "Secrets in etcd are encrypted by default.", reality: "By default Secrets are stored **base64-encoded, not encrypted**. Encryption at rest must be configured on the API server (an EncryptionConfiguration, optionally with a KMS provider)." } ], example: { title: "Watching the API, and backing up etcd", description: `\`--watch\` uses the same streaming mechanism controllers use. The etcd snapshot command is what an administrator runs on a control-plane node (certificate paths vary per installation).`, language: "bash", code: `# Stream changes to Pods in the current namespace (a client-side watch) kubectl get pods --watch # See raw API paths and the request chain at high verbosity kubectl get deployments -v=6 # ... GET https://127.0.0.1:6443/apis/apps/v1/namespaces/default/deployments 200 OK # Ask the API server what you are allowed to do (authorization check) kubectl auth can-i create deployments --namespace default # On a control-plane node: snapshot etcd ETCDCTL_API=3 etcdctl snapshot save /backup/etcd.db \\ --endpoints=https://127.0.0.1:2379 \\ --cacert=/etc/kubernetes/pki/etcd/ca.crt \\ --cert=/etc/kubernetes/pki/etcd/server.crt \\ --key=/etc/kubernetes/pki/etcd/server.key` }, exercise: { prompt: `For each outcome, name the stage of the request chain responsible: (a) \`error: You must be logged in to the server (Unauthorized)\`; (b) a Pod created without a ServiceAccount ends up with \`serviceAccountName: default\`; (c) a privileged Pod is rejected in a namespace labelled \`pod-security.kubernetes.io/enforce: restricted\`; (d) \`User "dev" cannot delete resource "pods"\`.`, solution: `(a) **Authentication** \u2013 the credentials were missing or invalid (401). (b) **Mutating admission** \u2013 the ServiceAccount admission plugin defaults the field. (c) **Validating admission** \u2013 Pod Security Admission enforces the Pod Security Standards. (d) **Authorization** \u2013 RBAC found no rule granting \`delete\` on \`pods\` (403).` }, faq: [ { q: "What happens to the cluster if etcd loses all its data?", a: "The cluster forgets every object: Deployments, Services, Secrets, RBAC. Containers already running keep going for a while, but nothing can be managed or recovered without restoring an etcd **snapshot**." }, { q: "Why do some API paths start with /api and others with /apis?", a: "`/api/v1` is the **core** (legacy) group: Pods, Services, ConfigMaps, Secrets, Namespaces. Named groups live under `/apis//`, e.g. `/apis/apps/v1` for Deployments." }, { q: "Can I extend the API?", a: "Yes. **CustomResourceDefinitions** add new resource types that the API server stores in etcd like built-ins; aggregated API servers are the less common alternative. Operators build on CRDs." } ], keyTerms: ["kube-apiserver", "etcd", "authentication", "authorization", "admission controller", "mutating webhook", "watch", "API group"], deeper: `To avoid hammering the API server, clients use **informers**: one list + watch per resource type, a local in-memory cache, and event handlers that enqueue work. The API server also has its own watch cache in front of etcd. **API Priority and Fairness** queues requests by priority so a noisy client cannot starve system components.`, references: [ { title: "Controlling Access to the Kubernetes API", url: "https://kubernetes.io/docs/concepts/security/controlling-access/" }, { title: "Admission Control in Kubernetes", url: "https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/" }, { title: "etcd documentation", url: "https://etcd.io/docs/" } ], diagram: "request-flow" }, { id: "control-plane-controllers", title: "Scheduler and controller managers", stage: 1, order: 3, objectives: ["kf-core", "kf-scheduling"], scope: "kcna", estMinutes: 20, summary: "kube-scheduler decides where each Pod runs by filtering and scoring nodes. kube-controller-manager runs the built-in control loops. cloud-controller-manager holds the loops that need a cloud provider API.", what: `Three control-plane programs make decisions; none of them runs containers. - **kube-scheduler** watches for Pods with no \`spec.nodeName\` and picks the best node for each. - **kube-controller-manager** is one process bundling many independent **controllers**, each a loop that watches some resources and nudges the cluster towards their spec: ReplicaSet, Deployment, StatefulSet, DaemonSet, Job, CronJob, Node, Namespace, EndpointSlice, ServiceAccount, garbage collector, and more. - **cloud-controller-manager** (CCM) contains the controllers that must call a cloud provider: the **node** controller (match nodes to cloud instances, remove deleted ones), **route** controller (configure cloud routes for Pod networks) and **service** controller (create cloud load balancers for \`type: LoadBalancer\`).`, why: `KCNA asks you to distinguish these clearly: "which component chooses the node?", "which component creates replacement Pods?", "what provisions a cloud load balancer?". Scheduling concepts (resources, taints, affinity) are their own objective, and they all plug into the filter-and-score process described here.`, problem: `Separating decisions into small loops makes each one simple and replaceable. One giant "cluster manager" would be hard to extend and a single bug would stall everything. Splitting cloud logic into the CCM let Kubernetes remove provider-specific code from the core and lets each provider release its integration independently.`, how: `Follow a single Pod from creation to placement: - **A controller creates the Pod.** The ReplicaSet controller sees 2 Pods but wants 3, so it creates a Pod object from its template. The Pod has no node yet; it is \`Pending\`. - **The scheduler queues it.** The scheduler's watch delivers the unscheduled Pod; it enters a priority-ordered **scheduling queue**. - **Filtering.** The scheduler removes nodes that cannot run the Pod: not enough unreserved CPU/memory for the Pod's **requests**, a **taint** the Pod does not tolerate, a mismatched **nodeSelector** or required **node affinity**, port conflicts, volume topology constraints. - **Scoring.** Remaining nodes are ranked by plugins \u2013 e.g. spreading Pods across nodes/zones, preferring nodes that already have the image, balancing resource use, preferred affinities. - **Binding.** The highest-scoring node wins (ties broken randomly). The scheduler writes a **Binding**, which sets \`spec.nodeName\`. - **Unschedulable?** If no node passes filtering, the Pod stays \`Pending\` with a \`FailedScheduling\` event explaining why (e.g. \`0/3 nodes are available: 3 Insufficient cpu\`). Higher-priority Pods may trigger **preemption** of lower-priority ones. - **Hand-off.** The kubelet on the chosen node takes over.`, components: [ { name: "kube-scheduler", description: "Filter \u2192 score \u2192 bind. Considers requests (not actual usage), taints/tolerations, affinity, topology spread." }, { name: "kube-controller-manager", description: "Runs built-in controllers in one binary; one active instance via leader election." }, { name: "cloud-controller-manager", description: "Node, route and service controllers that talk to the cloud provider API. Absent on bare metal unless a provider integration is installed." }, { name: "Leader election", description: "Multiple replicas of scheduler/controller managers run for HA, but only the lease holder acts." } ], misconceptions: [ { myth: "The scheduler places Pods based on current CPU usage.", reality: `Filtering uses resource **requests** compared to node allocatable minus other Pods' requests. A node can look idle in metrics and still be "full" for scheduling.` }, { myth: "The scheduler moves Pods to better nodes later.", reality: 'Scheduling happens **once** per Pod. A bound Pod is never moved; to "move" it, it must be deleted and a new Pod scheduled. (The separate Descheduler project can evict Pods to trigger this.)' }, { myth: "kube-controller-manager is a single controller.", reality: "It is a **process** containing dozens of independent controllers, each with its own loop." } ], example: { title: "Reading a scheduling failure", description: `The scheduler explains filter failures in events. Here, no node had enough CPU for the Pod's requests, and one node was excluded by a taint.`, language: "bash", code: `kubectl describe pod api-7c9d5b7f6-xq2lp # ... # Status: Pending # Events: # Type Reason From Message # Warning FailedScheduling default-scheduler 0/3 nodes are available: # 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, # 2 Insufficient cpu. preemption: 0/3 nodes are available: # 1 Preemption is not helpful for scheduling, # 2 No preemption victims found for incoming pod.` }, exercise: { prompt: `A Pod requests \`cpu: 2\`. Node A has 4 allocatable CPUs with Pods requesting 3; node B has 8 allocatable CPUs with Pods requesting 5 but carries the taint \`gpu=true:NoSchedule\`; node C has 4 allocatable CPUs with Pods requesting 1. The Pod has no tolerations. Which nodes pass filtering, and where does it go?`, solution: `- Node A: 4 \u2212 3 = 1 CPU free < 2 \u2192 **filtered out**. - Node B: 3 CPUs free, but the Pod does not tolerate \`gpu=true:NoSchedule\` \u2192 **filtered out**. - Node C: 4 \u2212 1 = 3 CPUs free \u2265 2 \u2192 **passes**. Only node C is feasible, so the Pod is bound to node C (scoring is trivial with one candidate).` }, faq: [ { q: "Can I run more than one scheduler?", a: "Yes. A Pod can choose one with `spec.schedulerName`; Pods without it use `default-scheduler`." }, { q: "Who sets a Node to NotReady?", a: "The **node lifecycle controller** in kube-controller-manager, when the kubelet stops renewing its heartbeat. After a timeout, taints cause Pods on that node to be evicted so their controllers replace them elsewhere." }, { q: "What if I bypass the scheduler by setting nodeName myself?", a: "Setting `spec.nodeName` in the manifest skips scheduling entirely; the named node's kubelet just tries to run it. Filtering (resources, taints) is not checked, so the Pod may be rejected by that kubelet." } ], keyTerms: ["kube-scheduler", "filtering", "scoring", "binding", "kube-controller-manager", "cloud-controller-manager", "leader election", "FailedScheduling"], deeper: `The scheduler is built on the **scheduling framework**: extension points (PreFilter, Filter, Score, Reserve, Permit, Bind...) where plugins hook in, configured through scheduler profiles. Custom schedulers for batch/AI workloads (e.g. gang scheduling) plug in this way or run alongside the default scheduler.`, references: [ { title: "Kubernetes Scheduler", url: "https://kubernetes.io/docs/concepts/scheduling-eviction/kube-scheduler/" }, { title: "Controllers", url: "https://kubernetes.io/docs/concepts/architecture/controller/" }, { title: "Cloud Controller Manager", url: "https://kubernetes.io/docs/concepts/architecture/cloud-controller/" } ] }, { id: "node-components", title: "Node components: kubelet, runtime, kube-proxy", stage: 1, order: 4, objectives: ["kf-core", "kf-containerization"], scope: "kcna", estMinutes: 20, summary: "On each node the kubelet turns Pod specs into running containers by calling a container runtime through the CRI (containerd or CRI-O, which call runc). kube-proxy programs Service routing.", what: `Look at the cluster from a single node's point of view: - **kubelet** \u2013 the node's agent. It registers the node with the API server, watches for Pods bound to it, makes their containers run, mounts volumes, runs probes, and reports Pod and node status. - **Container Runtime Interface (CRI)** \u2013 a gRPC API between the kubelet and the runtime. Any runtime implementing it works. - **High-level runtime** \u2013 **containerd** or **CRI-O**: pulls images, manages storage, creates the Pod sandbox and containers. - **Low-level OCI runtime** \u2013 usually **runc** (alternatives: crun, gVisor's runsc, Kata). Creates the actual namespaced, cgrouped process. - **kube-proxy** \u2013 watches Services and EndpointSlices and programs rules (iptables, IPVS, or nftables in newer versions) so a Service IP reaches a backend Pod. Some CNI plugins replace it entirely. - **CNI plugin** \u2013 called by the runtime to give each Pod sandbox a network interface and IP.`, why: `The 1.24 **dockershim removal** made "Kubernetes runtime" a common exam topic: the kubelet only speaks CRI, and Docker Engine does not implement CRI natively. Knowing the chain kubelet \u2192 CRI \u2192 containerd/CRI-O \u2192 runc also tells you where to look when a Pod is stuck in \`ContainerCreating\` or \`ImagePullBackOff\`.`, problem: `Early Kubernetes hard-coded Docker support into the kubelet (dockershim). Every new runtime needed kubelet changes, and the extra Docker daemon layer added complexity. The CRI defined a stable plug-in boundary, so the kubelet stays the same while runtimes evolve independently \u2013 the same "define an interface" pattern as CNI for networking and CSI for storage.`, how: `What the kubelet does when a Pod is bound to its node: - **Observe.** Its watch on the API server delivers a Pod with \`spec.nodeName\` equal to this node. - **Admit.** It checks node-level conditions (enough resources, no node pressure that blocks admission). - **Create the sandbox.** CRI \`RunPodSandbox\`: the runtime creates the Pod's shared namespaces (notably the network namespace, held by a tiny "pause" container in containerd/CRI-O). The **CNI plugin** attaches a network interface and assigns the Pod IP. - **Prepare volumes.** ConfigMaps, Secrets, emptyDirs and persistent volumes are mounted. - **Pull images.** CRI \`PullImage\` according to \`imagePullPolicy\`. - **Start containers.** Init containers run first, one by one, to completion; then the app containers start via CRI \`CreateContainer\`/\`StartContainer\`. The runtime hands an OCI bundle to runc, which creates the process. - **Supervise.** The kubelet runs liveness/readiness/startup probes, restarts failed containers per \`restartPolicy\` (with exponential back-off), and reports status (\`Running\`, \`Ready\`, restart counts) to the API server. - **Heartbeat.** It updates the node's Lease and status so the control plane knows the node is alive.`, components: [ { name: "kubelet", description: "Node agent; the only component that actually causes containers to start on a node. Also runs **static Pods** from local manifest files." }, { name: "CRI", description: "gRPC interface (RuntimeService + ImageService) between kubelet and runtime." }, { name: "containerd", description: "CNCF graduated high-level runtime with a built-in CRI plugin; default in many distributions and managed services." }, { name: "CRI-O", description: "Lightweight runtime built specifically for the Kubernetes CRI; a CNCF project used e.g. by OpenShift." }, { name: "runc", description: "Reference OCI runtime that creates container processes from bundles." }, { name: "kube-proxy", description: "Per-node Service implementation; typically a DaemonSet. Does not proxy traffic in user space in modern modes \u2013 it programs the kernel." } ], misconceptions: [ { myth: "After dockershim removal, images built with Docker no longer work.", reality: "Docker builds standard OCI images; containerd and CRI-O run them unchanged. Only using **Docker Engine as the node runtime** was affected (it now needs the external cri-dockerd adapter)." }, { myth: "kube-proxy carries all Service traffic through itself.", reality: "In iptables/IPVS/nftables modes kube-proxy only **writes kernel rules**; packets are redirected by the kernel. kube-proxy can be down briefly without breaking existing rules." }, { myth: "The kubelet manages any container on the node.", reality: "The kubelet manages containers for **Pods** (from the API server or static manifests). Containers started directly with a runtime CLI are invisible to Kubernetes." } ], example: { title: "Inspecting the node layer", description: `\`crictl\` is the CRI debugging CLI; it talks to the same socket the kubelet uses, so it shows exactly what the kubelet sees. Run it on a node (e.g. \`docker exec -it kind-worker bash\` in kind).`, language: "bash", code: `# Which runtime does each node use? kubectl get nodes -o wide # NAME ... CONTAINER-RUNTIME # kind-worker ... containerd://1.7.18 # On the node: list Pod sandboxes and containers through the CRI crictl pods crictl ps crictl images # kubelet logs on a systemd-based node journalctl -u kubelet --since "10 min ago"` }, exercise: { prompt: `A Pod is stuck in \`ContainerCreating\` for several minutes. List which node-level component could be responsible at each step of the kubelet's start-up sequence, and one concrete thing that could go wrong for each.`, solution: `- **Sandbox / CNI:** the CNI plugin is missing or failing \u2192 events like \`failed to setup network for sandbox\`. - **Volumes (kubelet):** a referenced ConfigMap/Secret does not exist, or a persistent volume cannot attach/mount \u2192 \`FailedMount\` events. - **Image pull (runtime):** pull is slow or failing \u2192 usually shown as \`ErrImagePull\`/\`ImagePullBackOff\` rather than \`ContainerCreating\`. - **Runtime itself:** containerd/CRI-O is unhealthy \u2192 kubelet logs show CRI errors and the node may go \`NotReady\`. \`kubectl describe pod\` (events) is the first place to look; kubelet logs on the node are the next.` }, faq: [ { q: "Is Docker still useful with Kubernetes?", a: "Yes \u2013 for **building** images and local development. It is just not the recommended node runtime. Tools like kind even run whole Kubernetes nodes inside Docker containers." }, { q: "What is the pause container?", a: "A tiny container that holds the Pod's shared namespaces (especially the network namespace) so app containers can restart without losing the Pod IP. It is an implementation detail of runtimes like containerd and CRI-O." }, { q: "Do I always need kube-proxy?", a: "Most clusters run it. Some CNI plugins (Cilium in kube-proxy replacement mode, for example) implement Services with eBPF instead, so kube-proxy is not deployed." } ], keyTerms: ["kubelet", "CRI", "containerd", "CRI-O", "runc", "kube-proxy", "dockershim", "static Pod"], deeper: `Static Pods are how kubeadm runs the control plane: the kubelet reads manifests from a directory (commonly \`/etc/kubernetes/manifests\`) and creates the Pods without any API server involvement, then publishes read-only "mirror Pods" to the API. The kubelet also enforces node-pressure **eviction** when memory or disk runs low, evicting Pods by QoS class and usage.`, references: [ { title: "Kubernetes Components", url: "https://kubernetes.io/docs/concepts/overview/components/" }, { title: "Container Runtime Interface (CRI)", url: "https://kubernetes.io/docs/concepts/architecture/cri/" }, { title: "Container Runtimes", url: "https://kubernetes.io/docs/setup/production-environment/container-runtimes/" } ] }, { id: "declarative-reconciliation", title: "Desired state and reconciliation", stage: 1, order: 5, objectives: ["kf-core"], scope: "kcna", estMinutes: 20, summary: "You declare what should exist; controllers repeatedly observe actual state, compare it with desired state, and act to close the gap. kubectl apply is the declarative way to submit desired state.", what: `**Desired state** is what you write in an object's \`spec\` ("3 replicas of image v2"). **Actual state** is what is really running, reported in \`status\`. A **controller** is a loop that never stops trying to make actual match desired. This pattern is called **reconciliation**. Two ways to talk to Kubernetes: - **Imperative** \u2013 tell it *what to do*: \`kubectl create deployment\`, \`kubectl scale --replicas=5\`, \`kubectl delete pod\`. Fast for experiments; nothing records why. - **Declarative** \u2013 tell it *what you want*: keep manifests in files (ideally Git) and run \`kubectl apply -f\`. Kubernetes works out the changes.`, why: `Reconciliation is the single most important idea in Kubernetes: it explains self-healing, rolling updates, scaling, GitOps and operators. Exam questions often describe a symptom ("I deleted a Pod and it came back") whose answer is "a controller reconciled it".`, problem: `Scripts that execute steps ("start 3 containers") work only if the world is exactly as the script expected and nothing changes afterwards. Servers crash, someone deletes something by hand, a step fails half-way. A declarative system does not care how you got here \u2013 it compares where you are with where you should be and corrects the difference, every time.`, how: `A thermostat is the classic analogy: you set 21 \xB0C (desired), it measures 19 \xB0C (actual), it turns on the heat (act), and it keeps checking. A Kubernetes controller does the same: - **Observe.** Watch the relevant objects through the API server (e.g. a ReplicaSet and the Pods it owns). - **Compare.** Desired: \`spec.replicas: 3\`. Actual: 2 Pods match the selector. - **Act.** Make the smallest change that moves towards desired: create 1 Pod. Never "start from scratch". - **Report.** Update \`status\` (e.g. \`readyReplicas\`) so others can observe progress. - **Repeat.** Triggered by watch events **and** periodic resyncs. Because the logic is based on current state (level-triggered), a missed event or a controller restart does no harm. How \`kubectl apply\` fits in: - On the first apply, the object is created. kubectl records what you applied (client-side: in the \`kubectl.kubernetes.io/last-applied-configuration\` annotation; server-side apply: as **managed fields** per field manager). - On later applies, kubectl computes a **three-way merge** of your file, the last-applied state and the live object, and sends a patch. Fields you removed from the file are removed; fields set by others (e.g. an autoscaler) are left alone. - \`kubectl diff -f\` shows what an apply would change before you run it.`, components: [ { name: "spec", description: "Desired state, written by users or higher-level controllers." }, { name: "status", description: "Observed state, written by controllers and kubelets." }, { name: "Controller / control loop", description: "Observe \u2192 compare \u2192 act \u2192 repeat. Each controller owns one concern." }, { name: "kubectl apply", description: "Declarative create-or-update from files using a merge of file, last-applied and live state." }, { name: "Operator", description: "A custom controller (usually with CRDs) that encodes operational knowledge for an application, e.g. a database." } ], misconceptions: [ { myth: "Declarative means kubectl never changes anything until I re-apply.", reality: "Controllers act continuously. If a Pod dies, a replacement appears without any new apply \u2013 the desired state is still stored." }, { myth: "`kubectl apply` and `kubectl create` are interchangeable.", reality: "`create` fails if the object exists (`AlreadyExists`). `apply` creates **or** updates and tracks what you manage. Mixing them on the same object is a common source of surprises." }, { myth: "Fixing a running Pod by hand is a permanent fix.", reality: "If the Pod is owned by a controller, the next replacement Pod comes from the **template**. Manual changes to live objects (`kubectl edit`) also drift from what is in Git and may be overwritten by the next apply or a GitOps sync." } ], example: { title: "Watch a control loop heal", description: "Delete a Pod owned by a Deployment and the ReplicaSet controller restores the count. Then change the desired state declaratively and preview it first.", language: "bash", code: `kubectl apply -f web-deployment.yaml # replicas: 3 kubectl get pods -l app=web # web-5d8f7c6b9-2kq4x Running # web-5d8f7c6b9-8hz7m Running # web-5d8f7c6b9-tw9pd Running kubectl delete pod web-5d8f7c6b9-2kq4x kubectl get pods -l app=web # web-5d8f7c6b9-8hz7m Running # web-5d8f7c6b9-tw9pd Running # web-5d8f7c6b9-x7n2c ContainerCreating <- replacement # change replicas: 3 -> 5 in the file, then: kubectl diff -f web-deployment.yaml # preview the change kubectl apply -f web-deployment.yaml` }, exercise: { prompt: `A teammate runs \`kubectl scale deployment web --replicas=10\` during an incident. The manifest in Git says \`replicas: 3\`, and a GitOps tool applies Git every few minutes. What happens next, and what should they have done instead?`, solution: `The imperative scale changes the live desired state to 10, and the controller creates 7 Pods. On the next sync the GitOps tool re-applies Git, setting \`replicas\` back to **3**, and the extra Pods are removed \u2013 possibly mid-incident. Better: change \`replicas\` in Git (so the declared state is the real intent), or let a HorizontalPodAutoscaler own the replica count and remove \`replicas\` from the manifest so the two do not fight.` }, faq: [ { q: "If everything is declarative, why does kubectl have imperative commands?", a: "They are convenient for learning, debugging and generating starter YAML (`kubectl create deployment web --image=nginx --dry-run=client -o yaml`). For anything long-lived, store manifests and apply them." }, { q: "What is server-side apply?", a: "`kubectl apply --server-side` performs the merge on the API server and records which **field manager** owns each field, reporting conflicts when two managers set the same field. It replaces the last-applied annotation mechanism." }, { q: "How fast does reconciliation happen?", a: "Usually within seconds, because controllers react to watch events. But it is asynchronous: `apply` returning success means the desired state was stored, not that it is achieved. Use `kubectl rollout status` or `kubectl get -w` to watch convergence." } ], keyTerms: ["desired state", "actual state", "reconciliation", "control loop", "declarative", "imperative", "kubectl apply", "operator"], deeper: `Controllers are written as a work queue fed by informers: events only enqueue an object key, and a worker re-reads the latest state before acting. This is why controllers are idempotent and tolerate duplicate or out-of-order events. The same pattern scales out to GitOps: Argo CD and Flux are reconcilers whose "desired state" lives in Git instead of etcd.`, references: [ { title: "Controllers", url: "https://kubernetes.io/docs/concepts/architecture/controller/" }, { title: "Declarative Management of Kubernetes Objects Using Configuration Files", url: "https://kubernetes.io/docs/tasks/manage-kubernetes-objects/declarative-config/" }, { title: "Kubernetes Object Management", url: "https://kubernetes.io/docs/concepts/overview/working-with-objects/object-management/" } ], diagram: "reconciliation-loop" }, // ───────────────────────────── Stage 2 · Workloads ───────────────────────────── { id: "pods", title: "Pods", stage: 2, order: 1, objectives: ["kf-core"], scope: "kcna", estMinutes: 25, summary: "A Pod is the smallest deployable unit: one or more containers that share a network namespace and volumes and are always scheduled together. Pods are disposable; controllers recreate them.", what: `A **Pod** wraps one or more containers that must live together: - They are **scheduled onto the same node** as a unit. - They share one **network namespace**: one Pod IP, one port space; containers talk over \`localhost\`. - They can share **volumes** declared at Pod level. - They share a lifecycle: the Pod is created, runs, and is deleted as one object. Container roles inside a Pod: - **App containers** \u2013 the main workload; started together, run in parallel. - **Init containers** \u2013 run **one at a time, to completion**, before app containers start (wait for a dependency, run a migration, fetch config). - **Sidecar containers** \u2013 helpers that run alongside the app for the Pod's whole life (log shipping, proxies). Native sidecars are declared as init containers with \`restartPolicy: Always\` (stable since Kubernetes 1.33): they start before the app containers, keep running, and are stopped after them.`, why: `Everything that runs in Kubernetes runs in a Pod \u2013 Deployments, Jobs, StatefulSets all create Pods. Pod phases, restart policies and container states are exactly what you see in \`kubectl get pods\` when something is wrong, and they come up repeatedly on the exam.`, problem: `Some processes are tightly coupled: a web server and the agent that ships its logs, or an app and its local proxy. Putting them in one container breaks "one process per container" and couples their images and releases. Putting them in separate, independently scheduled units loses \`localhost\` and shared files. The Pod is the middle ground: separate images, shared context.`, how: `Follow the Pod lifecycle as a state machine. **Pod phase** (\`status.phase\`) \u2013 a coarse summary: - \`Pending\` \u2013 accepted, but not all containers are running yet: waiting to be scheduled, pulling images, or running init containers. - \`Running\` \u2013 bound to a node, all containers created, at least one running (or starting/restarting). - \`Succeeded\` \u2013 all containers exited 0 and will not restart. - \`Failed\` \u2013 all containers terminated and at least one failed (non-zero exit or killed) and will not restart. - \`Unknown\` \u2013 the node cannot be reached, so status cannot be obtained. **Container state** (per container) \u2013 \`Waiting\` (with a reason such as \`ContainerCreating\`, \`ImagePullBackOff\`, \`CrashLoopBackOff\`), \`Running\`, or \`Terminated\` (with exit code and reason such as \`Completed\`, \`Error\`, \`OOMKilled\`). **restartPolicy** (Pod-level, applies to app containers): \`Always\` (default; required for Deployments), \`OnFailure\` (restart only on non-zero exit; common for Jobs), \`Never\`. Restarts happen **in place on the same node** with exponential back-off (10s, 20s, 40s ... capped at 5 minutes) \u2013 that back-off is what \`CrashLoopBackOff\` means. **Termination:** on delete, the Pod is marked terminating, removed from Service endpoints, any \`preStop\` hook runs, containers get \`SIGTERM\`, and after \`terminationGracePeriodSeconds\` (default 30) the remaining ones get \`SIGKILL\`.`, components: [ { name: "Pod IP / network namespace", description: "One IP per Pod, shared by all its containers. Two containers in a Pod cannot both listen on the same port." }, { name: "Init containers", description: "Sequential, run-to-completion setup steps. If one fails, the kubelet retries it (per restartPolicy) and app containers do not start." }, { name: "Sidecar containers", description: "Init containers with `restartPolicy: Always`; run for the Pod's lifetime and do not block Job completion." }, { name: "restartPolicy", description: "`Always` | `OnFailure` | `Never`, set once for the whole Pod." }, { name: "Phase vs conditions", description: "Phase is a summary; conditions (`PodScheduled`, `Initialized`, `ContainersReady`, `Ready`) give detail. `Ready` controls Service traffic." } ], misconceptions: [ { myth: "`CrashLoopBackOff` is a Pod phase.", reality: "It is a **container waiting reason**: the container keeps crashing and the kubelet is backing off before the next restart. The Pod phase is usually still `Running`." }, { myth: "A Pod that crashes gets rescheduled to another node.", reality: "Containers are restarted **in place** by the kubelet. A Pod is never moved; only a controller can create a **new** Pod (with a new name and IP) elsewhere \u2013 for example after the Pod is deleted or its node fails." }, { myth: "Each container in a Pod gets its own IP.", reality: "The **Pod** gets one IP. Containers inside share it and distinguish themselves by port." } ], example: { title: "Init container, native sidecar and app in one Pod", description: `The init container waits for the \`db\` Service to resolve. The sidecar (an init container with \`restartPolicy: Always\`) tails the app's log file from a shared \`emptyDir\` volume and keeps running alongside the app.`, language: "yaml", code: `apiVersion: v1 kind: Pod metadata: name: shop labels: app: shop spec: restartPolicy: Always volumes: - name: logs emptyDir: {} initContainers: - name: wait-for-db image: busybox:1.36 command: ["sh", "-c", "until nslookup db; do echo waiting; sleep 2; done"] - name: log-shipper # native sidecar image: busybox:1.36 restartPolicy: Always command: ["sh", "-c", "tail -F /var/log/app/app.log"] volumeMounts: - name: logs mountPath: /var/log/app containers: - name: app image: registry.example.com/shop/app:1.4.2 ports: - containerPort: 8080 volumeMounts: - name: logs mountPath: /var/log/app` }, exercise: { prompt: `Predict \`kubectl get pod\` output for each case: (a) the \`db\` Service in the example does not exist yet; (b) the app container exits with code 1 every few seconds; (c) the app container finishes with exit code 0 (restartPolicy \`Always\`).`, solution: `(a) The first init container never completes. STATUS shows \`Init:0/2\`, phase \`Pending\`; the app container never starts. (b) The kubelet restarts it with growing back-off. STATUS alternates between \`Error\` and \`CrashLoopBackOff\`, the RESTARTS count climbs, phase stays \`Running\`. (c) With \`Always\`, even a clean exit triggers a restart, so you again see RESTARTS increasing (and eventually \`CrashLoopBackOff\`). A run-to-completion task belongs in a **Job** with \`OnFailure\` or \`Never\`.` }, faq: [ { q: "When should I put two containers in one Pod?", a: "Only when they must share lifecycle, network or files \u2013 a sidecar proxy, log shipper or config reloader. If they could scale independently (a web app and its database), use separate Pods." }, { q: "Should I create Pods directly?", a: "Rarely. A bare Pod is not recreated if it is deleted or its node fails. Use a controller \u2013 Deployment, StatefulSet, DaemonSet or Job \u2013 that manages Pods from a template. Note that `kubectl run` creates only a single bare Pod." }, { q: "Can I change a running Pod's spec?", a: "Mostly no \u2013 most Pod spec fields are immutable (image is a notable exception). In practice you change the controller's template and let it replace Pods." }, { q: "What is the difference between Running and Ready?", a: "`Running` means the containers are started. `Ready` means readiness probes pass and the Pod should receive Service traffic. A Pod can be Running but not Ready." } ], keyTerms: ["Pod", "init container", "sidecar container", "Pod phase", "container state", "restartPolicy", "CrashLoopBackOff", "terminationGracePeriodSeconds"], deeper: `**Ephemeral containers** can be added to a running Pod for debugging (\`kubectl debug\`); they cannot be restarted and have no ports or probes. **Static Pods** are created by a kubelet from local files rather than the API. The **Pod overhead** and **Pod-level resources** features let the scheduler account for sandbox costs or set budgets for the whole Pod \u2013 check feature states for your version.`, references: [ { title: "Pods", url: "https://kubernetes.io/docs/concepts/workloads/pods/" }, { title: "Pod Lifecycle", url: "https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/" }, { title: "Sidecar Containers", url: "https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/" } ] }, { id: "replicasets-and-deployments", title: "ReplicaSets and Deployments", stage: 2, order: 2, objectives: ["kf-core"], scope: "kcna", estMinutes: 25, summary: "A ReplicaSet keeps N identical Pods running. A Deployment manages ReplicaSets to add rolling updates and rollbacks. Ownership links the three levels, and garbage collection cleans up children.", what: `Two controllers stacked on top of each other: - **ReplicaSet** \u2013 ensures that a specified number of Pods matching its **selector** exist. It creates Pods from its **Pod template** when there are too few and deletes some when there are too many. It does not know about versions. - **Deployment** \u2013 the object you normally write for stateless apps. It owns ReplicaSets: one per version of the Pod template. Changing the template creates a new ReplicaSet and gradually shifts Pods to it (**rolling update**); old ReplicaSets are kept (scaled to 0) to allow **rollback**. The chain is **Deployment \u2192 ReplicaSet \u2192 Pods**, linked by \`metadata.ownerReferences\` on each child.`, why: `Deployments are the default way to run stateless workloads, and most exam workload questions touch them: scaling, self-healing, update strategy, rollback, and "who created this Pod?". Understanding the ReplicaSet layer explains the odd Pod names (\`web-5d8f7c6b9-x7n2c\`) and what happens during a rollout.`, problem: `Running one Pod is fragile. Running N Pods by hand means noticing failures and replacing them yourself. And updating N Pods by deleting them all and creating new ones causes downtime and gives no way back if the new version is broken. ReplicaSets solve "keep N", Deployments solve "change versions safely".`, how: `Run an experiment in your head (or on a cluster): - **Create.** You apply a Deployment \`web\` with \`replicas: 3\` and image \`nginx:1.26\`. The Deployment controller creates ReplicaSet \`web-\` (the hash comes from the Pod template and is also added as a \`pod-template-hash\` label). The ReplicaSet controller creates 3 Pods. - **Break something.** Delete a Pod. The **ReplicaSet** controller (not the Deployment controller) sees 2 < 3 and creates one more. - **Scale.** \`kubectl scale deployment web --replicas=5\` \u2013 the Deployment updates its current ReplicaSet's replica count; no new ReplicaSet is created because the template did not change. - **Update.** Change the image to \`nginx:1.27\`. The template hash changes, so a **new ReplicaSet** is created. With the default **RollingUpdate** strategy (\`maxSurge: 25%\`, \`maxUnavailable: 25%\`), the controller scales the new one up and the old one down step by step, waiting for new Pods to become Ready. - **Roll back.** \`kubectl rollout undo deployment/web\` points the Deployment back at the previous template; the old ReplicaSet is scaled up again. How many old ReplicaSets are kept is set by \`revisionHistoryLimit\` (default 10). - **Delete.** Deleting the Deployment triggers **cascading deletion**: the garbage collector follows ownerReferences and removes the ReplicaSets and Pods. The **Recreate** strategy is the alternative: scale old Pods to zero first, then create new ones (downtime, but never two versions at once).`, components: [ { name: "Pod template", description: "`spec.template` \u2013 the blueprint for every Pod. Any change to it triggers a rollout." }, { name: "Selector", description: "`spec.selector` must match the template's labels; immutable for `apps/v1` Deployments after creation." }, { name: "ReplicaSet", description: "Keeps the replica count for one template version. Rarely created directly." }, { name: "Rolling update parameters", description: "`maxSurge` (extra Pods allowed above desired) and `maxUnavailable` (Pods allowed below desired); both default to 25%." }, { name: "ownerReferences & garbage collection", description: "Children point to their owner; deleting the owner removes dependents (cascading deletion) unless orphaning is requested." } ], misconceptions: [ { myth: "Scaling a Deployment creates a new revision.", reality: "Only changes to `spec.template` create a new ReplicaSet/revision. Changing `replicas` adjusts the existing ReplicaSet." }, { myth: "You should edit the ReplicaSet to change the image.", reality: "The Deployment owns the ReplicaSet and will revert or ignore such changes. Always change the **Deployment**; for a bare ReplicaSet, an edited template only affects Pods created later." }, { myth: "Rollback restores the previous number of replicas too.", reality: "`rollout undo` restores the previous **Pod template**. The replica count is not part of the revision." } ], example: { title: "A Deployment with an explicit rolling update strategy", description: `With 4 replicas, \`maxSurge: 1\` and \`maxUnavailable: 0\`, the rollout never drops below 4 ready Pods and runs at most 5 Pods at a time \u2013 zero-downtime at the cost of one extra Pod of capacity.`, language: "yaml", code: `apiVersion: apps/v1 kind: Deployment metadata: name: web labels: app: web spec: replicas: 4 revisionHistoryLimit: 5 selector: matchLabels: app: web strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: labels: app: web # must match spec.selector spec: containers: - name: web image: nginx:1.27 ports: - containerPort: 80 # kubectl set image deployment/web web=nginx:1.28 # kubectl rollout status deployment/web # kubectl rollout history deployment/web # kubectl rollout undo deployment/web --to-revision=2` }, exercise: { prompt: `You have Deployment \`api\` with 10 replicas and default rolling update settings. You change the image. (a) What is the maximum number of Pods that may exist during the rollout? (b) The minimum number that must be available? (c) Afterwards, how many ReplicaSets does \`kubectl get rs\` show for \`api\`, and with what replica counts?`, solution: `(a) \`maxSurge\` 25% of 10 = 2.5, rounded **up** \u2192 3, so up to **13** Pods. (b) \`maxUnavailable\` 25% of 10 = 2.5, rounded **down** \u2192 2, so at least **8** available. (c) **Two** ReplicaSets: the new one with 10 replicas and the old one scaled to **0** (kept for rollback, within \`revisionHistoryLimit\`).` }, faq: [ { q: "Why are Pod names like web-5d8f7c6b9-x7n2c?", a: "`web` is the Deployment, `5d8f7c6b9` is the ReplicaSet's Pod template hash, and `x7n2c` is a random suffix for the individual Pod. It lets you see at a glance which version a Pod belongs to." }, { q: "How do I find out who owns a Pod?", a: "Look at `metadata.ownerReferences` (`kubectl get pod -o yaml`) or the `Controlled By:` line in `kubectl describe pod`." }, { q: "When would I use a ReplicaSet directly?", a: "Almost never. Deployments give you everything a ReplicaSet does plus updates and rollbacks. Use StatefulSets for stable identity and DaemonSets for one-Pod-per-node instead." } ], keyTerms: ["ReplicaSet", "Deployment", "Pod template", "RollingUpdate", "Recreate", "maxSurge", "maxUnavailable", "ownerReferences"], deeper: `\`kubectl rollout pause\` lets you batch several template changes into one rollout. \`progressDeadlineSeconds\` (default 600) marks a rollout as failed (\`ProgressDeadlineExceeded\`) if it stops making progress \u2013 it reports, but does not roll back automatically. Garbage collection supports **foreground**, **background** (default) and **orphan** propagation policies, e.g. \`kubectl delete deployment web --cascade=orphan\` keeps the ReplicaSets and Pods.`, references: [ { title: "Deployments", url: "https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" }, { title: "ReplicaSet", url: "https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/" }, { title: "Garbage Collection", url: "https://kubernetes.io/docs/concepts/architecture/garbage-collection/" } ], diagram: "deployment-hierarchy" }, { id: "namespaces-labels-selectors", title: "Namespaces, labels, selectors and annotations", stage: 2, order: 3, objectives: ["kf-core", "kf-admin"], scope: "kcna", estMinutes: 20, summary: "Namespaces partition names, permissions and quotas. Labels are queryable key/value tags; selectors are the queries that wire Services, Deployments and policies to Pods. Annotations hold non-identifying metadata.", what: `Think of the cluster as a database and these as its organizing tools. - **Namespace** \u2013 a scope for names. Two Deployments called \`web\` can coexist in \`team-a\` and \`team-b\`. Namespaces are also the unit for RBAC Roles, ResourceQuotas, LimitRanges, and (by label) Pod Security Admission. Default ones: \`default\`, \`kube-system\`, \`kube-public\`, \`kube-node-lease\`. - **Labels** \u2013 key/value pairs in \`metadata.labels\` that **identify** objects: \`app: web\`, \`tier: frontend\`, \`env: prod\`. Keys may have a DNS prefix (\`app.kubernetes.io/name\`); the name part is at most 63 characters. - **Selectors** \u2013 queries over labels. **Equality-based**: \`app=web\`, \`env!=prod\`. **Set-based**: \`env in (staging,prod)\`, \`tier notin (cache)\`, \`release\` (key exists), \`!release\` (key absent). - **Annotations** \u2013 key/value pairs in \`metadata.annotations\` for **non-identifying** data: build info, contact, tool configuration. Values can be large; they cannot be selected on.`, why: `Labels and selectors are the glue of Kubernetes. Objects are not linked by name but by **label queries**: a Service finds its Pods by selector, a Deployment's ReplicaSet counts Pods by selector, NetworkPolicies and affinity rules select Pods by label. When a Service has no endpoints, a label mismatch is the first suspect. Namespaces are the first tool for multi-team clusters.`, problem: `In a cluster with thousands of objects you need to answer "show me all production frontend Pods" and "let team A manage only its own things". Folder-like hierarchies cannot express overlapping views (a Pod is both "frontend" and "prod" and "v2"). Free-form labels with a query language can, and namespaces add hard naming and permission boundaries where you need them.`, how: `How a selector connects objects, step by step, using a Service: - **Label the Pods.** The Deployment template sets \`labels: {app: web, tier: frontend}\` on every Pod it creates. - **Write the query.** The Service declares \`selector: {app: web}\`. Services support only equality-based selectors (all listed pairs must match). - **Controller evaluates it.** The EndpointSlice controller lists Pods **in the Service's namespace** that carry \`app=web\` and are Ready, and writes their IPs into EndpointSlices. - **Labels change \u2192 membership changes.** Remove the \`app=web\` label from one Pod and it drops out of the Service (and out of the ReplicaSet's count, so the ReplicaSet creates a replacement \u2013 a handy trick for debugging a Pod in isolation). - **Namespace boundary.** Selectors in Services, ReplicaSets and Deployments only match objects in the same namespace. Cross-namespace access uses DNS names like \`web.team-a.svc.cluster.local\`. Note that not everything is namespaced: **Nodes, PersistentVolumes, StorageClasses, Namespaces, ClusterRoles** and CRDs are cluster-scoped. \`kubectl api-resources --namespaced=false\` lists them.`, components: [ { name: "Namespace", description: "Scope for names and for RBAC, quotas and policies. Not a network or security boundary by itself." }, { name: "Label", description: "Identifying key/value pair; indexed and selectable. Recommended keys: `app.kubernetes.io/name`, `/instance`, `/version`, `/component`, `/part-of`, `/managed-by`." }, { name: "Equality-based selector", description: "`=`, `==`, `!=`. Used by Services and `matchLabels`." }, { name: "Set-based selector", description: "`in`, `notin`, exists, does-not-exist. Used by `matchExpressions` (Deployments, Jobs, affinity) and kubectl `-l`." }, { name: "Annotation", description: "Non-identifying metadata for tools and humans; not selectable." } ], misconceptions: [ { myth: "Namespaces isolate network traffic between teams.", reality: "By default every Pod can reach every other Pod across namespaces. Isolation needs **NetworkPolicies** (and a CNI that enforces them); permission isolation needs **RBAC**." }, { myth: "Annotations and labels are interchangeable.", reality: "Only labels can be used in selectors. Annotations are for data you want to attach but never query, e.g. a git commit or a tool setting." }, { myth: "A Service can select Pods in any namespace by label.", reality: "A Service selector only matches Pods in **its own namespace**." } ], example: { title: "Querying with labels and working across namespaces", description: "Common label and namespace operations with kubectl.", language: "bash", code: `kubectl create namespace team-a kubectl config set-context --current --namespace=team-a # change default namespace kubectl get pods --show-labels kubectl get pods -l app=web # equality-based kubectl get pods -l 'env in (staging,prod),tier!=cache' # set-based + equality kubectl get pods -l '!canary' # label key absent kubectl get pods -A -l app=web # all namespaces kubectl label pod web-5d8f7c6b9-x7n2c env=prod # add a label kubectl label pod web-5d8f7c6b9-x7n2c env=staging --overwrite kubectl label pod web-5d8f7c6b9-x7n2c env- # remove a label kubectl annotate deployment web owner="team-a@example.com" kubectl api-resources --namespaced=false # cluster-scoped kinds` }, exercise: { prompt: `A Service in namespace \`shop\` has \`selector: {app: cart, tier: backend}\`. Pods: (1) \`app=cart, tier=backend\` in \`shop\`; (2) \`app=cart\` in \`shop\`; (3) \`app=cart, tier=backend, version=v2\` in \`shop\`; (4) \`app=cart, tier=backend\` in \`default\`. Which Pods become endpoints (assume all are Ready)?`, solution: `**Pods 1 and 3.** A selector requires **all** its pairs to match; extra labels on the Pod (\`version=v2\`) do not matter. Pod 2 lacks \`tier=backend\`. Pod 4 matches the labels but is in another namespace, and Service selectors only match within the Service's own namespace.` }, faq: [ { q: "Does deleting a namespace delete everything inside it?", a: "Yes \u2013 all namespaced objects in it are deleted. The namespace sits in `Terminating` until that finishes. Cluster-scoped objects (e.g. PersistentVolumes) are not inside namespaces and follow their own rules." }, { q: "Should I use namespaces for environments like dev and prod?", a: "It works for small setups, but many organizations put production in a **separate cluster** for stronger isolation. Namespaces are the usual unit for teams or applications within a cluster." }, { q: "Can I change a Deployment's selector later?", a: "No \u2013 in `apps/v1` the selector is immutable. Create a new Deployment instead. You *can* add labels to the template, as long as the selector still matches." } ], keyTerms: ["namespace", "label", "label selector", "matchLabels", "matchExpressions", "annotation", "cluster-scoped", "namespaced"], deeper: `Field selectors (\`--field-selector status.phase=Running\`) filter on a small set of built-in fields, not on arbitrary spec values. Hierarchical namespaces and virtual clusters (e.g. vcluster) are ecosystem projects for stronger multi-tenancy than plain namespaces provide.`, references: [ { title: "Labels and Selectors", url: "https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/" }, { title: "Namespaces", url: "https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/" }, { title: "Annotations", url: "https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/" } ] } ]; // ../../packages/content/src/lessons/b.ts var lessonsB = [ // ───────────────────────────── Stage 2 · Workloads ───────────────────────────── { id: "jobs-and-cronjobs", title: "Jobs and CronJobs", stage: 2, order: 4, objectives: ["kf-core"], scope: "kcna", estMinutes: 18, summary: "Jobs run Pods until a task finishes successfully; CronJobs create Jobs on a schedule. They are the workload controllers for work that is supposed to end.", what: `A **Job** is a controller that creates one or more Pods and keeps creating replacements until a specified number of them **complete successfully** (exit code 0). Then it stops. The Job object stays around as a record of the run. A **CronJob** is a controller one level higher: on a cron schedule (for example \`0 2 * * *\` = every day at 02:00) it creates a new Job from a \`jobTemplate\`. The chain is CronJob \u2192 Job \u2192 Pod, the same "controller owns controller" pattern you saw with Deployment \u2192 ReplicaSet \u2192 Pod.`, why: `A Deployment assumes its Pods should run forever: if a Pod exits, the ReplicaSet replaces it. That is exactly wrong for a database migration, a report generator or a batch image resize. Those tasks should run, finish, and **stay finished**. Jobs give batch work the same self-healing as long-running services: if the node dies halfway through, the Job controller notices the missing Pod and starts another, but once the work succeeds nothing gets restarted.`, problem: `Imagine running a nightly backup with a plain Pod. If the node reboots at 02:05, the Pod is gone and nothing retries it. If you wrap it in a Deployment instead, the backup "succeeds", exits, and is immediately started again in an endless loop. Before Kubernetes, the usual answer was a crontab on one specific server: a single point of failure that nobody remembered to migrate. CronJob moves the schedule into the cluster, where it survives the loss of any single machine.`, how: `Follow one run of a CronJob from schedule to cleanup: - The **CronJob controller** (part of kube-controller-manager) checks schedules. When a schedule time arrives, it creates a **Job** object from \`spec.jobTemplate\`. - If the previous Job is still running, \`concurrencyPolicy\` decides: \`Allow\` (default, run both), \`Forbid\` (skip this run) or \`Replace\` (cancel the old one, start the new one). - The **Job controller** sees the new Job and creates Pods from its Pod template. \`parallelism\` caps how many run at once; \`completions\` says how many must succeed (both default to 1). - Each Pod's \`restartPolicy\` must be \`Never\` or \`OnFailure\` \u2014 \`Always\` is rejected for Jobs. With \`OnFailure\` the kubelet restarts the failed container in place; with \`Never\` the Job controller creates a fresh Pod. - Failures are counted against \`backoffLimit\` (default 6), with exponential back-off between retries. When the limit is exceeded the Job is marked **Failed**. \`activeDeadlineSeconds\` puts a hard wall-clock limit on the whole Job. - When enough Pods succeed, the Job gets a **Complete** condition. Its Pods are left in \`Completed\` status so you can still read their logs. - Cleanup: the CronJob keeps the last \`successfulJobsHistoryLimit\` (default 3) and \`failedJobsHistoryLimit\` (default 1) Jobs. A plain Job can clean itself up with \`ttlSecondsAfterFinished\`.`, components: [ { name: "Job", description: "Runs Pods until `completions` successes are recorded, retrying failures up to `backoffLimit`." }, { name: "CronJob", description: "Creates a Job from `jobTemplate` each time `schedule` (standard 5-field cron syntax) fires." }, { name: "completions / parallelism", description: "How many successful Pods are needed in total, and how many may run at the same time." }, { name: "backoffLimit", description: "Number of retries before the Job is marked Failed (default 6)." }, { name: "concurrencyPolicy", description: "CronJob behaviour when the previous run is still active: `Allow`, `Forbid` or `Replace`." }, { name: "ttlSecondsAfterFinished", description: "Lets the TTL controller delete a finished Job (and its Pods) after N seconds." } ], misconceptions: [ { myth: "A Job can use restartPolicy: Always like any other Pod.", reality: `No. The API server rejects a Job whose Pod template uses \`Always\`. Only \`Never\` and \`OnFailure\` make sense for work that is meant to finish.` }, { myth: "Deleting a finished Job is necessary to see it stopped \u2014 Completed Pods are still running.", reality: `A \`Completed\` Pod has exited and uses no CPU or memory. It is kept only so you can read logs and status. Remove it with \`ttlSecondsAfterFinished\` or history limits when you no longer need it.` }, { myth: "A CronJob guarantees exactly one run per schedule tick.", reality: `CronJobs are approximately once. A run can be skipped (controller down, \`Forbid\` policy, missed \`startingDeadlineSeconds\`) or, rarely, happen twice. Batch tasks should be **idempotent** \u2014 safe to run again.` } ], example: { title: "A nightly report CronJob", description: `Runs at 02:30 every day, never overlaps with itself, retries twice, and keeps only a short history. Trigger it manually for testing with \`kubectl create job report-manual --from=cronjob/nightly-report\`.`, language: "yaml", code: `apiVersion: batch/v1 kind: CronJob metadata: name: nightly-report spec: schedule: "30 2 * * *" concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 jobTemplate: spec: backoffLimit: 2 activeDeadlineSeconds: 600 template: spec: restartPolicy: Never containers: - name: report image: busybox:1.36 command: ["sh", "-c", "echo generating report; sleep 5"]` }, exercise: { prompt: `You need to process 12 files. Each Pod handles one file, and you want at most 3 Pods running at the same time. Which two Job fields do you set, and to what values? What happens if one Pod crashes?`, solution: `Set \`completions: 12\` and \`parallelism: 3\`. The Job controller keeps up to 3 Pods running until 12 have succeeded. If a Pod crashes, the failure counts against \`backoffLimit\`. With \`restartPolicy: Never\` the controller creates a replacement Pod after a back-off delay; with \`OnFailure\` the container is restarted in the same Pod. Once failures exceed \`backoffLimit\`, the whole Job is marked Failed.` }, faq: [ { q: "Which API group are Jobs and CronJobs in?", a: `Both are in \`batch/v1\`. CronJob graduated to \`batch/v1\` (GA) in Kubernetes 1.21.` }, { q: "What time zone does a CronJob schedule use?", a: `By default, the time zone of the kube-controller-manager. You can set \`spec.timeZone\` (for example \`"Europe/Berlin"\`) to make it explicit.` }, { q: "How do I see why a Job failed?", a: `\`kubectl describe job \` shows conditions and events; then \`kubectl logs\` on one of its Pods (they carry the label \`job-name=\`) shows the application output.` } ], keyTerms: ["Job", "CronJob", "completions", "parallelism", "backoffLimit", "concurrencyPolicy", "ttlSecondsAfterFinished", "idempotency"], deeper: `**Indexed Jobs** (\`completionMode: Indexed\`) give each Pod a stable index (exposed as the \`JOB_COMPLETION_INDEX\` environment variable), so Pod 0 processes shard 0, and so on \u2014 the basis for static work partitioning. A **podFailurePolicy** lets a Job fail fast on non-retryable exit codes or ignore Pod disruptions (such as preemption) instead of burning \`backoffLimit\`. Jobs can also be suspended with \`spec.suspend: true\`, which queueing systems such as Kueue use to admit batch work only when capacity is available.`, references: [ { title: "Kubernetes docs: Jobs", url: "https://kubernetes.io/docs/concepts/workloads/controllers/job/" }, { title: "Kubernetes docs: CronJob", url: "https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/" } ] }, { id: "daemonsets-and-statefulsets", title: "DaemonSets and StatefulSets", stage: 2, order: 5, objectives: ["kf-core"], scope: "kcna", estMinutes: 20, summary: "DaemonSets run one Pod per eligible node for node-level agents; StatefulSets give Pods stable names, ordered rollout and their own persistent volumes for stateful apps.", what: `Both are workload controllers that exist because a Deployment's assumption \u2014 "all replicas are identical and interchangeable" \u2014 does not always hold. A **DaemonSet** ensures that **every eligible node runs exactly one copy** of a Pod. You do not set a replica count; the number of nodes is the replica count. Add a node and a Pod appears on it; remove the node and the Pod is garbage-collected. A **StatefulSet** manages Pods that each have a **persistent identity**: a stable ordinal name (\`db-0\`, \`db-1\`, \`db-2\`), a stable DNS name via a headless Service, and **its own PersistentVolumeClaim** that follows that name across rescheduling.`, why: `Think of a Deployment as a taxi rank: any car will do. A DaemonSet is a security guard posted at **every** building entrance. A StatefulSet is a numbered seat in a parliament: seat 2 is always seat 2, keeps its own desk drawer (volume), and members are sworn in in order. Log shippers (Fluent Bit), node metrics exporters, CNI agents and kube-proxy itself need to be on every node \u2014 that is the DaemonSet case. Databases, message brokers and consensus systems (PostgreSQL replicas, Kafka, etcd, ZooKeeper) need peers to find each other by name and keep their data \u2014 that is the StatefulSet case.`, problem: `Try running a three-node database with a Deployment. Pods get random names like \`db-7c9d4-xk2lp\`; when one is replaced, its name and IP change, so the other members no longer recognise it. Worse, all replicas share one PVC template \u2014 or none \u2014 so a replacement Pod starts with an empty disk. Try running a log agent with a Deployment of 5 replicas on 5 nodes. The scheduler may place two on one node and none on another, and when you scale the cluster to 8 nodes, three nodes are silently uncovered.`, how: `**DaemonSet, step by step:** - The DaemonSet controller lists nodes and checks which ones match the Pod template's \`nodeSelector\`/affinity and tolerate the node's taints. - For each eligible node without a Pod, it creates one with node affinity pinning it to that node; the default scheduler then binds it there. - It automatically adds tolerations for conditions such as \`not-ready\` and \`unreachable\`, so node agents keep running on unhealthy nodes. To run on control-plane nodes you must add a toleration for the control-plane taint yourself. - Updates use \`RollingUpdate\` (default) or \`OnDelete\`. **StatefulSet, step by step (default \`OrderedReady\` policy):** - \`spec.serviceName\` points at a **headless Service** (\`clusterIP: None\`) that gives each Pod a DNS name: \`db-0.db..svc.cluster.local\`. - The controller creates \`db-0\`, waits until it is Running and Ready, then creates \`db-1\`, and so on. - For each Pod it creates a PVC from \`volumeClaimTemplates\`, named \`