UNPKG

node-karin

Version:

Lightweight, efficient, concise, and stable robot framework.

1,552 lines 353 kB
#!/usr/bin/env node import fs, { createReadStream, createWriteStream } from "node:fs"; import { fileURLToPath, URL as URL$1, pathToFileURL } from "node:url"; import path, { join } from "node:path"; import require$$1, { execSync as execSync$1, exec as exec$2, spawn } from "node:child_process"; import { pipeline } from "node:stream/promises"; import fs$1 from "fs"; import require$$0 from "node:events"; import require$$4 from "node:process"; const execSync = (cmd, options = {}) => { try { const result = execSync$1(cmd, options); return { status: true, error: null, stdout: result.toString(), stderr: "" }; } catch (error2) { return { status: false, error: error2, stdout: "", stderr: "" }; } }; const exec$1 = (cmd, options = {}) => { return new Promise((resolve) => { exec$2(cmd, options, (error2, stdout, stderr) => { const status = !error2; resolve({ status, error: error2, stdout, stderr }); }); }); }; const pm2Dir = path.join(process.cwd(), "@karinjs/config/pm2.json"); const readPm2Config = () => { if (!fs.existsSync(pm2Dir)) { console.log(`[pm2] 配置文件不存在 请检查 ${pm2Dir} 是否存在`); return null; } return JSON.parse(fs.readFileSync(pm2Dir, "utf-8")); }; const ensureLogDir = (dirPath) => { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); console.log(`[pm2] 创建日志目录: ${dirPath}`); return true; } return false; }; const formatBytes = (bytes, decimals = 2) => { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`; }; const rotateLogFile = async (logPath, logDirPath, logType, maxSizeMB) => { if (!fs.existsSync(logPath)) return false; const stats = fs.statSync(logPath); const maxSizeBytes = maxSizeMB * 1024 * 1024; const minKeepSizeBytes = 2 * 1024 * 1024; console.log(`[pm2] 检查${logType}日志文件: ${logPath}`); console.log(`[pm2] 当前大小: ${formatBytes(stats.size)}, 切割阈值: ${formatBytes(maxSizeBytes)}`); if (stats.size < maxSizeBytes) { console.log(`[pm2] ${logType}日志文件大小未超过阈值,无需切割 `); return false; } const fileName = path.basename(logPath); const timestamp2 = Date.now(); const archivedName = `${fileName}.${timestamp2}`; const archivedPath = path.join(logDirPath, archivedName); console.log(` [pm2] 准备切割${logType}日志文件:`); console.log(`[pm2] - 源文件: ${logPath}`); console.log(`[pm2] - 归档文件名称: ${archivedName}`); console.log(`[pm2] - 归档文件路径: ${archivedPath}`); try { const sourceStream = createReadStream(logPath); const destStream = createWriteStream(archivedPath); await pipeline(sourceStream, destStream); const originalMode = fs.statSync(logPath).mode; const fileSize = stats.size; const keepSize = Math.max( minKeepSizeBytes, Math.floor(fileSize / 5) // 保留原大小的1/5或至少2MB ); console.log(`[pm2] 将保留${formatBytes(keepSize)}内容到新的日志文件中`); const keepBuffer = Buffer.alloc(keepSize); const fd = fs.openSync(logPath, "r"); fs.readSync(fd, keepBuffer, 0, keepSize, fileSize - keepSize); fs.closeSync(fd); fs.writeFileSync(logPath, keepBuffer); fs.chmodSync(logPath, originalMode); console.log(`[pm2] 日志已按大小切割: ${logType} => ${archivedName}`); console.log(`[pm2] 原始文件大小: ${formatBytes(stats.size)}, 归档后保留: ${formatBytes(keepSize)}`); console.log(`[pm2] 归档文件完整路径: ${archivedPath}`); return true; } catch (err) { console.error(`[pm2] 切割日志失败: ${logType}`, err); return false; } }; const cleanupLogFiles = (errorLogBaseName, outLogBaseName, logDirPath, maxLogDays) => { if (maxLogDays <= 0) { console.log(`[pm2] 日志保留天数设置为${maxLogDays},将保留所有日志文件`); return; } const files = fs.readdirSync(logDirPath); const cleanupByLogType = (baseName, logType) => { const filePattern = new RegExp(`^${baseName.replace(/\./g, "\\.")}\\.(\\d+)$`); const logFiles = files.filter((file) => filePattern.test(file)).map((file) => { const match = file.match(filePattern); return { file, timestamp: match ? parseInt(match[1], 10) : 0 }; }).sort((a, b) => b.timestamp - a.timestamp); const now = Date.now(); const maxAgeMs = maxLogDays * 24 * 60 * 60 * 1e3; let deletedCount = 0; logFiles.forEach((item) => { if (now - item.timestamp > maxAgeMs) { const filePath = path.join(logDirPath, item.file); fs.unlinkSync(filePath); const fileDate = new Date(item.timestamp).toISOString().split("T")[0]; deletedCount++; console.log(`[pm2] 删除过期${logType}日志: ${item.file} (${fileDate})`); } }); if (deletedCount > 0) { console.log(`[pm2] 共删除${deletedCount}个超过${maxLogDays}天的${logType}日志文件`); } }; cleanupByLogType(errorLogBaseName, "错误"); cleanupByLogType(outLogBaseName, "输出"); }; const rotateLogs = async () => { const config = readPm2Config(); if (!config) return; try { let maxLogDays = Number(config.maxLogDays) ?? 14; let maxErrorLogSize = Number(config.maxErrorLogSize); let maxOutLogSize = Number(config.maxOutLogSize); if (isNaN(maxLogDays) || maxLogDays < 0) maxLogDays = 14; if (isNaN(maxErrorLogSize) || maxErrorLogSize < 1) maxErrorLogSize = 50; if (isNaN(maxOutLogSize) || maxOutLogSize < 1) maxOutLogSize = 50; if (maxLogDays === 0) { console.log("[pm2] 日志保留策略: 保留所有日志,不进行清理"); } else { console.log(`[pm2] 日志保留策略: 最长${maxLogDays}天`); } const errorLogPath = path.resolve(process.cwd(), config.apps[0].error_file); const outLogPath = path.resolve(process.cwd(), config.apps[0].out_file); const logDirPath = path.dirname(errorLogPath); if (ensureLogDir(logDirPath)) return; const errorLogBaseName = path.basename(errorLogPath); const outLogBaseName = path.basename(outLogPath); await rotateLogFile(outLogPath, logDirPath, "输出", maxOutLogSize); await rotateLogFile(errorLogPath, logDirPath, "错误", maxErrorLogSize); cleanupLogFiles(errorLogBaseName, outLogBaseName, logDirPath, maxLogDays); } catch (error2) { console.error("[pm2] 日志切割过程中发生错误:", error2); } }; const upgradeScriptPath = (config) => { const script = "index.mjs"; if (config.apps[0].script !== script) { config.apps[0].script = script; fs.writeFileSync(pm2Dir, JSON.stringify(config, null, 2)); } }; const checkInitialization = () => { const script = "index.mjs"; if (!fs.existsSync(script)) { console.log("正在升级到1.8.0版本..."); execSync("npx ki init", { cwd: process.cwd() }); console.log("升级成功 正在启动pm2服务..."); } }; const start$1 = async () => { rotateLogs(); console.log("[pm2] 启动中..."); const config = readPm2Config(); if (!config) { process.exit(1); } upgradeScriptPath(config); checkInitialization(); const { error: error2 } = execSync(`pm2 start ${pm2Dir}`, { cwd: process.cwd() }); if (error2) { console.log("[pm2] 启动失败"); console.log(error2); process.exit(1); } console.log("[pm2] 启动成功"); console.log("[pm2] 重启服务: pnpm rs"); console.log("[pm2] 查看日志: pnpm log"); console.log("[pm2] 停止服务: pnpm stop"); console.log("[pm2] 查看监控: pm2 monit"); console.log("[pm2] 查看列表: pm2 list"); process.exit(0); }; const log = async () => { rotateLogs(); const config = readPm2Config(); if (!config) { console.log("[pm2] 如果是新项目,请先前台启动生成配置文件: pnpm app"); process.exit(1); } try { const prefix = process.platform === "win32" ? "pm2.cmd" : "pm2"; spawn(prefix, ["logs", config.apps[0].name, "--lines", config.lines || 1e3], { stdio: "inherit", shell: true }); } catch (error2) { console.error("[pm2] 发生未知错误: 请检查pm2是否安装 【npm install -g pm2】"); console.error(error2); process.exit(1); } }; const stop = async () => { rotateLogs(); const config = readPm2Config(); if (!config) { console.log("[pm2] 如果是新项目,请先前台启动生成配置文件: pnpm app"); process.exit(1); } execSync(`pm2 stop ${config.apps[0].name}`, { cwd: process.cwd() }); console.log("[pm2] 停止成功"); process.exit(0); }; const restart = async (force) => { rotateLogs(); try { const forceRestart = () => { const appName2 = readPm2Config()?.apps?.[0]?.name || "karin"; execSync(`pm2 delete ${appName2}`, { cwd: process.cwd() }); execSync(`pm2 start ${pm2Dir}`, { cwd: process.cwd() }); console.log("[pm2] 重启成功"); }; console.log("[pm2] 重启中..."); if (!fs.existsSync(pm2Dir)) { console.log(`[pm2] 配置文件不存在 请检查 ${pm2Dir} 是否存在`); console.log("[pm2] 如果是新项目,请先前台启动生成配置文件: pnpm app"); process.exit(1); } if (force) { forceRestart(); process.exit(0); } const appName = readPm2Config()?.apps?.[0]?.name || "karin"; if (!appName) ; execSync(`pm2 restart ${appName}`, { cwd: process.cwd() }); console.log("[pm2] 重启成功"); process.exit(0); } catch (error2) { console.log("[pm2] 尝试直接重启失败 尝试删除服务并重新启动"); const appName = readPm2Config()?.apps?.[0]?.name || "karin"; execSync(`pm2 delete ${appName}`, { cwd: process.cwd() }); execSync(`pm2 start ${pm2Dir}`, { cwd: process.cwd() }); console.log("[pm2] 重启成功"); process.exit(0); } }; const pm2 = { start: start$1, log, stop, restart }; const ALIAS = Symbol.for("yaml.alias"); const DOC = Symbol.for("yaml.document"); const MAP = Symbol.for("yaml.map"); const PAIR = Symbol.for("yaml.pair"); const SCALAR$1 = Symbol.for("yaml.scalar"); const SEQ = Symbol.for("yaml.seq"); const NODE_TYPE = Symbol.for("yaml.node.type"); const isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS; const isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC; const isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP; const isPair = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === PAIR; const isScalar$1 = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SCALAR$1; const isSeq = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === SEQ; function isCollection$1(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$1: case SEQ: return true; } return false; } const hasAnchor = (node) => (isScalar$1(node) || isCollection$1(node)) && !!node.anchor; const BREAK$1 = Symbol("break visit"); const SKIP$1 = Symbol("skip children"); const REMOVE$1 = Symbol("remove node"); function visit$1(node, visitor) { const visitor_ = initVisitor(visitor); if (isDocument(node)) { const cd = visit_(null, node.contents, visitor_, Object.freeze([node])); if (cd === REMOVE$1) node.contents = null; } else visit_(null, node, visitor_, Object.freeze([])); } visit$1.BREAK = BREAK$1; visit$1.SKIP = SKIP$1; visit$1.REMOVE = REMOVE$1; function visit_(key, node, visitor, path2) { const ctrl = callVisitor(key, node, visitor, path2); if (isNode(ctrl) || isPair(ctrl)) { replaceNode(key, path2, ctrl); return visit_(key, ctrl, visitor, path2); } if (typeof ctrl !== "symbol") { if (isCollection$1(node)) { path2 = Object.freeze(path2.concat(node)); for (let i = 0; i < node.items.length; ++i) { const ci = visit_(i, node.items[i], visitor, path2); if (typeof ci === "number") i = ci - 1; else if (ci === BREAK$1) return BREAK$1; else if (ci === REMOVE$1) { node.items.splice(i, 1); i -= 1; } } } else if (isPair(node)) { path2 = Object.freeze(path2.concat(node)); const ck = visit_("key", node.key, visitor, path2); if (ck === BREAK$1) return BREAK$1; else if (ck === REMOVE$1) node.key = null; const cv = visit_("value", node.value, visitor, path2); if (cv === BREAK$1) return BREAK$1; else if (cv === REMOVE$1) node.value = null; } } return ctrl; } async function visitAsync(node, visitor) { const visitor_ = initVisitor(visitor); if (isDocument(node)) { const cd = await visitAsync_(null, node.contents, visitor_, Object.freeze([node])); if (cd === REMOVE$1) node.contents = null; } else await visitAsync_(null, node, visitor_, Object.freeze([])); } visitAsync.BREAK = BREAK$1; visitAsync.SKIP = SKIP$1; visitAsync.REMOVE = REMOVE$1; async function visitAsync_(key, node, visitor, path2) { const ctrl = await callVisitor(key, node, visitor, path2); if (isNode(ctrl) || isPair(ctrl)) { replaceNode(key, path2, ctrl); return visitAsync_(key, ctrl, visitor, path2); } if (typeof ctrl !== "symbol") { if (isCollection$1(node)) { path2 = Object.freeze(path2.concat(node)); for (let i = 0; i < node.items.length; ++i) { const ci = await visitAsync_(i, node.items[i], visitor, path2); if (typeof ci === "number") i = ci - 1; else if (ci === BREAK$1) return BREAK$1; else if (ci === REMOVE$1) { node.items.splice(i, 1); i -= 1; } } } else if (isPair(node)) { path2 = Object.freeze(path2.concat(node)); const ck = await visitAsync_("key", node.key, visitor, path2); if (ck === BREAK$1) return BREAK$1; else if (ck === REMOVE$1) node.key = null; const cv = await visitAsync_("value", node.value, visitor, path2); if (cv === BREAK$1) return BREAK$1; else if (cv === REMOVE$1) 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, path2) { if (typeof visitor === "function") return visitor(key, node, path2); if (isMap(node)) return visitor.Map?.(key, node, path2); if (isSeq(node)) return visitor.Seq?.(key, node, path2); if (isPair(node)) return visitor.Pair?.(key, node, path2); if (isScalar$1(node)) return visitor.Scalar?.(key, node, path2); if (isAlias(node)) return visitor.Alias?.(key, node, path2); return void 0; } function replaceNode(key, path2, node) { const parent = path2[path2.length - 1]; if (isCollection$1(parent)) { parent.items[key] = node; } else if (isPair(parent)) { if (key === "key") parent.key = node; else parent.value = node; } else if (isDocument(parent)) { parent.contents = node; } else { const pt = isAlias(parent) ? "alias" : "scalar"; throw new Error(`Cannot replace node with ${pt} parent`); } } const escapeChars = { "!": "%21", ",": "%2C", "[": "%5B", "]": "%5D", "{": "%7B", "}": "%7D" }; const escapeTagName = (tn) => tn.replace(/[!,[\]{}]/g, (ch) => escapeChars[ch]); 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 (error2) { onError(String(error2)); 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 && isNode(doc.contents)) { const tags = {}; visit$1(doc.contents, (_key, node) => { if (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:" }; 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$1(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); if (!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 && (isScalar$1(ref.node) || isCollection$1(ref.node))) { ref.node.anchor = ref.anchor; } else { const error2 = new Error("Failed to resolve repeated object (this should not happen)"); error2.source = source; throw error2; } } }, sourceObjects }; } 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); } 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 || !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; } class NodeBase { constructor(type) { Object.defineProperty(this, 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 (!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(this, "", ctx); if (typeof onAnchor === "function") for (const { count, res: res2 } of ctx.anchors.values()) onAnchor(res2, count); return typeof reviver === "function" ? applyReviver(reviver, { "": res }, "", res) : res; } } class Alias extends NodeBase { constructor(source) { super(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) { let found = void 0; visit$1(doc, { Node: (_key, node) => { if (node === this) return visit$1.BREAK; if (node.anchor === this.source) found = node; } }); return found; } toJSON(_arg, ctx) { if (!ctx) return { source: this.source }; const { anchors, doc, maxAliasCount } = ctx; const source = this.resolve(doc); if (!source) { const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`; throw new ReferenceError(msg); } let data = anchors.get(source); if (!data) { toJS(source, null, ctx); data = anchors.get(source); } if (!data || 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(doc, source, anchors); if (data.count * data.aliasCount > maxAliasCount) { const msg = "Excessive alias count indicates a resource exhaustion attack"; throw new ReferenceError(msg); } } return data.res; } toString(ctx, _onComment, _onChompKeep) { const src = `*${this.source}`; if (ctx) { 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, anchors) { if (isAlias(node)) { const source = node.resolve(doc); const anchor = anchors && source && anchors.get(source); return anchor ? anchor.count * anchor.aliasCount : 0; } else if (isCollection$1(node)) { let count = 0; for (const item of node.items) { const c = getAliasCount(doc, item, anchors); if (c > count) count = c; } return count; } else if (isPair(node)) { const kc = getAliasCount(doc, node.key, anchors); const vc = getAliasCount(doc, node.value, anchors); return Math.max(kc, vc); } return 1; } const isScalarValue = (value) => !value || typeof value !== "function" && typeof value !== "object"; class Scalar extends NodeBase { constructor(value) { super(SCALAR$1); this.value = value; } toJSON(arg, ctx) { return ctx?.keep ? this.value : 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"; const 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 (isDocument(value)) value = value.contents; if (isNode(value)) return value; if (isPair(value)) { const map2 = ctx.schema[MAP].createNode?.(ctx.schema, null, ctx); map2.items.push(value); return map2; } if (value instanceof String || value instanceof Number || value instanceof Boolean || typeof BigInt !== "undefined" && value instanceof BigInt) { value = value.valueOf(); } const { aliasDuplicateObjects, onAnchor, onTagObj, schema: schema2, sourceObjects } = ctx; let ref = void 0; if (aliasDuplicateObjects && value && typeof value === "object") { ref = sourceObjects.get(value); if (ref) { if (!ref.anchor) ref.anchor = onAnchor(value); return new 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, schema2.tags); if (!tagObj) { if (value && typeof value.toJSON === "function") { value = value.toJSON(); } if (!value || typeof value !== "object") { const node2 = new Scalar(value); if (ref) ref.node = node2; return node2; } tagObj = value instanceof Map ? schema2[MAP] : Symbol.iterator in Object(value) ? schema2[SEQ] : schema2[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(value); if (tagName) node.tag = tagName; else if (!tagObj.default) node.tag = tagObj.tag; if (ref) ref.node = node; return node; } function collectionFromPath(schema2, path2, value) { let v = value; for (let i = path2.length - 1; i >= 0; --i) { const k = path2[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(v, void 0, { aliasDuplicateObjects: false, keepUndefined: false, onAnchor: () => { throw new Error("This should not happen, please report a bug."); }, schema: schema2, sourceObjects: /* @__PURE__ */ new Map() }); } const isEmptyPath = (path2) => path2 == null || typeof path2 === "object" && !!path2[Symbol.iterator]().next().done; class Collection extends NodeBase { constructor(type, schema2) { super(type); Object.defineProperty(this, "schema", { value: schema2, configurable: true, enumerable: false, writable: true }); } /** * Create a copy of this collection. * * @param schema - If defined, overwrites the original's schema */ clone(schema2) { const copy = Object.create(Object.getPrototypeOf(this), Object.getOwnPropertyDescriptors(this)); if (schema2) copy.schema = schema2; copy.items = copy.items.map((it) => isNode(it) || isPair(it) ? it.clone(schema2) : 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(path2, value) { if (isEmptyPath(path2)) this.add(value); else { const [key, ...rest] = path2; const node = this.get(key, true); if (isCollection$1(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(path2) { const [key, ...rest] = path2; if (rest.length === 0) return this.delete(key); const node = this.get(key, true); if (isCollection$1(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(path2, keepScalar) { const [key, ...rest] = path2; const node = this.get(key, true); if (rest.length === 0) return !keepScalar && isScalar$1(node) ? node.value : node; else return isCollection$1(node) ? node.getIn(rest, keepScalar) : void 0; } hasAllNullValues(allowScalar) { return this.items.every((node) => { if (!isPair(node)) return false; const n = node.value; return n == null || allowScalar && isScalar$1(n) && n.value == null && !n.commentBefore && !n.comment && !n.tag; }); } /** * Checks if the collection includes a value with the key `key`. */ hasIn(path2) { const [key, ...rest] = path2; if (rest.length === 0) return this.has(key); const node = this.get(key, true); return isCollection$1(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(path2, value) { const [key, ...rest] = path2; if (rest.length === 0) { this.set(key, value); } else { const node = this.get(key, true); if (isCollection$1(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}`); } } } const stringifyComment = (str) => str.replace(/^(?!$)(?: $)?/gm, "#"); function indentComment(comment, indent) { if (/^\n+$/.test(comment)) return comment.substring(1); return indent ? comment.replace(/^(?! *$)/gm, indent) : comment; } const lineComment = (str, indent, comment) => str.endsWith("\n") ? indentComment(comment, indent) : comment.includes("\n") ? "\n" + indentComment(comment, indent) : (str.endsWith(" ") ? "" : " ") + comment; const FOLD_FLOW = "flow"; const FOLD_BLOCK = "block"; const 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 start2 = i + 1; let ch = text[start2]; while (ch === " " || ch === " ") { if (i < start2 + indent) { ch = text[++i]; } else { do { ch = text[++i]; } while (ch && ch !== "\n"); end = i; start2 = i + 1; ch = text[start2]; } } return end; } const getFoldOptions = (ctx, isBlock2) => ({ indentAtStart: isBlock2 ? ctx.indent.length : ctx.indentAtStart, lineWidth: ctx.options.lineWidth, minContentWidth: ctx.options.minContentWidth }); const containsDocumentMarker = (str) => /^(%|---|\.\.\.)/m.test(str); function lineLengthOverLimit(str, lineWidth, indentLength) { if (!lineWidth || lineWidth < 0) return false; const limit = lineWidth - indentLength; const strLen = str.length; if (strLen <= limit) return false; for (let i = 0, start2 = 0; i < strLen; ++i) { if (str[i] === "\n") { if (i - start2 > limit) return true; start2 = i + 1; if (strLen - start2 <= limit) return false; } } return true; } function doubleQuotedString(value, ctx) { const json = JSON.stringify(value); if (ctx.options.doubleQuotedAsJSON) return json; const { implicitKey } = ctx; const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength; const indent = ctx.indent || (containsDocumentMarker(value) ? " " : ""); let str = ""; let start2 = 0; for (let i = 0, ch = json[i]; ch; ch = json[++i]) { if (ch === " " && json[i + 1] === "\\" && json[i + 2] === "n") { str += json.slice(start2, i) + "\\ "; i += 1; start2 = i; ch = "\\"; } if (ch === "\\") switch (json[i + 1]) { case "u": { str += json.slice(start2, i); const code = json.substr(i + 2, 4); switch (code) { case "0000": str += "\\0"; break; case "0007": str += "\\a"; break; case "000b": str += "\\v"; break; case "001b": str += "\\e"; break; case "0085": str += "\\N"; break; case "00a0": str += "\\_"; break; case "2028": str += "\\L"; break; case "2029": str += "\\P"; break; default: if (code.substr(0, 2) === "00") str += "\\x" + code.substr(2); else str += json.substr(i, 6); } i += 5; start2 = i + 1; } break; case "n": if (implicitKey || json[i + 2] === '"' || json.length < minMultiLineLength) { i += 1; } else { str += json.slice(start2, i) + "\n\n"; while (json[i + 2] === "\\" && json[i + 3] === "n" && json[i + 4] !== '"') { str += "\n"; i += 2; } str += indent; if (json[i + 2] === " ") str += "\\"; i += 1; start2 = i + 1; } break; default: i += 1; } } str = start2 ? str + json.slice(start2) : json; return implicitKey ? str : foldFlowLines(str, indent, 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(res, indent, 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); } let blockEndNewlines; try { blockEndNewlines = new RegExp("(^|(?<!\n))\n+(?!\n|$)", "g"); } catch { blockEndNewlines = /\n+(?!\n|$)/g; } function blockString({ comment, type, value }, ctx, onComment, onChompKeep) { const { blockQuote, commentString, lineWidth } = ctx.options; if (!blockQuote || /\n[\t ]+$/.test(value) || /^\s*$/.test(value)) { return quotedString(value, ctx); } const indent = ctx.indent || (ctx.forceBlockIndent || containsDocumentMarker(value) ? " " : ""); const literal = blockQuote === "literal" ? true : blockQuote === "folded" || type === Scalar.BLOCK_FOLDED ? false : type === Scalar.BLOCK_LITERAL ? true : !lineLengthOverLimit(value, lineWidth, indent.length); if (!value) return literal ? "|\n" : ">\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 start2 = value.substring(0, startNlPos < startEnd ? startNlPos + 1 : startEnd); if (start2) { value = value.substring(start2.length); start2 = start2.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.BLOCK_FOLDED) { foldOptions.onOverflow = () => { literalFallback = true; }; } const body = foldFlowLines(`${start2}${foldedValue}${end}`, indent, FOLD_BLOCK, foldOptions); if (!literalFallback) return `>${header} ${indent}${body}`; } value = value.replace(/\n+/g, `$&${indent}`); return `|${header} ${indent}${start2}${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 (!value || /^[\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.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(str, indent, 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.QUOTE_DOUBLE) { if (/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(ss.value)) type = Scalar.QUOTE_DOUBLE; } const _stringify = (_type) => { switch (_type) { case Scalar.BLOCK_FOLDED: case Scalar.BLOCK_LITERAL: return implicitKey || inFlow ? quotedString(ss.value, ctx) : blockString(ss, ctx, onComment, onChompKeep); case Scalar.QUOTE_DOUBLE: return doubleQuotedString(ss.value, ctx); case Scalar.QUOTE_SINGLE: return singleQuotedString(ss.value, ctx); case 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; } function createStringifyContext(doc, options) { const opt = Object.assign({ blockQuote: true, commentString: 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, 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 (isScalar$1(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 ?? typeof obj; throw new Error(`Tag not resolved for ${name} value`); } return tagObj; } function stringifyProps(node, tagObj, { anchors, doc }) { if (!doc.directives) return ""; const props = []; const anchor = (isScalar$1(node) || isCollection$1(node)) && node.anchor; if (anchor && anchorIsValid(anchor)) { anchors.add(anchor); props.push(`&${anchor}`); } const tag = node.tag ? node.tag : tagObj.default ? null : tagObj.tag; if (tag) props.push(doc.directives.tagString(tag)); return props.join(" "); } function stringify$2(item, ctx, onComment, onChompKeep) { if (isPair(item)) return item.toString(ctx, onComment, onChompKeep); if (isAlias(item)) { if (ctx.doc.directives) return item.toString(ctx); if (