From 50c6e739a0c946a1a57aedbac55b11be58407e70 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 15:59:40 -0700 Subject: [PATCH 01/22] feat(pstricks): semantic layer rewrite with expanded command coverage Rewrites the parser around an ordered AST with diagnostics and source-order rendering, and roughly doubles the PSTricks surface. Commands added: pscustom, psgrid, psdots, psellipse, psbezier, pscurve, psecurve, psccurve, pswedge, multido. Text macros and header environments gain textbf/textit/texttt/underline, itemize/description, and the theorem family. A new expression parser in @latex2js/utils evaluates algebraic plot bodies (x^2, implicit multiplication, ternaries). Adds a Vite playground with a headless-friendly render page, a Playwright suite covering every corpus example plus interaction, and a CI workflow that runs both and uploads the rendered gallery. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- .github/workflows/run-tests.yaml | 66 +- .gitignore | 5 + bundle/latex2html5.bundle.js | 3209 +++++- package.json | 8 +- packages/html5/jest.config.ts | 13 + packages/html5/src/components/list.ts | 45 + packages/html5/src/index.ts | 5 +- packages/html5/test/corpus-render.test.ts | 47 + packages/html5/test/pspicture.test.ts | 276 + packages/html5/test/setup.ts | 12 + packages/html5/tsconfig.json | 3 +- packages/latex2js/jest.config.ts | 6 + packages/latex2js/package.json | 15 +- packages/latex2js/src/grammar/grammar.pegjs | 142 + packages/latex2js/src/grammar/parser.d.ts | 208 + packages/latex2js/src/grammar/parser.js | 1446 +++ packages/latex2js/src/index.ts | 2 + packages/latex2js/src/lib/environments.ts | 2 +- packages/latex2js/src/lib/headers.ts | 30 +- packages/latex2js/src/lib/parser.ts | 588 +- packages/latex2js/src/lib/text.ts | 72 + .../test/__snapshots__/parser.test.ts.snap | 886 +- packages/latex2js/test/corpus.test.ts | 45 + packages/latex2js/test/corpus/01.tex | 25 + packages/latex2js/test/corpus/02.tex | 4 + packages/latex2js/test/corpus/03.tex | 7 + packages/latex2js/test/corpus/04.tex | 30 + packages/latex2js/test/corpus/05.tex | 6 + packages/latex2js/test/corpus/06.tex | 7 + packages/latex2js/test/corpus/07.tex | 21 + packages/latex2js/test/corpus/08.tex | 12 + packages/latex2js/test/corpus/09.tex | 7 + packages/latex2js/test/corpus/10.tex | 33 + packages/latex2js/test/corpus/11.tex | 22 + packages/latex2js/test/corpus/12.tex | 51 + packages/latex2js/test/corpus/13.tex | 42 + .../latex2js/test/corpus/14-bar-chart.tex | 8 + packages/latex2js/test/corpus/15-scatter.tex | 7 + packages/latex2js/test/corpus/16-curves.tex | 8 + packages/latex2js/test/corpus/17-pie.tex | 9 + packages/latex2js/test/corpus/18-fills.tex | 15 + .../test/corpus/19-algebraic-plot.tex | 8 + packages/latex2js/test/corpus/20-document.tex | 22 + packages/latex2js/test/corpus/graph.tex | 79 + .../test/corpus/site-examples-index-1.tex | 678 ++ .../latex2js/test/corpus/site-index-1.tex | 120 + .../latex2js/test/corpus/site-index-2.tex | 37 + packages/latex2js/test/latex2js.test.ts | 70 +- .../latex2js/test/parser-semantics.test.ts | 454 + packages/latex2js/tsconfig.json | 3 +- packages/pstricks/jest.config.ts | 7 +- packages/pstricks/src/lib/psgraph.ts | 279 +- packages/pstricks/src/lib/pstricks.ts | 378 +- packages/pstricks/test/pstricks.test.ts | 243 + packages/pstricks/tsconfig.json | 3 +- packages/react/jest.config.ts | 2 +- packages/react/src/index.tsx | 2 +- packages/settings/jest.config.ts | 6 +- packages/settings/test/settings.test.ts | 41 + packages/settings/tsconfig.json | 3 +- packages/utils/jest.config.ts | 13 + packages/utils/src/expression.ts | 377 + packages/utils/src/index.ts | 41 +- packages/utils/test/expression.test.ts | 109 + packages/utils/test/utils.test.ts | 75 + packages/utils/tsconfig.json | 3 +- packages/vue/src/latex.vue | 2 + playground/e2e/examples.spec.ts | 70 + playground/e2e/interactive.spec.ts | 77 + playground/index.html | 27 + playground/package.json | 21 + playground/playwright.config.ts | 40 + playground/render.html | 16 + playground/src/main.ts | 228 + playground/src/render.ts | 55 + playground/src/style.css | 147 + playground/tsconfig.json | 20 + playground/vite.config.ts | 73 + pnpm-lock.yaml | 8734 +++++++---------- pnpm-workspace.yaml | 6 +- 80 files changed, 14192 insertions(+), 5822 deletions(-) create mode 100644 packages/html5/src/components/list.ts create mode 100644 packages/html5/test/corpus-render.test.ts create mode 100644 packages/html5/test/pspicture.test.ts create mode 100644 packages/html5/test/setup.ts create mode 100644 packages/latex2js/src/grammar/grammar.pegjs create mode 100644 packages/latex2js/src/grammar/parser.d.ts create mode 100644 packages/latex2js/src/grammar/parser.js create mode 100644 packages/latex2js/test/corpus.test.ts create mode 100644 packages/latex2js/test/corpus/01.tex create mode 100644 packages/latex2js/test/corpus/02.tex create mode 100644 packages/latex2js/test/corpus/03.tex create mode 100644 packages/latex2js/test/corpus/04.tex create mode 100644 packages/latex2js/test/corpus/05.tex create mode 100644 packages/latex2js/test/corpus/06.tex create mode 100644 packages/latex2js/test/corpus/07.tex create mode 100644 packages/latex2js/test/corpus/08.tex create mode 100644 packages/latex2js/test/corpus/09.tex create mode 100644 packages/latex2js/test/corpus/10.tex create mode 100644 packages/latex2js/test/corpus/11.tex create mode 100644 packages/latex2js/test/corpus/12.tex create mode 100644 packages/latex2js/test/corpus/13.tex create mode 100644 packages/latex2js/test/corpus/14-bar-chart.tex create mode 100644 packages/latex2js/test/corpus/15-scatter.tex create mode 100644 packages/latex2js/test/corpus/16-curves.tex create mode 100644 packages/latex2js/test/corpus/17-pie.tex create mode 100644 packages/latex2js/test/corpus/18-fills.tex create mode 100644 packages/latex2js/test/corpus/19-algebraic-plot.tex create mode 100644 packages/latex2js/test/corpus/20-document.tex create mode 100644 packages/latex2js/test/corpus/graph.tex create mode 100644 packages/latex2js/test/corpus/site-examples-index-1.tex create mode 100644 packages/latex2js/test/corpus/site-index-1.tex create mode 100644 packages/latex2js/test/corpus/site-index-2.tex create mode 100644 packages/latex2js/test/parser-semantics.test.ts create mode 100644 packages/pstricks/test/pstricks.test.ts create mode 100644 packages/settings/test/settings.test.ts create mode 100644 packages/utils/jest.config.ts create mode 100644 packages/utils/src/expression.ts create mode 100644 packages/utils/test/expression.test.ts create mode 100644 packages/utils/test/utils.test.ts create mode 100644 playground/e2e/examples.spec.ts create mode 100644 playground/e2e/interactive.spec.ts create mode 100644 playground/index.html create mode 100644 playground/package.json create mode 100644 playground/playwright.config.ts create mode 100644 playground/render.html create mode 100644 playground/src/main.ts create mode 100644 playground/src/render.ts create mode 100644 playground/src/style.css create mode 100644 playground/tsconfig.json create mode 100644 playground/vite.config.ts diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index bf4b3dff..4c7da12e 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -1,10 +1,13 @@ -name: LaTeX2JS tests +name: LaTeX2JS CI + on: push: + branches: [main] + pull_request: workflow_dispatch: jobs: - container-job: + test: runs-on: ubuntu-latest steps: @@ -14,20 +17,67 @@ jobs: - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 10 + version: 11 run_install: false - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 cache: 'pnpm' - name: Install dependencies - run: pnpm install + run: pnpm install --frozen-lockfile - - name: Build packages + - name: Build all packages run: pnpm build - - name: Run tests for latex2js - run: cd ./packages/latex2js && pnpm test + - name: Run all tests (parser, rendering, corpus, units) + run: pnpm test + + - name: Generated grammar is up to date + # Fails if the committed src/grammar/parser.js no longer matches + # grammar.pegjs (e.g. after editing the grammar without regenerating). + run: | + pnpm --filter latex2js grammar + git diff --exit-code -- packages/latex2js/src/grammar/ + + - name: Playground production build + run: pnpm dev:build + + e2e: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + version: 11 + run_install: false + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install Playwright browsers (workspace-local) + run: | + export PLAYWRIGHT_BROWSERS_PATH="$GITHUB_WORKSPACE/playground/.browsers" + pnpm --filter @latex2js/playground exec playwright install --with-deps chromium + + - name: Run browser tests (gallery + interactive) + run: pnpm e2e + + - name: Upload renderings as artifacts + uses: actions/upload-artifact@v4 + with: + name: example-renderings + path: playground/renders/*.png + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 1821c2d4..966e24e2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ packages/**/build packages/**/main packages/**/module packages/**/dist +playground/dist +playground/.browsers +playground/test-results +playground/playwright-report +playground/renders diff --git a/bundle/latex2html5.bundle.js b/bundle/latex2html5.bundle.js index 8e1791bd..26a2424d 100644 --- a/bundle/latex2html5.bundle.js +++ b/bundle/latex2html5.bundle.js @@ -22,6 +22,51 @@ function render(that) { },{}],2:[function(require,module,exports){ "use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = render; +function itemizeLine(line) { + var m = line.match(/\\item (.*)/); + if (m) + return '
  • ' + m[1] + '
  • '; + return line; +} +function descriptionLine(line) { + var m = line.match(/\\item\[([^\]]*)\]\s*(.*)/); + if (m) + return '
    ' + m[1] + '
    ' + m[2] + '
    '; + return itemizeLine(line); +} +/** + * Renders enumerate / itemize / description lists from \item lines. + */ +function render(that) { + const type = that.type || 'enumerate'; + const convert = type === 'description' ? descriptionLine : itemizeLine; + const lines = that.lines.map(convert).join('\n'); + let el; + if (type === 'enumerate') { + const ol = document.createElement('ol'); + ol.className = 'math enumerate'; + ol.innerHTML = lines; + el = ol; + } + else if (type === 'description') { + const dl = document.createElement('dl'); + dl.className = 'math description'; + dl.innerHTML = lines; + el = dl; + } + else { + const ul = document.createElement('ul'); + ul.className = 'math itemize'; + ul.innerHTML = lines; + el = ul; + } + return el; +} + +},{}],3:[function(require,module,exports){ +"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -37,7 +82,7 @@ function render(_that) { return div; } -},{"@latex2js/macros":14}],3:[function(require,module,exports){ +},{"@latex2js/macros":16}],4:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = render; @@ -48,7 +93,7 @@ function render(that) { return span; } -},{}],4:[function(require,module,exports){ +},{}],5:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = render; @@ -59,7 +104,7 @@ function render(that) { return span; } -},{}],5:[function(require,module,exports){ +},{}],6:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = render; @@ -121,7 +166,7 @@ function render(that) { return div; } -},{"@latex2js/pstricks":16,"@latex2js/utils":20}],6:[function(require,module,exports){ +},{"@latex2js/pstricks":18,"@latex2js/utils":23}],7:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = render; @@ -132,13 +177,13 @@ function render(that) { return pre; } -},{}],7:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.init = exports.macros = exports.math = exports.verbatim = exports.enumerate = exports.nicebox = exports.pspicture = void 0; +exports.init = exports.macros = exports.math = exports.verbatim = exports.list = exports.enumerate = exports.nicebox = exports.pspicture = void 0; exports.default = render; const latex2js_1 = __importDefault(require("latex2js")); const mathjaxjs_1 = require("mathjaxjs"); @@ -148,13 +193,15 @@ const nicebox_js_1 = __importDefault(require("./components/nicebox.js")); exports.nicebox = nicebox_js_1.default; const enumerate_js_1 = __importDefault(require("./components/enumerate.js")); exports.enumerate = enumerate_js_1.default; +const list_js_1 = __importDefault(require("./components/list.js")); +exports.list = list_js_1.default; const verbatim_js_1 = __importDefault(require("./components/verbatim.js")); exports.verbatim = verbatim_js_1.default; const math_js_1 = __importDefault(require("./components/math.js")); exports.math = math_js_1.default; const macros_1 = __importDefault(require("./components/macros")); exports.macros = macros_1.default; -const ELEMENTS = { pspicture: pspicture_js_1.default, nicebox: nicebox_js_1.default, enumerate: enumerate_js_1.default, verbatim: verbatim_js_1.default, math: math_js_1.default, macros: macros_1.default }; +const ELEMENTS = { pspicture: pspicture_js_1.default, nicebox: nicebox_js_1.default, enumerate: enumerate_js_1.default, itemize: list_js_1.default, description: list_js_1.default, verbatim: verbatim_js_1.default, math: math_js_1.default, macros: macros_1.default }; function render(tex, resolve) { const done = () => { const latex = new latex2js_1.default(); @@ -188,7 +235,1455 @@ const init = () => { }; exports.init = init; -},{"./components/enumerate.js":1,"./components/macros":2,"./components/math.js":3,"./components/nicebox.js":4,"./components/pspicture.js":5,"./components/verbatim.js":6,"latex2js":8,"mathjaxjs":15}],8:[function(require,module,exports){ +},{"./components/enumerate.js":1,"./components/list.js":2,"./components/macros":3,"./components/math.js":4,"./components/nicebox.js":5,"./components/pspicture.js":6,"./components/verbatim.js":7,"latex2js":10,"mathjaxjs":17}],9:[function(require,module,exports){ +// @generated by Peggy 5.1.0. +// +// https://peggyjs.org/ + +"use strict"; + +class peg$SyntaxError extends SyntaxError { + constructor(message, expected, found, location) { + super(message); + this.expected = expected; + this.found = found; + this.location = location; + this.name = "SyntaxError"; + } + + format(sources) { + let str = "Error: " + this.message; + if (this.location) { + let src = null; + const st = sources.find(s => s.source === this.location.source); + if (st) { + src = st.text.split(/\r\n|\n|\r/g); + } + const s = this.location.start; + const offset_s = (this.location.source && (typeof this.location.source.offset === "function")) + ? this.location.source.offset(s) + : s; + const loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column; + if (src) { + const e = this.location.end; + const filler = "".padEnd(offset_s.line.toString().length, " "); + const line = src[s.line - 1]; + const last = s.line === e.line ? e.column : line.length + 1; + const hatLen = (last - s.column) || 1; + str += "\n --> " + loc + "\n" + + filler + " |\n" + + offset_s.line + " | " + line + "\n" + + filler + " | " + "".padEnd(s.column - 1, " ") + + "".padEnd(hatLen, "^"); + } else { + str += "\n at " + loc; + } + } + return str; + } + + static buildMessage(expected, found) { + function hex(ch) { + return ch.codePointAt(0).toString(16).toUpperCase(); + } + + const nonPrintable = Object.prototype.hasOwnProperty.call(RegExp.prototype, "unicode") + ? new RegExp("[\\p{C}\\p{Mn}\\p{Mc}]", "gu") + : null; + function unicodeEscape(s) { + if (nonPrintable) { + return s.replace(nonPrintable, ch => "\\u{" + hex(ch) + "}"); + } + return s; + } + + function literalEscape(s) { + return unicodeEscape(s + .replace(/\\/g, "\\\\") + .replace(/"/g, "\\\"") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch)) + .replace(/[\x10-\x1F\x7F-\x9F]/g, ch => "\\x" + hex(ch))); + } + + function classEscape(s) { + return unicodeEscape(s + .replace(/\\/g, "\\\\") + .replace(/\]/g, "\\]") + .replace(/\^/g, "\\^") + .replace(/-/g, "\\-") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, ch => "\\x0" + hex(ch)) + .replace(/[\x10-\x1F\x7F-\x9F]/g, ch => "\\x" + hex(ch))); + } + + const DESCRIBE_EXPECTATION_FNS = { + literal(expectation) { + return "\"" + literalEscape(expectation.text) + "\""; + }, + + class(expectation) { + const escapedParts = expectation.parts.map( + part => (Array.isArray(part) + ? classEscape(part[0]) + "-" + classEscape(part[1]) + : classEscape(part)) + ); + + return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]" + (expectation.unicode ? "u" : ""); + }, + + any() { + return "any character"; + }, + + end() { + return "end of input"; + }, + + other(expectation) { + return expectation.description; + }, + }; + + function describeExpectation(expectation) { + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + } + + function describeExpected(expected) { + const descriptions = expected.map(describeExpectation); + descriptions.sort(); + + if (descriptions.length > 0) { + let j = 1; + for (let i = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + + switch (descriptions.length) { + case 1: + return descriptions[0]; + + case 2: + return descriptions[0] + " or " + descriptions[1]; + + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + + function describeFound(found) { + return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + } + + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; + } +} + +function peg$parse(input, options) { + options = options !== undefined ? options : {}; + + const peg$FAILED = {}; + const peg$source = options.grammarSource; + + const peg$startRuleFunctions = { + Document: peg$parseDocument, + }; + let peg$startRuleFunction = peg$parseDocument; + + const peg$c0 = "\\begin{"; + const peg$c1 = "verbatim"; + const peg$c2 = "print"; + const peg$c3 = "}"; + const peg$c4 = "\\end{"; + const peg$c5 = "\\"; + const peg$c6 = "begin{"; + const peg$c7 = "end{"; + const peg$c8 = "%"; + const peg$c9 = "\r\n"; + + const peg$r0 = /^[a-zA-Z*]/; + const peg$r1 = /^[a-zA-Z@]/; + const peg$r2 = /^[([{]/; + const peg$r3 = /^[)\]}]/; + const peg$r4 = /^[\n\r]/; + const peg$r5 = /^[ \t]/; + + const peg$e0 = peg$anyExpectation(); + const peg$e1 = peg$literalExpectation("\\begin{", false); + const peg$e2 = peg$literalExpectation("verbatim", false); + const peg$e3 = peg$literalExpectation("print", false); + const peg$e4 = peg$literalExpectation("}", false); + const peg$e5 = peg$literalExpectation("\\end{", false); + const peg$e6 = peg$classExpectation([["a", "z"], ["A", "Z"], "*"], false, false, false); + const peg$e7 = peg$literalExpectation("\\", false); + const peg$e8 = peg$literalExpectation("begin{", false); + const peg$e9 = peg$literalExpectation("end{", false); + const peg$e10 = peg$classExpectation([["a", "z"], ["A", "Z"], "@"], false, false, false); + const peg$e11 = peg$literalExpectation("%", false); + const peg$e12 = peg$classExpectation(["(", "[", "{"], false, false, false); + const peg$e13 = peg$classExpectation([")", "]", "}"], false, false, false); + const peg$e14 = peg$literalExpectation("\r\n", false); + const peg$e15 = peg$classExpectation(["\n", "\r"], false, false, false); + const peg$e16 = peg$classExpectation([" ", "\t"], false, false, false); + + function peg$f0(segs) { return segs; } + function peg$f1(e) { return { kind: 'strayEnd', name: e.name, raw: e.raw, loc: loc() }; } + function peg$f2(start, content, end) { + return { + kind: 'env', + name: start.name, + verbatim: true, + begin: start, + end: { name: start.name, raw: '\\end{' + end + '}', loc: loc() }, + content: [{ + kind: 'verbatim', + text: content.map((pair) => pair[1]).join('').replace(/\n$/, '') + }], + loc: loc() + }; + } + function peg$f3(n) { return { name: n, raw: '\\begin{' + n + '}', loc: loc() }; } + function peg$f4(n) { return n; } + function peg$f5(b, content, e) { + return { kind: 'env', name: b.name, verbatim: false, begin: b, end: e || null, content: content, loc: loc() }; + } + function peg$f6(name, tail) { + return { name: name, raw: '\\begin{' + name + '}' + tail, loc: loc() }; + } + function peg$f7(name) { + return { name: name, raw: '\\end{' + name + '}', loc: loc() }; + } + function peg$f8(chars) { return chars.join(''); } + function peg$f9(start, tail) { + depth = 0; + return { kind: 'command', name: start.name, raw: start.raw + tail, loc: loc() }; + } + function peg$f10(chars) { + return { name: chars.join(''), raw: '\\' + chars.join('') }; + } + function peg$f11(parts) { return parts.join(''); } + function peg$f12() { depth++; return text(); } + function peg$f13() { depth = Math.max(0, depth - 1); return text(); } + function peg$f14() { return depth === 0; } + function peg$f15(c) { return c; } + function peg$f16() { return depth > 0; } + function peg$f17(c) { return c; } + function peg$f18() { return ''; } + function peg$f19(parts, eol) { return { kind: 'line', parts: parts, hasEol: !!eol, loc: loc() }; } + function peg$f20(eol) { return { kind: 'line', parts: [], hasEol: true, loc: loc() }; } + function peg$f21(c) { return { kind: 'char', c: c, loc: loc() }; } + let peg$currPos = options.peg$currPos | 0; + let peg$savedPos = peg$currPos; + const peg$posDetailsCache = [{ line: 1, column: 1 }]; + let peg$maxFailPos = peg$currPos; + let peg$maxFailExpected = options.peg$maxFailExpected || []; + let peg$silentFails = options.peg$silentFails | 0; + + let peg$result; + + if (options.startRule) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function offset() { + return peg$savedPos; + } + + function range() { + return { + source: peg$source, + start: peg$savedPos, + end: peg$currPos, + }; + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildStructuredError( + [peg$otherExpectation(description)], + input.substring(peg$savedPos, peg$currPos), + location + ); + } + + function error(message, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildSimpleError(message, location); + } + + function peg$getUnicode(pos = peg$currPos) { + const cp = input.codePointAt(pos); + if (cp === undefined) { + return ""; + } + return String.fromCodePoint(cp); + } + + function peg$literalExpectation(text, ignoreCase) { + return { type: "literal", text, ignoreCase }; + } + + function peg$classExpectation(parts, inverted, ignoreCase, unicode) { + return { type: "class", parts, inverted, ignoreCase, unicode }; + } + + function peg$anyExpectation() { + return { type: "any" }; + } + + function peg$endExpectation() { + return { type: "end" }; + } + + function peg$otherExpectation(description) { + return { type: "other", description }; + } + + function peg$computePosDetails(pos) { + let details = peg$posDetailsCache[pos]; + let p; + + if (details) { + return details; + } else { + if (pos >= peg$posDetailsCache.length) { + p = peg$posDetailsCache.length - 1; + } else { + p = pos; + while (!peg$posDetailsCache[--p]) {} + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column, + }; + + while (p < pos) { + if (input.charCodeAt(p) === 10) { + details.line++; + details.column = 1; + } else { + details.column++; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + + return details; + } + } + + function peg$computeLocation(startPos, endPos, offset) { + const startPosDetails = peg$computePosDetails(startPos); + const endPosDetails = peg$computePosDetails(endPos); + + const res = { + source: peg$source, + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column, + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column, + }, + }; + if (offset && peg$source && (typeof peg$source.offset === "function")) { + res.start = peg$source.offset(res.start); + res.end = peg$source.offset(res.end); + } + return res; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildSimpleError(message, location) { + return new peg$SyntaxError(message, null, null, location); + } + + function peg$buildStructuredError(expected, found, location) { + return new peg$SyntaxError( + peg$SyntaxError.buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$parseDocument() { + let s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parseSegment(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseSegment(); + } + peg$savedPos = s0; + s1 = peg$f0(s1); + s0 = s1; + + return s0; + } + + function peg$parseSegment() { + let s0; + + s0 = peg$parseEnv(); + if (s0 === peg$FAILED) { + s0 = peg$parseStrayEnd(); + if (s0 === peg$FAILED) { + s0 = peg$parseLine(); + } + } + + return s0; + } + + function peg$parseStrayEnd() { + let s0, s1; + + s0 = peg$currPos; + s1 = peg$parseEndTag(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f1(s1); + } + s0 = s1; + + return s0; + } + + function peg$parseEnv() { + let s0; + + s0 = peg$parseVerbatimEnv(); + if (s0 === peg$FAILED) { + s0 = peg$parseRegularEnv(); + } + + return s0; + } + + function peg$parseVerbatimEnv() { + let s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseBeginVerb(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$currPos; + peg$silentFails++; + s5 = peg$parseEndVerb(); + peg$silentFails--; + if (s5 === peg$FAILED) { + s4 = undefined; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + if (input.length > peg$currPos) { + s5 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$currPos; + peg$silentFails++; + s5 = peg$parseEndVerb(); + peg$silentFails--; + if (s5 === peg$FAILED) { + s4 = undefined; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + if (input.length > peg$currPos) { + s5 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + s3 = peg$parseEndVerb(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f2(s1, s2, s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseBeginVerb() { + let s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 7) === peg$c0) { + s1 = peg$c0; + peg$currPos += 7; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s1 !== peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c1) { + s2 = peg$c1; + peg$currPos += 8; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + if (s2 === peg$FAILED) { + if (input.substr(peg$currPos, 5) === peg$c2) { + s2 = peg$c2; + peg$currPos += 5; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e3); } + } + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 125) { + s3 = peg$c3; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f3(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseEndVerb() { + let s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 5) === peg$c4) { + s1 = peg$c4; + peg$currPos += 5; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e5); } + } + if (s1 !== peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c1) { + s2 = peg$c1; + peg$currPos += 8; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + if (s2 === peg$FAILED) { + if (input.substr(peg$currPos, 5) === peg$c2) { + s2 = peg$c2; + peg$currPos += 5; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e3); } + } + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 125) { + s3 = peg$c3; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f4(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseRegularEnv() { + let s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseBeginTag(); + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + s3 = []; + s4 = peg$parseEnvContent(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parseEnvContent(); + } + s4 = peg$parse_(); + s5 = peg$parseEndTag(); + if (s5 === peg$FAILED) { + s5 = null; + } + peg$savedPos = s0; + s0 = peg$f5(s1, s3, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseBeginTag() { + let s0, s1, s2, s3, s4; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 7) === peg$c0) { + s1 = peg$c0; + peg$currPos += 7; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseEnvName(); + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 125) { + s3 = peg$c3; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parseTail(); + peg$savedPos = s0; + s0 = peg$f6(s2, s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseEndTag() { + let s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 5) === peg$c4) { + s1 = peg$c4; + peg$currPos += 5; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e5); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseEnvName(); + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 125) { + s3 = peg$c3; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f7(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseEnvName() { + let s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = input.charAt(peg$currPos); + if (peg$r0.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e6); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = input.charAt(peg$currPos); + if (peg$r0.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e6); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f8(s1); + } + s0 = s1; + + return s0; + } + + function peg$parseEnvContent() { + let s0; + + s0 = peg$parseEnv(); + if (s0 === peg$FAILED) { + s0 = peg$parseCommand(); + if (s0 === peg$FAILED) { + s0 = peg$parseLine(); + } + } + + return s0; + } + + function peg$parseCommand() { + let s0, s1, s2; + + s0 = peg$currPos; + s1 = peg$parseCommandStart(); + if (s1 !== peg$FAILED) { + s2 = peg$parseTail(); + peg$savedPos = s0; + s0 = peg$f9(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseCommandStart() { + let s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c5; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e7); } + } + if (s1 !== peg$FAILED) { + s2 = peg$currPos; + peg$silentFails++; + if (input.substr(peg$currPos, 6) === peg$c6) { + s3 = peg$c6; + peg$currPos += 6; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e8); } + } + peg$silentFails--; + if (s3 === peg$FAILED) { + s2 = undefined; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$currPos; + peg$silentFails++; + if (input.substr(peg$currPos, 4) === peg$c7) { + s4 = peg$c7; + peg$currPos += 4; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e9); } + } + peg$silentFails--; + if (s4 === peg$FAILED) { + s3 = undefined; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = input.charAt(peg$currPos); + if (peg$r1.test(s5)) { + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e10); } + } + if (s5 !== peg$FAILED) { + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = input.charAt(peg$currPos); + if (peg$r1.test(s5)) { + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e10); } + } + } + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f10(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseTail() { + let s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parseTailPart(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseTailPart(); + } + peg$savedPos = s0; + s1 = peg$f11(s1); + s0 = s1; + + return s0; + } + + function peg$parseTailPart() { + let s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$parseComment(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseOpen(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f12(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseClose(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f13(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + peg$savedPos = peg$currPos; + s1 = peg$f14(); + if (s1) { + s1 = undefined; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$currPos; + peg$silentFails++; + s3 = peg$parseEOL(); + peg$silentFails--; + if (s3 === peg$FAILED) { + s2 = undefined; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$currPos; + peg$silentFails++; + s4 = peg$parseCommandStart(); + peg$silentFails--; + if (s4 === peg$FAILED) { + s3 = undefined; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s4 = peg$currPos; + peg$silentFails++; + s5 = peg$parseBeginStart(); + peg$silentFails--; + if (s5 === peg$FAILED) { + s4 = undefined; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + s5 = peg$currPos; + peg$silentFails++; + s6 = peg$parseEndStart(); + peg$silentFails--; + if (s6 === peg$FAILED) { + s5 = undefined; + } else { + peg$currPos = s5; + s5 = peg$FAILED; + } + if (s5 !== peg$FAILED) { + if (input.length > peg$currPos) { + s6 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s6 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f15(s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + peg$savedPos = peg$currPos; + s1 = peg$f16(); + if (s1) { + s1 = undefined; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + if (input.length > peg$currPos) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f17(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } + } + } + + return s0; + } + + function peg$parseComment() { + let s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 37) { + s1 = peg$c8; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e11); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$currPos; + peg$silentFails++; + s5 = peg$parseEOL(); + peg$silentFails--; + if (s5 === peg$FAILED) { + s4 = undefined; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + if (input.length > peg$currPos) { + s5 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$currPos; + peg$silentFails++; + s5 = peg$parseEOL(); + peg$silentFails--; + if (s5 === peg$FAILED) { + s4 = undefined; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + if (input.length > peg$currPos) { + s5 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + peg$savedPos = s0; + s0 = peg$f18(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseOpen() { + let s0; + + s0 = input.charAt(peg$currPos); + if (peg$r2.test(s0)) { + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e12); } + } + + return s0; + } + + function peg$parseClose() { + let s0; + + s0 = input.charAt(peg$currPos); + if (peg$r3.test(s0)) { + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e13); } + } + + return s0; + } + + function peg$parseBeginStart() { + let s0; + + if (input.substr(peg$currPos, 7) === peg$c0) { + s0 = peg$c0; + peg$currPos += 7; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + + return s0; + } + + function peg$parseEndStart() { + let s0; + + if (input.substr(peg$currPos, 5) === peg$c4) { + s0 = peg$c4; + peg$currPos += 5; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e5); } + } + + return s0; + } + + function peg$parseLine() { + let s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parseLinePart(); + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseLinePart(); + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseEOL(); + if (s2 === peg$FAILED) { + s2 = null; + } + peg$savedPos = s0; + s0 = peg$f19(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseEOL(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f20(s1); + } + s0 = s1; + } + + return s0; + } + + function peg$parseLinePart() { + let s0, s1, s2, s3, s4; + + s0 = peg$parseComment(); + if (s0 === peg$FAILED) { + s0 = peg$parseCommand(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$currPos; + peg$silentFails++; + s2 = peg$parseBeginStart(); + peg$silentFails--; + if (s2 === peg$FAILED) { + s1 = undefined; + } else { + peg$currPos = s1; + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$currPos; + peg$silentFails++; + s3 = peg$parseEndStart(); + peg$silentFails--; + if (s3 === peg$FAILED) { + s2 = undefined; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$currPos; + peg$silentFails++; + s4 = peg$parseEOL(); + peg$silentFails--; + if (s4 === peg$FAILED) { + s3 = undefined; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + if (input.length > peg$currPos) { + s4 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f21(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } + + return s0; + } + + function peg$parseEOL() { + let s0; + + if (input.substr(peg$currPos, 2) === peg$c9) { + s0 = peg$c9; + peg$currPos += 2; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + if (s0 === peg$FAILED) { + s0 = input.charAt(peg$currPos); + if (peg$r4.test(s0)) { + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + } + + return s0; + } + + function peg$parse_() { + let s0, s1; + + s0 = []; + s1 = input.charAt(peg$currPos); + if (peg$r5.test(s1)) { + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + while (s1 !== peg$FAILED) { + s0.push(s1); + s1 = input.charAt(peg$currPos); + if (peg$r5.test(s1)) { + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + } + + return s0; + } + + + let depth = 0; + + function loc() { + const l = location(); + return { line: l.start.line, column: l.start.column }; + } + + peg$result = peg$startRuleFunction(); + + const peg$success = (peg$result !== peg$FAILED && peg$currPos === input.length); + function peg$throw() { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + + throw peg$buildStructuredError( + peg$maxFailExpected, + peg$maxFailPos < input.length ? peg$getUnicode(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } + if (options.peg$library) { + return /** @type {any} */ ({ + peg$result, + peg$currPos, + peg$FAILED, + peg$maxFailExpected, + peg$maxFailPos, + peg$success, + peg$throw: peg$success ? undefined : peg$throw, + }); + } + if (peg$success) { + return peg$result; + } else { + peg$throw(); + } +} + +module.exports = { + StartRules: ["Document"], + SyntaxError: peg$SyntaxError, + parse: peg$parse, +}; + +},{}],10:[function(require,module,exports){ "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; @@ -202,6 +1697,7 @@ const ignore_1 = __importDefault(require("./lib/ignore")); const parser_1 = __importDefault(require("./lib/parser")); class LaTeX2HTML5 { constructor(Text = text_1.default, Headers = headers_1.default, Environments = environments_1.default, Ignore = ignore_1.default, PSTricks = pstricks_1.pstricks, Views = {}) { + this.lastDiagnostics = []; this.Text = Text; this.Headers = Headers; this.Environments = Environments; @@ -251,6 +1747,7 @@ class LaTeX2HTML5 { parse(text) { const parser = new parser_1.default(this); const parsed = parser.parse(text); + this.lastDiagnostics = parser.diagnostics; parsed.forEach((element) => { if (!element.hasOwnProperty('type')) { throw new Error('no type!'); @@ -262,13 +1759,13 @@ class LaTeX2HTML5 { } exports.default = LaTeX2HTML5; -},{"./lib/environments":9,"./lib/headers":10,"./lib/ignore":11,"./lib/parser":12,"./lib/text":13,"@latex2js/pstricks":16}],9:[function(require,module,exports){ +},{"./lib/environments":11,"./lib/headers":12,"./lib/ignore":13,"./lib/parser":14,"./lib/text":15,"@latex2js/pstricks":18}],11:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const environments = ['pspicture', 'verbatim', 'enumerate', 'print', 'nicebox']; +const environments = ['pspicture', 'verbatim', 'enumerate', 'print', 'nicebox', 'itemize', 'description']; exports.default = environments; -},{}],10:[function(require,module,exports){ +},{}],12:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Functions = exports.Expressions = void 0; @@ -277,10 +1774,24 @@ exports.Expressions = { claim: /\\begin\{claim\}/, corollary: /\\begin\{corollary\}/, definition: /\\begin\{definition\}/, + lemma: /\\begin\{lemma\}/, + proposition: /\\begin\{proposition\}/, + axiom: /\\begin\{axiom\}/, + remark: /\\begin\{remark\}/, + note: /\\begin\{note\}/, + exercise: /\\begin\{exercise\}/, + question: /\\begin\{question\}/, endclaim: /\\end\{claim\}/, - endcorallary: /\\end\{corallary\}/, + endcorollary: /\\end\{corollary\}/, enddefinition: /\\end\{definition\}/, endexample: /\\end\{example\}/, + endlemma: /\\end\{lemma\}/, + endproposition: /\\end\{proposition\}/, + endaxiom: /\\end\{axiom\}/, + endremark: /\\end\{remark\}/, + endnote: /\\end\{note\}/, + endexercise: /\\end\{exercise\}/, + endquestion: /\\end\{question\}/, endproblem: /\\end\{problem\}/, endsolution: /\\end\{solution\}/, endtheorem: /\\end\{theorem\}/, @@ -297,10 +1808,24 @@ exports.Functions = { claim: () => '

    Claim

    ', corollary: () => '

    Corollary

    ', definition: () => '

    Definition

    ', + lemma: () => '

    Lemma

    ', + proposition: () => '

    Proposition

    ', + axiom: () => '

    Axiom

    ', + remark: () => '

    Remark

    ', + note: () => '

    Note

    ', + exercise: () => '

    Exercise

    ', + question: () => '

    Question

    ', endclaim: () => '', endcorollary: () => '', enddefinition: () => '', endexample: () => '', + endlemma: () => '', + endproposition: () => '', + endaxiom: () => '', + endremark: () => '', + endnote: () => '', + endexercise: () => '', + endquestion: () => '', endproblem: () => '', endsolution: () => '', endtheorem: () => '', @@ -317,7 +1842,7 @@ exports.default = { Functions: exports.Functions }; -},{}],11:[function(require,module,exports){ +},{}],13:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const ignore = [ @@ -343,9 +1868,55 @@ const ignore = [ ]; exports.default = ignore; -},{}],12:[function(require,module,exports){ +},{}],14:[function(require,module,exports){ "use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); Object.defineProperty(exports, "__esModule", { value: true }); +const pegParser = __importStar(require("../grammar/parser.js")); +/** + * Parser: turns a LaTeX-ish document into the flat environment objects the + * components consume ({type, lines, env, plot}) — but driven by the Peggy + * grammar in src/grammar instead of per-line regular expressions. + * + * The grammar tokenizes structure (balanced environments, commands with args, + * comments, verbatim). This class interprets that tree using the registries + * (Text / Headers / Ignore / PSTricks / Delimiters), so the runtime extension + * API (addEnvironment / addText / addHeaders) keeps working. It also collects + * diagnostics (unclosed environments, unknown commands, syntax errors) that + * were previously silent. + */ class Parser { constructor(LaTeX2JS) { this.Ignore = LaTeX2JS.Ignore; @@ -359,30 +1930,206 @@ class Parser { '', 'units=1cm,linecolor=black,linestyle=solid,fillstyle=none' ]); + this.diagnostics = []; } + // ------------------------------------------------------------------------- + // Public API + // ------------------------------------------------------------------------- parse(text) { + this.diagnostics = []; if (!text) return []; - var lines = text.split('\n'); - this.parseEnvText(lines); - this.parseEnv(lines); + const tree = this.parseTree(text); + this.walk(tree); this.objects.forEach((obj) => { if (obj.type.match(/pspicture/)) { - obj.plot = this.parsePSTricks(obj.lines, obj.env); + obj.plot = this.parsePSTricks(obj.commands || [], obj.env); + delete obj.commands; } }); return this.objects; } - newEnvironment(type) { - if (this.environment && this.environment.lines.length) { - this.environment.settings = { ...this.settings }; - this.objects.push(this.environment); + // ------------------------------------------------------------------------- + // Grammar integration + // ------------------------------------------------------------------------- + parseTree(text) { + try { + return pegParser.parse(text); } - this.environment = { - type: type, - lines: [] - }; + catch (err) { + const loc = err.location || { start: { line: 1, column: 1 } }; + this.diagnostics.push({ + severity: 'error', + message: `parse error: ${err.message || String(err)}`, + line: loc.start.line, + column: loc.start.column + }); + // Degraded fallback: treat the whole input as a math text block. + return [{ kind: 'raw', text: text }]; + } + } + // ------------------------------------------------------------------------- + // Tree walk + // ------------------------------------------------------------------------- + walk(segments) { + this.objects = []; + this.environment = { type: 'math', lines: [] }; + segments.forEach((seg) => this.walkSegment(seg)); + this.newEnvironment('math'); + } + walkSegment(seg) { + if (seg.kind === 'raw') { + seg.text.split('\n').forEach((line) => this.pushMathLine(line)); + return; + } + switch (seg.kind) { + case 'line': + this.walkContent(seg); + break; + case 'env': + this.walkEnv(seg); + break; + case 'strayEnd': + if (this.isIgnored(seg.raw)) + return; + this.diagnose('warning', `unexpected \\end{${seg.name}}`, seg.loc); + break; + } + } + walkEnv(env) { + const name = env.name; + // Ignored wrapper environments (center, document, interactive…) are + // dropped, but their content is still walked in the current context. + if (this.isIgnoredEnv(name)) { + env.content.forEach((c) => this.walkContent(c)); + return; + } + const structural = env.verbatim || !!this.Delimiters[name]; + if (!structural) { + // Non-structural environments (theorem, proof, quotation…) flatten into + // the current environment as header text (handled by the Headers pass). + const inPspicture = this.inPspicture(); + if (inPspicture) + this.pushLine(env.begin.raw); + else + this.pushMathLine(env.begin.raw); + env.content.forEach((c) => this.walkContent(c)); + if (env.end) { + if (inPspicture) + this.pushLine(env.end.raw); + else + this.pushMathLine(env.end.raw); + } + else { + this.diagnose('warning', `unclosed \\begin{${name}}`, env.begin.loc); + } + return; + } + // Structural environment: close the current one and open a new one. + this.newEnvironment(name); + if (!env.verbatim) + this.metaData(name, env); + if (env.verbatim) { + const v = env.content[0]; + this.environment.lines = v && v.kind === 'verbatim' ? v.text.split('\n') : []; + } + else if (name.match(/pspicture/)) { + this.environment.commands = []; + env.content.forEach((c) => this.walkContent(c)); + } + else { + // enumerate / nicebox: content is text lines (with transforms). + env.content.forEach((c) => this.walkContent(c)); + } + if (env.end && env.end.name !== name) { + this.diagnose('warning', `\\end{${env.end.name}} does not match \\begin{${name}}`, env.end.loc); + } + else if (!env.end) { + this.diagnose('warning', `unclosed environment '${name}'`, env.begin.loc); + } + this.newEnvironment('math'); + } + /** + * Walk one node of environment content. Behavior depends on the current + * environment: inside pspicture we collect commands (and raw lines) for plot + * extraction; elsewhere lines go through the text/header passes. + */ + walkContent(node) { + const inPspicture = this.inPspicture(); + switch (node.kind) { + case 'line': { + // Comment-only lines are dropped (mirrors the old /^%/ ignore rule). + const allComments = node.parts.length > 0 && node.parts.every((p) => p.kind === 'comment'); + if (allComments) + return; + if (node.parts.length === 0) { + this.pushBlankLine(inPspicture); + return; + } + const text = this.lineToString(node); + if (inPspicture) + this.pushLine(text); + else + this.pushMathLine(text); + break; + } + case 'command': { + if (node.name === 'psset') { + this.parseUnits(node.raw); + return; + } + if (inPspicture) + this.environment.commands.push(node); + else + this.pushMathLine(node.raw); + break; + } + case 'env': + this.walkEnv(node); + break; + default: + break; + } + } + /** + * Convert a Line node's parts back to a string, dropping comment fragments. + */ + lineToString(line) { + return line.parts + .filter((p) => p.kind !== 'comment') + .map((p) => (p.kind === 'char' ? p.c : p.raw)) + .join(''); + } + // ------------------------------------------------------------------------- + // Line handling + // ------------------------------------------------------------------------- + inPspicture() { + return !!(this.environment && this.environment.type.match(/pspicture/)); + } + pushBlankLine(inPspicture) { + if (inPspicture) + return; + if (this.inPspicture()) + return; + this.environment.lines.push('
    '); + } + pushMathLine(text) { + if (this.isIgnored(text)) + return; + if (!text.trim().length) { + this.environment.lines.push('
    '); + return; + } + if (this.PSTricks.Expressions.psset.test(text)) { + this.parseUnits(text); + return; + } + const processed = this.parseText(text); + if (processed.trim().length) + this.environment.lines.push(processed); } + /** Raw line inside a pspicture: no text/header transforms (they corrupt + * PSTricks content). */ pushLine(line) { var add = true; this.Ignore.forEach((exp) => { @@ -390,24 +2137,48 @@ class Parser { add = false; } }); - if (add) { - if (typeof line === 'string' && line.trim().length) { - if (this.PSTricks.Expressions.psset.test(line)) { - this.parseUnits(line); - } - else { - this.environment.lines.push(line); - } + if (add && typeof line === 'string' && line.trim().length) { + if (this.PSTricks.Expressions.psset.test(line)) { + this.parseUnits(line); + } + else { + this.environment.lines.push(line); } } } + isIgnored(line) { + return this.Ignore.some((exp) => exp.test(line)); + } + isIgnoredEnv(name) { + return this.isIgnored('\\begin{' + name + '}'); + } + newEnvironment(type) { + if (this.environment && + (this.environment.lines.length || this.environment.type !== 'math')) { + this.environment.settings = { ...this.settings }; + this.objects.push(this.environment); + } + this.environment = { + type: type, + lines: [] + }; + } parseUnits(line) { - var m = line.match(this.PSTricks.Expressions.psset); + var m = line.replace(/\n/g, ' ').match(this.PSTricks.Expressions.psset); Object.assign(this.settings, this.PSTricks.Functions.psset.call(this, m)); } - metaData(environment, line) { + metaData(environment, envNode) { if (this.PSTricks.Expressions.hasOwnProperty(environment)) { - this.environment.match = line.match(this.PSTricks.Expressions[environment]); + this.environment.match = envNode.begin.raw + .replace(/\n/g, ' ') + .match(this.PSTricks.Expressions[environment]); + if (!this.environment.match) { + this.diagnose('error', `could not parse \\begin{${environment}} arguments`, envNode.begin.loc); + this.environment.env = {}; + this.environment.env.xunit = this.settings.xunit; + this.environment.env.yunit = this.settings.yunit; + return; + } this.environment.env = this.PSTricks.Functions[environment].call(this.settings, this.environment.match); if (environment.match(/pspicture/)) { if (typeof this.environment.env.xunit === 'undefined') { @@ -419,149 +2190,143 @@ class Parser { } } } - parseEnv(lines) { - this.objects = []; - this.environment = { - type: 'math', - lines: [] - }; - const Delimiters = this.Delimiters; - lines.forEach((line) => { - var isDelim = false; - Object.entries(Delimiters).forEach(([env, type]) => { - Object.entries(type).forEach(([k, delim]) => { - if (line.match(delim)) { - isDelim = true; - if (k.match(/begin/)) { - if (this.environment.type.match(/verbatim/)) { - isDelim = false; - } - else if (this.environment.type.match(/print/)) { - isDelim = false; - } - else { - this.newEnvironment(env); - this.metaData(env, line); - } - } - else if (k.match(/end/)) { - if (this.environment.type.match(/verbatim/)) { - if (env.match(/verbatim/)) { - this.newEnvironment('math'); - } - else { - isDelim = false; - } - } - else if (this.environment.type.match(/print/)) { - if (env.match(/print/)) { - this.newEnvironment('math'); - } - else { - isDelim = false; - } - } - else { - this.newEnvironment('math'); - } - } - } - }); - }); - if (!isDelim) - this.pushLine(line); // } + // ------------------------------------------------------------------------- + // PSTricks command extraction (ordered) + // ------------------------------------------------------------------------- + /** + * Extract plot data from the ordered command nodes of a pspicture. + * Returns the grouped `plot` map (keyed by command type, used by the + * interactive re-render paths) and records the ordered `elements` list on + * the env for source-order initial rendering. + */ + parsePSTricks(commands, env) { + var plot = {}; + const entries = Object.entries(this.PSTricks.Expressions); + entries.forEach(([k, _exp]) => { + plot[k] = []; }); - // push last! - this.newEnvironment('math'); + const elements = []; + this.extractCommands(commands, env, plot, elements); + env.elements = elements; + return plot; } - parseEnvText(lines) { - var _env = 'math'; - const Delimiters = this.Delimiters; - lines.forEach((line, i) => { - var isDelim = false; - Object.entries(Delimiters).forEach(([env, type]) => { - Object.entries(type).forEach(([k, delim]) => { - if (line.match(delim)) { - isDelim = true; - if (k.match(/begin/)) { - if (!_env.match(/verbatim/)) { - _env = env; - } - else { - isDelim = false; - } - } - else if (k.match(/end/)) { - if (!_env.match(/verbatim/)) { - _env = 'math'; - } - else { - if (!env.match(/verbatim/)) { - isDelim = false; - } - else { - _env = 'math'; - } - } - } - } - }); - }); - if (!isDelim) { - if (!_env.match(/verbatim/)) { - lines[i] = this.parseText(line); - } - if (!line.trim().length) { - lines[i] = '
    '; + /** + * Extract one command node into `plot` (grouped) and `elements` (ordered). + * Recurses into `\multido` bodies (expanded, counter substituted) and + * `\pscustom` bodies (the renderer re-parses those itself — the command is + * kept as a single element with its raw body). + */ + extractCommands(commands, env, plot, elements) { + commands.forEach((node) => { + const k = node.name; + const exp = this.PSTricks.Expressions[k]; + if (!exp) { + this.diagnose('warning', `unknown command \\${k} in pspicture`, node.loc); + return; + } + // The grammar captures commands across lines; the semantic regexes are + // single-line, so collapse internal newlines before matching. + const raw = node.raw.replace(/\n/g, ' '); + const m = raw.match(exp); + if (!m) { + this.diagnose('warning', `could not parse \\${k}: ${JSON.stringify(node.raw)}`, node.loc); + return; + } + const data = this.PSTricks.Functions[k].call(env, m); + // \multido{var=start+step}{count}{body} — expand and recurse. + if (k === 'multido') { + this.expandMultido(data, env, plot, elements, node); + return; + } + // \pscustom{...} — pre-extract the inner commands into pixel data so + // the renderer can build a single filled/stroked path. + if (k === 'pscustom' && data.body) { + data.commands = this.extractCustomBody(data.body, env); + } + plot[k].push({ data: data, env: env, match: m, fn: this.PSTricks.Functions[k] }); + elements.push({ name: k, data: data, match: m, fn: this.PSTricks.Functions[k], loc: node.loc }); + // side effects preserved from the old parser: + if (k === 'psaxes' && plot[k].length > 0) { + const axesData = plot[k][plot[k].length - 1].data; + if (axesData && axesData.dx !== undefined) { + env.dx = axesData.dx; + env.dy = axesData.dy; + env.origin = axesData.origin; } } + if (k === 'uservariable') { + env.variables = env.variables || {}; + env.variables[data.name] = data.value; + } }); } - parsePSExpression(line, exp, plot, k, env) { - var match = line.match(exp); - if (match) { - plot[k].push({ - data: this.PSTricks.Functions[k].call(env, match), - env: env, - match: match, - fn: this.PSTricks.Functions[k] + /** Expand a \multido loop into its constituent commands. */ + expandMultido(data, env, plot, elements, node) { + if (!data.variable || !(data.count > 0) || !data.body) + return; + const re = new RegExp('\\\\' + data.variable + '\\b', 'g'); + for (let i = 0; i < data.count; i++) { + const value = data.start + i * data.step; + const body = data.body.replace(re, String(value)); + this.commandNodesFrom(this.parseTree(body)).forEach((cmd) => { + this.extractCommands([cmd], env, plot, elements); }); - return true; } - return false; } - parsePSVariables(line, exp, _plot, k, env) { - var match = line.match(exp); - if (match) { - if (k.match(/uservariable/)) { - var dd = this.PSTricks.Functions[k].call(env, match); - env.variables = env.variables || {}; - env.variables[dd.name] = dd.value; + /** + * Extract the inner commands of a \pscustom body into pixel data for the + * renderer. Commands that need DOM/runtime handling (rput, slider, psset, + * nested pscustom, multido) are skipped. + */ + extractCustomBody(body, env) { + const out = []; + const skip = ['rput', 'slider', 'psset', 'pspicture', 'pscustom', 'multido', 'uservariable']; + this.commandNodesFrom(this.parseTree(body)).forEach((node) => { + const k = node.name; + if (skip.indexOf(k) !== -1) + return; + const exp = this.PSTricks.Expressions[k]; + if (!exp) + return; + const m = node.raw.replace(/\n/g, ' ').match(exp); + if (!m) + return; + try { + const data = this.PSTricks.Functions[k].call(env, m); + if (data) + out.push({ key: k, data: data }); + } + catch (err) { + /* ignore malformed inner commands */ } - } - } - parsePSTricks(lines, env) { - var plot = {}; - const entries = Object.entries(this.PSTricks.Expressions); - entries.forEach(([k, _exp]) => { - plot[k] = []; - }); - lines.forEach((line) => { - entries.forEach(([k, exp]) => { - this.parsePSVariables(line, exp, plot, k, env); - const result = this.parsePSExpression(line, exp, plot, k, env); - if (result && k === 'psaxes' && plot[k].length > 0) { - const axesData = plot[k][plot[k].length - 1].data; - if (axesData && axesData.dx !== undefined) { - env.dx = axesData.dx; - env.dy = axesData.dy; - env.origin = axesData.origin; - } - } - }); }); - return plot; + return out; } + /** + * Flatten parsed segments into an ordered list of command nodes, walking + * into line parts and nested environments. + */ + commandNodesFrom(segs) { + const out = []; + const walk = (seg) => { + if (seg.kind === 'command') + out.push(seg); + else if (seg.kind === 'line') { + (seg.parts || []).forEach((p) => { + if (p.kind === 'command') + out.push(p); + }); + } + else if (seg.kind === 'env') { + (seg.content || []).forEach(walk); + } + }; + segs.forEach(walk); + return out; + } + // ------------------------------------------------------------------------- + // Text / header transforms (reused from the old parser, string-based) + // ------------------------------------------------------------------------- parseTextExpression(line, exp, k, contents) { var match = line.match(exp); if (match) { @@ -588,10 +2353,21 @@ class Parser { }); return contents; } + // ------------------------------------------------------------------------- + // Diagnostics + // ------------------------------------------------------------------------- + diagnose(severity, message, loc) { + this.diagnostics.push({ + severity: severity, + message: message, + line: loc ? loc.line : undefined, + column: loc ? loc.column : undefined + }); + } } exports.default = Parser; -},{}],13:[function(require,module,exports){ +},{"../grammar/parser.js":9}],15:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Functions = exports.Expressions = void 0; @@ -616,6 +2392,24 @@ exports.Expressions = { set: /\\set\{[^}]*\}/g, youtube: /\\youtube\{[^}]*\}/g, euler: /Euler\^/g, + textbf: /\\textbf\{[^}]*\}/g, + textit: /\\textit\{[^}]*\}/g, + texttt: /\\texttt\{[^}]*\}/g, + textrm: /\\textrm\{[^}]*\}/g, + textsc: /\\textsc\{[^}]*\}/g, + underline: /\\underline\{[^}]*\}/g, + overline: /\\overline\{[^}]*\}/g, + section: /\\section\{[^}]*\}/, + subsection: /\\subsection\{[^}]*\}/, + subsubsection: /\\subsubsection\{[^}]*\}/, + paragraph: /\\paragraph\{[^}]*\}/, + hspace: /\\hspace\{[^}]*\}/, + noindent: /\\noindent/g, + newpage: /\\newpage/g, + hrule: /\\hrule/g, + rule: /\\rule\{[^}]*\}\{[^}]*\}/g, + textcolor: /\\textcolor\{[^}]*\}\{[^}]*\}/g, + footnote: /\\footnote\{[^}]*\}/g, }; exports.Functions = { cite: function (m, contents) { @@ -678,13 +2472,65 @@ exports.Functions = { vspace: (0, utils_1.simplerepl)(/\\vspace/g, '
    '), TeX: (0, utils_1.simplerepl)(/\\TeX\\|\\TeX/g, '$\\TeX$'), LaTeX: (0, utils_1.simplerepl)(/\\LaTeX\\|\\LaTeX/g, '$\\LaTeX$'), + textbf: (0, utils_1.matchrepl)(/\\textbf\{([^}]*)\}/, function (m) { + return '' + m[1] + ''; + }), + textit: (0, utils_1.matchrepl)(/\\textit\{([^}]*)\}/, function (m) { + return '' + m[1] + ''; + }), + texttt: (0, utils_1.matchrepl)(/\\texttt\{([^}]*)\}/, function (m) { + return '' + m[1] + ''; + }), + textrm: (0, utils_1.matchrepl)(/\\textrm\{([^}]*)\}/, function (m) { + return '' + m[1] + ''; + }), + textsc: (0, utils_1.matchrepl)(/\\textsc\{([^}]*)\}/, function (m) { + return '' + m[1] + ''; + }), + underline: (0, utils_1.matchrepl)(/\\underline\{([^}]*)\}/, function (m) { + return '' + m[1] + ''; + }), + overline: (0, utils_1.matchrepl)(/\\overline\{([^}]*)\}/, function (m) { + return '' + m[1] + ''; + }), + section: (0, utils_1.matchrepl)(/\\section\{([^}]*)\}/, function (m) { + return '

    ' + m[1] + '

    '; + }), + subsection: (0, utils_1.matchrepl)(/\\subsection\{([^}]*)\}/, function (m) { + return '

    ' + m[1] + '

    '; + }), + subsubsection: (0, utils_1.matchrepl)(/\\subsubsection\{([^}]*)\}/, function (m) { + return '

    ' + m[1] + '

    '; + }), + paragraph: (0, utils_1.matchrepl)(/\\paragraph\{([^}]*)\}/, function (m) { + return '
    ' + m[1] + '
    '; + }), + hspace: (0, utils_1.matchrepl)(/\\hspace\{([^}]*)\}/, function (_m) { + return '  '; + }), + noindent: (0, utils_1.simplerepl)(/\\noindent/g, ''), + newpage: (0, utils_1.simplerepl)(/\\newpage/g, '

    '), + hrule: (0, utils_1.simplerepl)(/\\hrule/g, '
    '), + rule: (0, utils_1.matchrepl)(/\\rule\{([^}]*)\}\{([^}]*)\}/, function (m) { + return (''); + }), + textcolor: (0, utils_1.matchrepl)(/\\textcolor\{([^}]*)\}\{([^}]*)\}/, function (m) { + return '' + m[2] + ''; + }), + footnote: (0, utils_1.matchrepl)(/\\footnote\{([^}]*)\}/, function (m) { + return '' + m[1] + ''; + }), }; exports.default = { Expressions: exports.Expressions, Functions: exports.Functions, }; -},{"@latex2js/utils":20}],14:[function(require,module,exports){ +},{"@latex2js/utils":23}],16:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = String.raw ` @@ -827,7 +2673,7 @@ exports.default = String.raw ` $$ `; -},{}],15:[function(require,module,exports){ +},{}],17:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadMathJax = exports.getMathJax = exports.DEFAULT_CONFIG = void 0; @@ -896,7 +2742,7 @@ const loadMathJax = async (callback = () => { }, config = exports.DEFAULT_CONFIG }; exports.loadMathJax = loadMathJax; -},{}],16:[function(require,module,exports){ +},{}],18:[function(require,module,exports){ "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; @@ -947,7 +2793,7 @@ exports.default = { arrow: psgraph_1.arrow, }; -},{"./lib/psgraph":17,"./lib/pstricks":18}],17:[function(require,module,exports){ +},{"./lib/psgraph":19,"./lib/pstricks":20}],19:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.arrow = arrow; @@ -978,6 +2824,55 @@ function arrow(x1, y1, x2, y2) { context.push('Z'); return context.join(' '); } +/** + * Catmull-Rom → cubic Bézier path for a flat [x0,y0,x1,y1,...] point list. + * `closed` wraps the curve back to the start point. + */ +function buildCurvePath(data, closed) { + const pts = []; + for (let i = 0; i < data.length; i += 2) + pts.push([data[i], data[i + 1]]); + const n = pts.length; + if (n < 2) + return ''; + const at = (i) => pts[((i % n) + n) % n]; + let d = 'M ' + pts[0][0] + ' ' + pts[0][1]; + for (let i = 0; i < n - 1; i++) { + const p0 = closed ? at(i - 1) : i === 0 ? pts[0] : pts[i - 1]; + const p1 = pts[i]; + const p2 = pts[i + 1]; + const p3 = closed ? at(i + 2) : i + 2 < n ? pts[i + 2] : pts[i + 1]; + const c1x = p1[0] + (p2[0] - p0[0]) / 6; + const c1y = p1[1] + (p2[1] - p0[1]) / 6; + const c2x = p2[0] - (p3[0] - p1[0]) / 6; + const c2y = p2[1] - (p3[1] - p1[1]) / 6; + d += ' C ' + c1x + ' ' + c1y + ', ' + c2x + ' ' + c2y + ', ' + p2[0] + ' ' + p2[1]; + } + if (closed) { + const pn1 = pts[n - 1]; + const p0 = pts[0]; + const pn2 = pts[n - 2]; + const p1 = pts[1]; + const c1x = pn1[0] + (p0[0] - pn2[0]) / 6; + const c1y = pn1[1] + (p0[1] - pn2[1]) / 6; + const c2x = p0[0] - (p1[0] - pn1[0]) / 6; + const c2y = p0[1] - (p1[1] - pn1[1]) / 6; + d += ' C ' + c1x + ' ' + c1y + ', ' + c2x + ' ' + c2y + ', ' + p0[0] + ' ' + p0[1] + ' Z'; + } + return d; +} +function curveRenderer(svg) { + const d = buildCurvePath(this.data, !!this.closed); + if (!d) + return; + svg + .append('svg:path') + .attr('d', d) + .style('stroke-width', this.linewidth) + .style('stroke', this.linecolor) + .style('stroke-opacity', 1) + .style('fill', this.fillstyle === 'solid' || this.filled ? this.fillcolor : 'none'); +} const psgraph = { env: null, getSize() { @@ -996,6 +2891,17 @@ const psgraph = { }; }, psframe(svg) { + const filled = this.filled || this.fillstyle === 'solid'; + if (filled) { + svg + .append('svg:rect') + .attr('x', Math.min(this.x1, this.x2)) + .attr('y', Math.min(this.y1, this.y2)) + .attr('width', Math.abs(this.x2 - this.x1)) + .attr('height', Math.abs(this.y2 - this.y1)) + .style('fill', this.fillcolor) + .style('stroke', 'none'); + } svg .append('svg:line') .attr('x1', this.x1) @@ -1034,14 +2940,15 @@ const psgraph = { .style('stroke-opacity', 1); }, pscircle: function (svg) { + const filled = this.filled || this.fillstyle === 'solid'; svg .append('svg:circle') .attr('cx', this.cx) .attr('cy', this.cy) .attr('r', this.r) - .style('stroke', 'black') - .style('fill', 'none') - .style('stroke-width', 2) + .style('stroke', this.linecolor) + .style('fill', filled ? this.fillcolor : 'none') + .style('stroke-width', this.linewidth) .style('stroke-opacity', 1); }, psplot(svg) { @@ -1088,32 +2995,28 @@ const psgraph = { .attr('d', context.join(' ')) .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'none' ? 'none' : this.fillcolor) + .style('fill', this.fillstyle === 'none' && !this.filled ? 'none' : this.fillcolor) .style('stroke', 'black'); }, psarc(svg) { - var context = []; - context.push('M'); - context.push(this.cx); - context.push(this.cy); - context.push('L'); - context.push(this.A.x); - context.push(this.A.y); - context.push('A'); - context.push(this.A.x); - context.push(this.A.y); - context.push(0); - context.push(0); - context.push(0); - context.push(this.B.x); - context.push(this.B.y); + const sweep = this.angleB - this.angleA > 0 ? 1 : 0; + const large = Math.abs(this.angleB - this.angleA) > Math.PI ? 1 : 0; + const filled = this.filled || this.fillstyle === 'solid'; + const d = filled + ? 'M ' + this.cx + ' ' + this.cy + + ' L ' + this.A.x + ' ' + this.A.y + + ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + + ' ' + this.B.x + ' ' + this.B.y + ' Z' + : 'M ' + this.A.x + ' ' + this.A.y + + ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + + ' ' + this.B.x + ' ' + this.B.y; svg .append('svg:path') - .attr('d', context.join(' ')) - .style('stroke-width', 2) + .attr('d', d) + .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', 'blue') - .style('stroke', 'black'); + .style('fill', filled ? this.fillcolor : 'none') + .style('stroke', this.linecolor); }, psaxes(svg) { var xaxis = [this.bottomLeft[0], this.topRight[0]]; @@ -1458,17 +3361,33 @@ const psgraph = { pspicture(svg) { var env = this.env; var el = this.$el; - Object.keys(this.plot).forEach((key) => { - const plot = this.plot[key]; - if (key.match(/rput/)) - return; - if (psgraph.hasOwnProperty(key)) { - plot.forEach((data) => { - data.data.global = env; - psgraph[key].call(data.data, svg); - }); - } - }); + // Source-order initial draw: the parser records `env.elements` in + // document order, so layers (fills under lines, etc.) respect the author's + // order. Falls back to the old type-grouped iteration for legacy data. + const elements = env && env.elements; + if (elements && elements.length) { + elements.forEach((item) => { + if (!item || !item.name || item.name.match(/rput/)) + return; + if (!psgraph.hasOwnProperty(item.name)) + return; + item.data.global = env; + psgraph[item.name].call(item.data, svg); + }); + } + else { + Object.keys(this.plot).forEach((key) => { + const plot = this.plot[key]; + if (key.match(/rput/)) + return; + if (psgraph.hasOwnProperty(key)) { + plot.forEach((data) => { + data.data.global = env; + psgraph[key].call(data.data, svg); + }); + } + }); + } svg.on('touchmove', function (event) { event.preventDefault(); var touch = event.touches ? event.touches[0] : null; @@ -1542,6 +3461,145 @@ const psgraph = { // Enhanced cleanup and RPUT processing psgraph.processRputElements.call(this, el); }, + psdots(svg) { + for (let i = 0; i < this.data.length; i += 2) { + svg + .append('svg:circle') + .attr('cx', this.data[i]) + .attr('cy', this.data[i + 1]) + .attr('r', this.dotsize) + .style('fill', this.linecolor) + .style('stroke', 'none'); + } + }, + psgrid(svg) { + const x0 = this.x0, y0 = this.y0, x1 = this.x1, y1 = this.y1; + for (let x = x0; x <= x1 + 0.001; x += this.xunit) { + svg + .append('svg:line') + .attr('x1', x).attr('y1', y0) + .attr('x2', x).attr('y2', y1) + .style('stroke', this.linecolor) + .style('stroke-width', this.gridwidth) + .style('stroke-opacity', 1); + } + for (let y = y0; y <= y1 + 0.001; y += this.yunit) { + svg + .append('svg:line') + .attr('x1', x0).attr('y1', y) + .attr('x2', x1).attr('y2', y) + .style('stroke', this.linecolor) + .style('stroke-width', this.gridwidth) + .style('stroke-opacity', 1); + } + }, + psellipse(svg) { + svg + .append('svg:ellipse') + .attr('cx', this.cx) + .attr('cy', this.cy) + .attr('rx', this.rx) + .attr('ry', this.ry) + .style('stroke', this.linecolor) + .style('stroke-width', this.linewidth) + .style('stroke-opacity', 1) + .style('fill', this.fillstyle === 'solid' ? this.fillcolor : 'none'); + }, + psbezier(svg) { + svg + .append('svg:path') + .attr('d', 'M ' + this.x1 + ' ' + this.y1 + + ' C ' + this.x2 + ' ' + this.y2 + ', ' + this.x3 + ' ' + this.y3 + ', ' + this.x4 + ' ' + this.y4) + .style('stroke-width', this.linewidth) + .style('stroke', this.linecolor) + .style('stroke-opacity', 1) + .style('fill', 'none'); + }, + pscurve(svg) { + const d = buildCurvePath(this.data, !!this.closed); + if (!d) + return; + svg + .append('svg:path') + .attr('d', d) + .style('stroke-width', this.linewidth) + .style('stroke', this.linecolor) + .style('stroke-opacity', 1) + .style('fill', this.fillstyle === 'solid' || this.filled ? this.fillcolor : 'none'); + }, + psecurve: curveRenderer, + psccurve: curveRenderer, + pswedge(svg) { + const sweep = this.angleB - this.angleA > 0 ? 1 : 0; + const large = Math.abs(this.angleB - this.angleA) > Math.PI ? 1 : 0; + svg + .append('svg:path') + .attr('d', 'M ' + this.cx + ' ' + this.cy + + ' L ' + this.A.x + ' ' + this.A.y + + ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + + ' ' + this.B.x + ' ' + this.B.y + ' Z') + .style('stroke-width', this.linewidth) + .style('stroke', this.linecolor) + .style('stroke-opacity', 1) + .style('fill', this.fillstyle === 'solid' ? this.fillcolor : 'none'); + }, + pscustom(svg) { + const filled = this.filled || this.fillstyle === 'solid'; + let d = ''; + let started = false; + (this.commands || []).forEach((cmd) => { + const data = cmd.data; + if (!data) + return; + if (cmd.key === 'psline' || cmd.key === 'userline' || cmd.key === 'psbezier') { + if (cmd.key === 'psbezier') { + if (!started) { + d += 'M ' + data.x1 + ' ' + data.y1; + started = true; + } + d += ' C ' + data.x2 + ' ' + data.y2 + ', ' + data.x3 + ' ' + data.y3 + ', ' + data.x4 + ' ' + data.y4; + return; + } + if (!started) { + d += 'M ' + data.x1 + ' ' + data.y1; + started = true; + } + d += ' L ' + data.x2 + ' ' + data.y2; + } + else if (cmd.key === 'psframe') { + if (!started) { + d += 'M ' + data.x1 + ' ' + data.y1; + started = true; + } + d += ' L ' + data.x2 + ' ' + data.y1 + + ' L ' + data.x2 + ' ' + data.y2 + + ' L ' + data.x1 + ' ' + data.y2 + ' Z'; + } + else if (cmd.key === 'pspolygon' || cmd.key === 'pscurve') { + const pts = data.data || []; + if (pts.length < 2) + return; + if (!started) { + d += 'M ' + pts[0] + ' ' + pts[1]; + started = true; + } + for (let i = 2; i < pts.length; i += 2) + d += ' L ' + pts[i] + ' ' + pts[i + 1]; + d += ' Z'; + } + }); + if (!started) + return; + if (filled) + d += ' Z'; + svg + .append('svg:path') + .attr('d', d) + .style('stroke-width', this.linewidth) + .style('stroke', this.linestyle === 'none' ? 'none' : this.linecolor) + .style('stroke-opacity', 1) + .style('fill', filled ? this.fillcolor : 'none'); + }, processRputElements(el) { // Validate container if (!el || typeof el.querySelectorAll !== 'function') { @@ -1632,7 +3690,7 @@ const psgraph = { }; exports.default = psgraph; -},{"@latex2js/utils":20}],18:[function(require,module,exports){ +},{"@latex2js/utils":23}],20:[function(require,module,exports){ "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; @@ -1641,11 +3699,21 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.Functions = exports.Expressions = void 0; const utils_1 = require("@latex2js/utils"); const settings_1 = __importDefault(require("@latex2js/settings")); +/** + * Parse a PSTricks linewidth value: a bare number is used as-is (SVG px), + * a `pt` value is converted to px (1pt ≈ 1.333px). + */ +function parseLinewidth(value) { + const m = value.trim().match(/^([\d.]+)\s*(pt)?$/); + if (!m) + return 2; + return Number(m[1]) * (m[2] ? 1.333 : 1); +} exports.Expressions = { pspicture: /\\begin\{pspicture\}\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, - psframe: /\\psframe\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, - psplot: /\\psplot(\[[^\]]*\])?\{([^\}]*)\}\{([^\}]*)\}\{([^\}]*)\}/, - psarc: new RegExp('\\\\psarc' + + psframe: /\\psframe\*?(\[[^\]]*\])?\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, + psplot: /\\psplot\*?(\[[^\]]*\])?\{([^\}]*)\}\{([^\}]*)\}\{([^\}]*)\}/, + psarc: new RegExp('\\\\psarc\\*?' + utils_1.RE.options + utils_1.RE.type + utils_1.RE.coords + @@ -1653,8 +3721,8 @@ exports.Expressions = { utils_1.RE.squiggle + utils_1.RE.squiggle), pscircle: /\\pscircle.*\(\s*(.*),(.*)\s*\)\{(.*)\}/, - pspolygon: new RegExp('\\\\pspolygon' + utils_1.RE.options + '(.*)'), - psaxes: new RegExp('\\\\psaxes' + + pspolygon: new RegExp('\\\\pspolygon\\*?' + utils_1.RE.options + '(.*)'), + psaxes: new RegExp('\\\\psaxes\\*?' + utils_1.RE.options + utils_1.RE.type + utils_1.RE.coords + @@ -1667,7 +3735,7 @@ exports.Expressions = { utils_1.RE.squiggle + utils_1.RE.squiggle + utils_1.RE.squiggle), - psline: new RegExp('\\\\psline' + utils_1.RE.options + utils_1.RE.type + utils_1.RE.coords + utils_1.RE.coordsOpt), + psline: new RegExp('\\\\psline\\*?' + utils_1.RE.options + utils_1.RE.type + utils_1.RE.coords + utils_1.RE.coordsOpt), userline: new RegExp('\\\\userline' + utils_1.RE.options + utils_1.RE.type + @@ -1679,7 +3747,17 @@ exports.Expressions = { utils_1.RE.squiggleOpt), uservariable: new RegExp('\\\\uservariable' + utils_1.RE.options + utils_1.RE.squiggle + utils_1.RE.coords + utils_1.RE.squiggle), rput: /\\rput\((.*),(.*)\)\{(.*)\}/, - psset: /\\psset\{(.*)\}/ + psset: /\\psset\{(.*)\}/, + psdots: new RegExp('\\\\psdots' + utils_1.RE.options + '(.*)'), + psgrid: new RegExp('\\\\psgrid' + utils_1.RE.options + utils_1.RE.coordsOpt + utils_1.RE.coordsOpt + utils_1.RE.coordsOpt), + psellipse: /\\psellipse.*\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, + psbezier: /\\psbezier(\[[^\]]*\])?\((.*),(.*)\)\((.*),(.*)\)\((.*),(.*)\)\((.*),(.*)\)/, + pscurve: new RegExp('\\\\pscurve' + utils_1.RE.options + utils_1.RE.coords + '(.*)'), + psecurve: new RegExp('\\\\psecurve' + utils_1.RE.options + utils_1.RE.coords + '(.*)'), + psccurve: new RegExp('\\\\psccurve' + utils_1.RE.options + utils_1.RE.coords + '(.*)'), + pswedge: /\\pswedge(\[[^\]]*\])?\(\s*(.*),(.*)\s*\)\{(.*)\}\{(.*)\}\{(.*)\}/, + pscustom: /\\pscustom(\[[^\]]*\])?\{([\s\S]*)\}/, + multido: /\\multido\{([^}]*)\}\{([^}]*)\}\{([\s\S]*)\}/ }; exports.Functions = { slider(m) { @@ -1716,19 +3794,36 @@ exports.Functions = { }, psframe(m) { var obj = { - x1: utils_1.X.call(this, m[1]), - y1: utils_1.Y.call(this, m[2]), - x2: utils_1.X.call(this, m[3]), - y2: utils_1.Y.call(this, m[4]) + x1: utils_1.X.call(this, m[2]), + y1: utils_1.Y.call(this, m[3]), + x2: utils_1.X.call(this, m[4]), + y2: utils_1.Y.call(this, m[5]), + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'none', + fillcolor: 'black', + linewidth: 2, + filled: /\\psframe\*/.test(m[0]) }; + if (m[1]) + Object.assign(obj, (0, utils_1.parseOptions)(m[1])); return obj; }, pscircle(m) { var obj = { cx: utils_1.X.call(this, m[1]), cy: utils_1.Y.call(this, m[2]), - r: this.xunit * m[3] + r: this.xunit * m[3], + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'none', + fillcolor: 'black', + linewidth: 2, + filled: /\\pscircle\*/.test(m[0]) }; + var opts = m[0].match(/\[([^\]]*)\]/); + if (opts) + Object.assign(obj, (0, utils_1.parseOptions)(opts[1])); return obj; }, psaxes(m) { @@ -1789,29 +3884,6 @@ exports.Functions = { var endX = utils_1.evaluate.call(this, m[3]); var data = []; var x; - // get env - var expression = ''; - Object.entries(this.variables || {}).forEach(([name, val]) => { - expression += 'var ' + name + ' = ' + val + ';'; - }); - const mathFunctions = 'var cos=Math.cos,sin=Math.sin,tan=Math.tan,atan=Math.atan,atan2=Math.atan2,exp=Math.exp,log=Math.log,sqrt=Math.sqrt,abs=Math.abs,floor=Math.floor,ceil=Math.ceil,round=Math.round,pow=Math.pow,PI=Math.PI,E=Math.E;'; - expression += mathFunctions + 'return ' + m[4] + ';'; - for (x = startX; x <= endX; x += 0.005) { - data.push(utils_1.X.call(this, x)); - try { - const evalFunc = new Function('x', expression); - const yValue = evalFunc(x); - if (yValue !== undefined && !isNaN(yValue)) { - data.push(utils_1.Y.call(this, yValue)); - } - else { - data.push(utils_1.Y.call(this, 0)); - } - } - catch (err) { - data.push(utils_1.Y.call(this, 0)); // fallback value - } - } var obj = { linecolor: 'black', linestyle: 'solid', @@ -1821,6 +3893,36 @@ exports.Functions = { }; if (m[1]) Object.assign(obj, (0, utils_1.parseOptions)(m[1])); + // Sampling: honor `plotpoints=N` (number of samples); default to a + // fixed 0.005 step like the original implementation. + var step = 0.005; + var plotpoints = obj.plotpoints ? Number(obj.plotpoints) : 0; + if (plotpoints > 1) { + step = (endX - startX) / (plotpoints - 1); + } + // Compile the plot expression once; evaluate per sample against a + // reused scope (compile-once / evaluate-many). + let compiled; + try { + compiled = (0, utils_1.parseExpression)(m[4]); + } + catch (err) { + console.warn('psplot: could not parse expression:', err.message); + obj.data = data; + return obj; + } + const scope = Object.assign({}, this.variables || {}); + for (x = startX; x <= endX + step / 2; x += step) { + data.push(utils_1.X.call(this, x)); + scope.x = x; + const yValue = compiled.evaluate(scope); + if (yValue !== undefined && !isNaN(yValue)) { + data.push(utils_1.Y.call(this, yValue)); + } + else { + data.push(utils_1.Y.call(this, 0)); + } + } obj.data = data; return obj; }, @@ -1845,6 +3947,7 @@ exports.Functions = { fillstyle: 'none', fillcolor: 'black', linewidth: 2, + filled: /\\pspolygon\*/.test(m[0]), data: data }; if (m[1]) @@ -1863,6 +3966,7 @@ exports.Functions = { linewidth: 2, arrows: arrows, dots: dots, + filled: /\\psarc\*/.test(m[0]), cx: utils_1.X.call(this, 0), cy: utils_1.Y.call(this, 0) }; @@ -1909,7 +4013,8 @@ exports.Functions = { fillcolor: 'black', linewidth: 2, arrows: arrows, - dots: dots + dots: dots, + filled: /\\psline\*/.test(m[0]) }; if (m[5]) { obj.x1 = utils_1.X.call(this, m[3]); @@ -1928,7 +4033,7 @@ exports.Functions = { } // TODO: add regex if (typeof obj.linewidth === 'string') { - obj.linewidth = 2; + obj.linewidth = parseLinewidth(obj.linewidth); } return obj; }, @@ -1946,26 +4051,19 @@ exports.Functions = { } var nx1 = utils_1.Xinv.call(this, coords[0]); var ny1 = utils_1.Yinv.call(this, coords[1]); - var expx1 = 'var x = ' + nx1 + ';'; - var expy1 = 'var y = ' + ny1 + ';'; - // return X.call(this, eval(expy1 + expx1 + xExp)); var obj = { name: m[2], x: utils_1.X.call(this, m[3]), y: utils_1.Y.call(this, m[4]), func: m[5], - value: (() => { - try { - const mathFunctions = 'var cos=Math.cos,sin=Math.sin,tan=Math.tan,atan=Math.atan,atan2=Math.atan2,exp=Math.exp,log=Math.log,sqrt=Math.sqrt,abs=Math.abs,floor=Math.floor,ceil=Math.ceil,round=Math.round,pow=Math.pow,PI=Math.PI,E=Math.E;'; - const evalFunc = new Function('', mathFunctions + expx1 + expy1 + 'return ' + m[5]); - return evalFunc(); - } - catch (err) { - console.warn('Error evaluating uservariable expression:', err); - return 0; - } - })() + value: 0 }; + try { + obj.value = (0, utils_1.parseExpression)(m[5]).evaluate(Object.assign({ x: nx1, y: ny1 }, this.variables || {})); + } + catch (err) { + console.warn('Error evaluating uservariable expression:', err.message); + } return obj; }, userline(m) { @@ -1975,41 +4073,40 @@ exports.Functions = { var l = (0, utils_1.parseArrows)(lineType); var arrows = l.arrows; var dots = l.dots; - var xExp = m[7]; - var yExp = m[8]; - const mathFunctions = 'var cos=Math.cos,sin=Math.sin,tan=Math.tan,atan=Math.atan,atan2=Math.atan2,exp=Math.exp,log=Math.log,sqrt=Math.sqrt,abs=Math.abs,floor=Math.floor,ceil=Math.ceil,round=Math.round,pow=Math.pow,PI=Math.PI,E=Math.E;'; - if (xExp) - xExp = mathFunctions + xExp.replace(/^\{/, '').replace(/\}$/, ''); - if (yExp) - yExp = mathFunctions + yExp.replace(/^\{/, '').replace(/\}$/, ''); - var xExp2 = m[9]; - var yExp2 = m[10]; - if (xExp2) - xExp2 = mathFunctions + xExp2.replace(/^\{/, '').replace(/\}$/, ''); - if (yExp2) - yExp2 = mathFunctions + yExp2.replace(/^\{/, '').replace(/\}$/, ''); - var expression = ''; - Object.entries(this.variables || {}).forEach(([name, val]) => { - expression += 'var ' + name + ' = ' + val + ';'; - }); + // Compile the interactive head/tail expressions once; each mousemove just + // re-evaluates them against a fresh {x, y} scope (compile-once). + const stripBraces = (s) => (s ? s.replace(/^\{/, '').replace(/\}$/, '').trim() : null); + const compileOpt = (src) => { + if (!src) + return null; + try { + return (0, utils_1.parseExpression)(src); + } + catch (err) { + console.warn('userline: could not parse expression:', err.message); + return null; + } + }; + const xExp = compileOpt(stripBraces(m[7])); + const yExp = compileOpt(stripBraces(m[8])); + const xExp2 = compileOpt(stripBraces(m[9])); + const yExp2 = compileOpt(stripBraces(m[10])); + const variables = this.variables || {}; + const evalAt = (compiled, x, y) => compiled.evaluate(Object.assign({ x: x, y: y }, variables)); var obj = { x1: utils_1.X.call(this, m[3]), y1: utils_1.Y.call(this, m[4]), x2: utils_1.X.call(this, m[5]), y2: utils_1.Y.call(this, m[6]), - xExp: xExp, - yExp: yExp, - xExp2: xExp2, - yExp2: yExp2, + xExp: m[7], + yExp: m[8], + xExp2: m[9], + yExp2: m[10], userx: (coords) => { var nx1 = utils_1.Xinv.call(this, coords[0]); var ny1 = utils_1.Yinv.call(this, coords[1]); - var expx1 = 'var x = ' + nx1 + ';'; - var expy1 = 'var y = ' + ny1 + ';'; try { - const cleanExp = xExp ? xExp.replace(/^var cos=Math\.cos[^;]*;/, '') : '0'; - const evalFunc = new Function('', mathFunctions + expression + expy1 + expx1 + 'return (' + cleanExp + ')'); - return utils_1.X.call(this, evalFunc()); + return utils_1.X.call(this, xExp ? evalAt(xExp, nx1, ny1) : 0); } catch (err) { console.warn('Error evaluating userx expression:', err); @@ -2019,12 +4116,8 @@ exports.Functions = { usery: (coords) => { var nx2 = utils_1.Xinv.call(this, coords[0]); var ny2 = utils_1.Yinv.call(this, coords[1]); - var expx2 = 'var x = ' + nx2 + ';'; - var expy2 = 'var y = ' + ny2 + ';'; try { - const cleanExp = yExp ? yExp.replace(/^var cos=Math\.cos[^;]*;/, '') : '0'; - const evalFunc = new Function('', mathFunctions + expression + expy2 + expx2 + 'return (' + cleanExp + ')'); - return utils_1.Y.call(this, evalFunc()); + return utils_1.Y.call(this, yExp ? evalAt(yExp, nx2, ny2) : 0); } catch (err) { console.warn('Error evaluating usery expression:', err); @@ -2034,12 +4127,8 @@ exports.Functions = { userx2: (coords) => { var nx3 = utils_1.Xinv.call(this, coords[0]); var ny3 = utils_1.Yinv.call(this, coords[1]); - var expx3 = 'var x = ' + nx3 + ';'; - var expy3 = 'var y = ' + ny3 + ';'; try { - const cleanExp = xExp2 ? xExp2.replace(/^var cos=Math\.cos[^;]*;/, '') : '0'; - const evalFunc = new Function('', mathFunctions + expression + expy3 + expx3 + 'return (' + cleanExp + ')'); - return utils_1.X.call(this, evalFunc()); + return utils_1.X.call(this, xExp2 ? evalAt(xExp2, nx3, ny3) : 0); } catch (err) { console.warn('Error evaluating userx2 expression:', err); @@ -2049,12 +4138,8 @@ exports.Functions = { usery2: (coords) => { var nx4 = utils_1.Xinv.call(this, coords[0]); var ny4 = utils_1.Yinv.call(this, coords[1]); - var expx4 = 'var x = ' + nx4 + ';'; - var expy4 = 'var y = ' + ny4 + ';'; try { - const cleanExp = yExp2 ? yExp2.replace(/^var cos=Math\.cos[^;]*;/, '') : '0'; - const evalFunc = new Function('', mathFunctions + expression + expy4 + expx4 + 'return (' + cleanExp + ')'); - return utils_1.Y.call(this, evalFunc()); + return utils_1.Y.call(this, yExp2 ? evalAt(yExp2, nx4, ny4) : 0); } catch (err) { console.warn('Error evaluating usery2 expression:', err); @@ -2074,7 +4159,7 @@ exports.Functions = { } // TODO: add regex if (typeof obj.linewidth === 'string') { - obj.linewidth = 2; + obj.linewidth = parseLinewidth(obj.linewidth); } return obj; }, @@ -2099,14 +4184,169 @@ exports.Functions = { }); }); return obj; + }, + psdots(m) { + var obj = { + linecolor: 'black', + dotstyle: 'dot', + dotsize: 2, + data: parseCoordList.call(this, m[2]) + }; + if (m[1]) + Object.assign(obj, (0, utils_1.parseOptions)(m[1])); + return obj; + }, + psgrid(m) { + var obj = { + linecolor: 'black', + linestyle: 'solid', + linewidth: 0.5, + gridwidth: 0.5 + }; + if (m[1]) + Object.assign(obj, (0, utils_1.parseOptions)(m[1])); + // \psgrid[opts](x0,y0)(x1,y1) — defaults to the whole pspicture bounds. + // coordsOpt outer groups: m[2]/m[5]/m[8] = '(x,y)' strings, m[3],m[4] etc. + var has0 = m[3] !== undefined; + var has1 = m[6] !== undefined; + var x0 = has0 ? utils_1.X.call(this, m[3]) : utils_1.X.call(this, this.x0); + var y0 = has0 ? utils_1.Y.call(this, m[4]) : utils_1.Y.call(this, this.y0); + var x1 = has1 ? utils_1.X.call(this, m[6]) : utils_1.X.call(this, this.x1); + var y1 = has1 ? utils_1.Y.call(this, m[7]) : utils_1.Y.call(this, this.y1); + obj.x0 = Math.min(x0, x1); + obj.y0 = Math.min(y0, y1); + obj.x1 = Math.max(x0, x1); + obj.y1 = Math.max(y0, y1); + obj.xunit = this.xunit; + obj.yunit = this.yunit; + return obj; + }, + psellipse(m) { + var obj = { + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'none', + fillcolor: 'black', + linewidth: 2 + }; + var opts = m[0].match(/\[([^\]]*)\]/); + if (opts) + Object.assign(obj, (0, utils_1.parseOptions)(opts[1])); + obj.cx = utils_1.X.call(this, m[1]); + obj.cy = utils_1.Y.call(this, m[2]); + obj.rx = Math.abs(Number(m[3])) * this.xunit; + obj.ry = Math.abs(Number(m[4])) * this.yunit; + return obj; + }, + psbezier(m) { + var obj = { + linecolor: 'black', + linestyle: 'solid', + linewidth: 2 + }; + if (m[1]) + Object.assign(obj, (0, utils_1.parseOptions)(m[1])); + obj.x1 = utils_1.X.call(this, m[2]); + obj.y1 = utils_1.Y.call(this, m[3]); + obj.x2 = utils_1.X.call(this, m[4]); + obj.y2 = utils_1.Y.call(this, m[5]); + obj.x3 = utils_1.X.call(this, m[6]); + obj.y3 = utils_1.Y.call(this, m[7]); + obj.x4 = utils_1.X.call(this, m[8]); + obj.y4 = utils_1.Y.call(this, m[9]); + return obj; + }, + pscurve(m) { + var obj = { + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'none', + fillcolor: 'black', + linewidth: 2, + closed: /\\psecurve|\\psccurve/.test(m[0]) + }; + if (m[1]) + Object.assign(obj, (0, utils_1.parseOptions)(m[1])); + // first point is captured separately (m[2], m[3]); the rest follow + obj.data = [utils_1.X.call(this, m[2]), utils_1.Y.call(this, m[3])].concat(parseCoordList.call(this, m[4] || '')); + return obj; + }, + psecurve(m) { + return exports.Functions.pscurve.call(this, m); + }, + psccurve(m) { + return exports.Functions.pscurve.call(this, m); + }, + pswedge(m) { + var obj = { + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'solid', + fillcolor: 'black', + linewidth: 2 + }; + if (m[1]) + Object.assign(obj, (0, utils_1.parseOptions)(m[1])); + obj.cx = utils_1.X.call(this, m[2]); + obj.cy = utils_1.Y.call(this, m[3]); + obj.r = Number(m[4]) * this.xunit; + obj.angleA = (Number(m[5]) * Math.PI) / 180; + obj.angleB = (Number(m[6]) * Math.PI) / 180; + obj.A = { + x: utils_1.X.call(this, Number(m[4]) * Math.cos(obj.angleA)), + y: utils_1.Y.call(this, Number(m[4]) * Math.sin(obj.angleA)) + }; + obj.B = { + x: utils_1.X.call(this, Number(m[4]) * Math.cos(obj.angleB)), + y: utils_1.Y.call(this, Number(m[4]) * Math.sin(obj.angleB)) + }; + return obj; + }, + pscustom(m) { + var obj = { + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'none', + fillcolor: 'black', + linewidth: 2, + body: m[2] + }; + if (m[1]) + Object.assign(obj, (0, utils_1.parseOptions)(m[1])); + return obj; + }, + multido(m) { + var spec = m[1] || ''; + var varMatch = spec.match(/\\([a-zA-Z@]+)\s*=\s*([\d.+-]+)\s*\+\s*([\d.+-]+)/); + return { + variable: varMatch ? varMatch[1] : null, + start: varMatch ? Number(varMatch[2]) : 0, + step: varMatch ? Number(varMatch[3]) : 1, + count: Number(m[2]), + body: m[3] + }; } }; +/** + * Parse a coordinate list like `(0,0)(1,1)(2,2)` into a flat + * [x0,y0,x1,y1,...] pixel array. + */ +function parseCoordList(coords) { + var data = []; + var re = new RegExp(utils_1.RE.coords, 'g'); + var m; + while ((m = re.exec(coords)) !== null) { + data.push(utils_1.X.call(this, m[1])); + data.push(utils_1.Y.call(this, m[2])); + } + return data; +} exports.default = { Expressions: exports.Expressions, Functions: exports.Functions }; -},{"@latex2js/settings":19,"@latex2js/utils":20}],19:[function(require,module,exports){ +},{"@latex2js/settings":21,"@latex2js/utils":23}],21:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Functions = exports.Expressions = void 0; @@ -2159,10 +4399,334 @@ exports.default = { Functions: exports.Functions }; -},{"@latex2js/utils":20}],20:[function(require,module,exports){ +},{"@latex2js/utils":23}],22:[function(require,module,exports){ +"use strict"; +/** + * Algebraic expression parser + evaluator for PSTricks-style math. + * + * PSTricks `algebraic` expressions are NOT JavaScript: they use `^` for + * power, allow implicit multiplication (`2x`, `2(x+1)`, `2sin(x)`), and rely + * on bare math function names (`cos(x)`). This module parses an expression + * once into an AST and compiles it to a JavaScript closure that can be + * evaluated cheaply many times with a variable scope — exactly the + * compile-once / evaluate-many pattern the interactive plot and userline + * paths need. + * + * Supported syntax: + * numbers, identifiers (variables), arithmetic + - * / ^, + * unary minus/plus, implicit multiplication, parentheses, + * function calls (cos, sin, tan, atan, atan2, pow, sqrt, abs, exp, ln, + * log, floor, ceil, round, min, max, ...), comparisons (< > <= >= == !=), + * and ternary conditionals (cond ? a : b). + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MATH_CONSTANTS = exports.MATH_FUNCTIONS = exports.ExpressionError = void 0; +exports.parseExpression = parseExpression; +class ExpressionError extends Error { + constructor(message, position) { + // position is a 0-based offset; compute 1-based line/column lazily + super(message); + this.name = 'ExpressionError'; + this.position = position; + this.line = 0; + this.column = 0; + } +} +exports.ExpressionError = ExpressionError; +const OPS = ['<=', '>=', '==', '!=', '<', '>', '?', ':', '+', '-', '*', '/', '^', ',']; +const PARENS = new Set(['(', ')']); +function tokenize(source) { + const tokens = []; + let i = 0; + const n = source.length; + const numberRe = /^\d*\.?\d+(?:[eE][+-]?\d+)?/; + const identRe = /^[a-zA-Z_][a-zA-Z0-9_]*/; + while (i < n) { + const ch = source[i]; + if (/\s/.test(ch)) { + i++; + continue; + } + // Unicode pi + if (ch === 'π') { + tokens.push({ type: 'ident', value: 'π', pos: i }); + i++; + continue; + } + if (ch === '(' || ch === ')') { + tokens.push({ type: 'paren', value: ch, pos: i }); + i++; + continue; + } + const num = source.slice(i).match(numberRe); + if (num) { + tokens.push({ type: 'number', value: num[0], pos: i }); + i += num[0].length; + continue; + } + const ident = source.slice(i).match(identRe); + if (ident) { + tokens.push({ type: 'ident', value: ident[0], pos: i }); + i += ident[0].length; + continue; + } + const op = OPS.find((o) => source.startsWith(o, i)); + if (op) { + tokens.push({ type: op === '(' || op === ')' ? 'paren' : 'op', value: op, pos: i }); + i += op.length; + continue; + } + throw new ExpressionError(`unexpected character '${ch}'`, i); + } + tokens.push({ type: 'eof', value: '', pos: n }); + return tokens; +} +class Parser { + constructor(source) { + this.source = source; + this.index = 0; + this.tokens = tokenize(source); + if (this.tokens.length <= 1) { + throw new ExpressionError('empty expression', 0); + } + } + peek() { + return this.tokens[this.index]; + } + next() { + return this.tokens[this.index++]; + } + expect(value) { + const t = this.peek(); + if (t.value !== value) { + throw new ExpressionError(`expected '${value}' but found '${t.value || 'end of input'}'`, t.pos); + } + return this.next(); + } + parse() { + const node = this.parseTernary(); + const t = this.peek(); + if (t.type !== 'eof') { + throw new ExpressionError(`unexpected '${t.value}'`, t.pos); + } + return node; + } + parseTernary() { + const cond = this.parseComparison(); + if (this.peek().value === '?') { + this.next(); + const then = this.parseTernary(); + this.expect(':'); + const els = this.parseTernary(); + return { type: 'ternary', cond, then, els }; + } + return cond; + } + parseComparison() { + let left = this.parseAdditive(); + for (;;) { + const op = this.peek().value; + if (op === '<' || op === '>' || op === '<=' || op === '>=' || op === '==' || op === '!=') { + this.next(); + const right = this.parseAdditive(); + left = { type: 'binary', op, left, right }; + } + else { + return left; + } + } + } + parseAdditive() { + let left = this.parseMultiplicative(); + for (;;) { + const op = this.peek().value; + if (op === '+' || op === '-') { + this.next(); + const right = this.parseMultiplicative(); + left = { type: 'binary', op, left, right }; + } + else { + return left; + } + } + } + parseMultiplicative() { + let left = this.parseUnary(); + for (;;) { + const op = this.peek().value; + if (op === '*' || op === '/') { + this.next(); + const right = this.parseUnary(); + left = { type: 'binary', op, left, right }; + } + else if (this.isImplicitStart(this.peek())) { + // implicit multiplication: 2x, 2(x+1), (x+1)(x+2), 2sin(x) + const right = this.parseUnary(); + left = { type: 'binary', op: '*', left, right }; + } + else { + return left; + } + } + } + parseUnary() { + const op = this.peek().value; + if (op === '-' || op === '+') { + this.next(); + return { type: 'unary', op, operand: this.parseUnary() }; + } + return this.parsePower(); + } + parsePower() { + const left = this.parsePrimary(); + if (this.peek().value === '^') { + this.next(); + const right = this.parseUnary(); // right-associative, binds tighter on the right + return { type: 'binary', op: '^', left, right }; + } + return left; + } + parsePrimary() { + const t = this.peek(); + if (t.type === 'number') { + this.next(); + return { type: 'number', value: t.value }; + } + if (t.type === 'ident') { + this.next(); + // a known math function followed by '(' is a function call + if (this.peek().value === '(' && exports.MATH_FUNCTIONS.hasOwnProperty(t.value)) { + this.next(); // consume '(' + const args = []; + if (this.peek().value !== ')') { + args.push(this.parseTernary()); + while (this.peek().value === ',') { + this.next(); + args.push(this.parseTernary()); + } + } + this.expect(')'); + return { type: 'call', name: t.value, args }; + } + return { type: 'var', name: t.value }; + } + if (t.value === '(') { + this.next(); + const node = this.parseTernary(); + this.expect(')'); + return node; + } + throw new ExpressionError(`unexpected '${t.value || 'end of input'}' in expression`, t.pos); + } + /** A token that can start an implicit multiplication operand. */ + isImplicitStart(t) { + return t.type === 'number' || t.type === 'ident' || t.value === '('; + } +} +// --------------------------------------------------------------------------- +// Compile AST → JS closure +// --------------------------------------------------------------------------- +exports.MATH_FUNCTIONS = { + cos: 'Math.cos', + sin: 'Math.sin', + tan: 'Math.tan', + atan: 'Math.atan', + atan2: 'Math.atan2', + asin: 'Math.asin', + acos: 'Math.acos', + exp: 'Math.exp', + ln: 'Math.log', + log: 'Math.log', + log10: 'Math.log10', + sqrt: 'Math.sqrt', + cbrt: 'Math.cbrt', + abs: 'Math.abs', + sign: 'Math.sign', + floor: 'Math.floor', + ceil: 'Math.ceil', + round: 'Math.round', + pow: 'Math.pow', + min: 'Math.min', + max: 'Math.max', + sinh: 'Math.sinh', + cosh: 'Math.cosh', + tanh: 'Math.tanh', +}; +exports.MATH_CONSTANTS = { + pi: 'Math.PI', + π: 'Math.PI', + PI: 'Math.PI', + E: 'Math.E', +}; +function compileNode(node, variableNames) { + switch (node.type) { + case 'number': + return node.value; + case 'var': { + if (exports.MATH_CONSTANTS.hasOwnProperty(node.name)) { + return exports.MATH_CONSTANTS[node.name]; + } + variableNames.add(node.name); + return 'v.' + node.name; + } + case 'call': { + const target = exports.MATH_FUNCTIONS.hasOwnProperty(node.name) + ? exports.MATH_FUNCTIONS[node.name] + : '(v.' + node.name + ')'; + return target + '(' + node.args.map((a) => compileNode(a, variableNames)).join(',') + ')'; + } + case 'unary': + return '(' + node.op + compileNode(node.operand, variableNames) + ')'; + case 'binary': { + const op = node.op === '^' ? '**' : node.op; + return '(' + compileNode(node.left, variableNames) + op + compileNode(node.right, variableNames) + ')'; + } + case 'ternary': + return ('(' + + compileNode(node.cond, variableNames) + + '?' + + compileNode(node.then, variableNames) + + ':' + + compileNode(node.els, variableNames) + + ')'); + default: + throw new Error('unknown node type ' + node.type); + } +} +/** + * Parse an algebraic expression and compile it to an evaluable closure. + * Throws ExpressionError with a character position on invalid syntax. + */ +function parseExpression(source) { + const trimmed = source.trim(); + if (!trimmed) { + throw new ExpressionError('empty expression', 0); + } + const parser = new Parser(trimmed); + const ast = parser.parse(); + const variableNames = new Set(); + const js = compileNode(ast, variableNames); + let fn; + try { + // eslint-disable-next-line no-new-func + fn = new Function('v', 'return (' + js + ');'); + } + catch (err) { + throw new ExpressionError('could not compile expression: ' + err.message, 0); + } + return { + source: trimmed, + toJS: () => js, + variables: () => Array.from(variableNames), + evaluate: (scope) => fn(scope || {}), + }; +} + +},{}],23:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.select = exports.SVGSelection = exports.dotType = exports.arrowType = exports.Yinv = exports.Y = exports.Xinv = exports.X = exports.evaluate = exports.parseArrows = exports.parseOptions = exports.RE = exports.convertUnits = exports.matchrepl = exports.simplerepl = void 0; +exports.MATH_CONSTANTS = exports.MATH_FUNCTIONS = exports.ExpressionError = exports.parseExpression = exports.select = exports.SVGSelection = exports.dotType = exports.arrowType = exports.Yinv = exports.Y = exports.Xinv = exports.X = exports.evaluate = exports.parseArrows = exports.parseOptions = exports.RE = exports.convertUnits = exports.matchrepl = exports.simplerepl = void 0; +const expression_1 = require("./expression"); const simplerepl = function (regex, replace) { return function (_m, contents) { return contents.replace(regex, replace); @@ -2277,24 +4841,30 @@ const evaluate = function (exp) { if (!isNaN(num)) return num; this.variables = this.variables || {}; - const mathKeys = Object.keys(Math); - const varKeys = Object.keys(this.variables); - const allKeys = [...mathKeys, ...varKeys]; - const allValues = [ - ...mathKeys.map(k => Math[k]), - ...varKeys.map(k => this.variables[k]) - ]; try { - // @ts-ignore - const fn = new Function(...allKeys, `return (${exp});`); - return fn(...allValues); + return getCompiled(exp).evaluate(this.variables); } catch (e) { - console.warn('Evaluation error:', e); + console.warn('Evaluation error:', e.message); return NaN; } }; exports.evaluate = evaluate; +// Small bounded cache so repeated identical expressions (e.g. plot bounds, +// slider-driven re-evaluation) skip re-parsing entirely. +const expressionCache = new Map(); +const EXPRESSION_CACHE_MAX = 500; +function getCompiled(exp) { + let compiled = expressionCache.get(exp); + if (!compiled) { + compiled = (0, expression_1.parseExpression)(exp); + if (expressionCache.size >= EXPRESSION_CACHE_MAX) { + expressionCache.clear(); + } + expressionCache.set(exp, compiled); + } + return compiled; +} const X = function (v) { // Enhanced validation for coordinate transformation const numV = typeof v === 'string' ? parseFloat(v) : v; @@ -2371,8 +4941,13 @@ exports.dotType = exports.parseArrows; var svg_utils_1 = require("./svg-utils"); Object.defineProperty(exports, "SVGSelection", { enumerable: true, get: function () { return svg_utils_1.SVGSelection; } }); Object.defineProperty(exports, "select", { enumerable: true, get: function () { return svg_utils_1.select; } }); +var expression_2 = require("./expression"); +Object.defineProperty(exports, "parseExpression", { enumerable: true, get: function () { return expression_2.parseExpression; } }); +Object.defineProperty(exports, "ExpressionError", { enumerable: true, get: function () { return expression_2.ExpressionError; } }); +Object.defineProperty(exports, "MATH_FUNCTIONS", { enumerable: true, get: function () { return expression_2.MATH_FUNCTIONS; } }); +Object.defineProperty(exports, "MATH_CONSTANTS", { enumerable: true, get: function () { return expression_2.MATH_CONSTANTS; } }); -},{"./svg-utils":21}],21:[function(require,module,exports){ +},{"./expression":22,"./svg-utils":24}],24:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SVGSelection = void 0; @@ -2456,5 +5031,5 @@ function select(selector) { return new SVGSelection(selector); } -},{}]},{},[7])(7) +},{}]},{},[8])(8) }); diff --git a/package.json b/package.json index 09d17f5c..3921f201 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,17 @@ { "name": "latex2js-workspace", "version": "0.0.1", + "packageManager": "pnpm@11.15.1", "publishConfig": { "access": "restricted" }, "private": true, "scripts": { - "build": "lerna run build" + "build": "lerna run build", + "dev": "pnpm --filter @latex2js/playground dev", + "dev:build": "pnpm --filter @latex2js/playground build", + "e2e": "pnpm --filter @latex2js/playground e2e", + "test": "lerna run test -- --passWithNoTests" }, "devDependencies": { "copyfiles": "^2.4.1", @@ -16,6 +21,7 @@ "@types/jest": "^29.5.0", "@types/node": "^20.0.0", "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", "lerna": "^8.2.3", "typescript": "^5.2.0", "ts-node": "^10.9.2" diff --git a/packages/html5/jest.config.ts b/packages/html5/jest.config.ts index c8fde8c9..dd2f5ccc 100644 --- a/packages/html5/jest.config.ts +++ b/packages/html5/jest.config.ts @@ -8,6 +8,19 @@ const config: Config = { transform: { '^.+\\.ts$': 'ts-jest', }, + // Run DOM tests against TypeScript sources (no build needed). Tests that + // need a browser environment opt in per-file with a `@jest-environment jsdom` + // docblock; the setup file polyfills what jsdom lacks (requestAnimationFrame). + moduleNameMapper: { + '^latex2js$': '/../../packages/latex2js/src/index.ts', + '^latex2html5$': '/index.ts', + '^@latex2js/pstricks$': '/../../packages/pstricks/src/index.ts', + '^@latex2js/settings$': '/../../packages/settings/src/index.ts', + '^@latex2js/utils$': '/../../packages/utils/src/index.ts', + '^@latex2js/macros$': '/../../packages/macros/src/index.ts', + '^mathjaxjs$': '/../../packages/mathjaxjs/src/index.ts', + }, + setupFilesAfterEnv: ['/test/setup.ts'], }; export default config; diff --git a/packages/html5/src/components/list.ts b/packages/html5/src/components/list.ts new file mode 100644 index 00000000..387b4d2f --- /dev/null +++ b/packages/html5/src/components/list.ts @@ -0,0 +1,45 @@ +interface ComponentProps { + type: string; + lines: string[]; + [key: string]: any; +} + +function itemizeLine(line: string): string { + var m = line.match(/\\item (.*)/); + if (m) return '
  • ' + m[1] + '
  • '; + return line; +} + +function descriptionLine(line: string): string { + var m = line.match(/\\item\[([^\]]*)\]\s*(.*)/); + if (m) return '
    ' + m[1] + '
    ' + m[2] + '
    '; + return itemizeLine(line); +} + +/** + * Renders enumerate / itemize / description lists from \item lines. + */ +export default function render(that: ComponentProps): HTMLElement { + const type = that.type || 'enumerate'; + const convert = type === 'description' ? descriptionLine : itemizeLine; + const lines = that.lines.map(convert).join('\n'); + + let el: HTMLElement; + if (type === 'enumerate') { + const ol = document.createElement('ol'); + ol.className = 'math enumerate'; + ol.innerHTML = lines; + el = ol; + } else if (type === 'description') { + const dl = document.createElement('dl'); + dl.className = 'math description'; + dl.innerHTML = lines; + el = dl; + } else { + const ul = document.createElement('ul'); + ul.className = 'math itemize'; + ul.innerHTML = lines; + el = ul; + } + return el; +} diff --git a/packages/html5/src/index.ts b/packages/html5/src/index.ts index 2057f7a7..960abbf0 100644 --- a/packages/html5/src/index.ts +++ b/packages/html5/src/index.ts @@ -3,13 +3,14 @@ import { getMathJax, loadMathJax } from 'mathjaxjs'; import pspicture from './components/pspicture.js'; import nicebox from './components/nicebox.js'; import enumerate from './components/enumerate.js'; +import list from './components/list.js'; import verbatim from './components/verbatim.js'; import math from './components/math.js'; import macros from './components/macros'; -const ELEMENTS = { pspicture, nicebox, enumerate, verbatim, math, macros }; +const ELEMENTS = { pspicture, nicebox, enumerate, itemize: list, description: list, verbatim, math, macros }; -export { pspicture, nicebox, enumerate, verbatim, math, macros }; +export { pspicture, nicebox, enumerate, list, verbatim, math, macros }; export default function render(tex: string, resolve: (div: HTMLDivElement) => void): void { const done = () => { diff --git a/packages/html5/test/corpus-render.test.ts b/packages/html5/test/corpus-render.test.ts new file mode 100644 index 00000000..e30c3f33 --- /dev/null +++ b/packages/html5/test/corpus-render.test.ts @@ -0,0 +1,47 @@ +/** @jest-environment jsdom */ +import * as fs from 'fs'; +import * as path from 'path'; +import LaTeX2JS from 'latex2js'; +import pspicture from '../src/components/pspicture'; + +/** + * Golden-corpus render test: every pspicture on latex2js.com must render to an + * SVG without throwing. This exercises the full parse → component → psgraph + * pipeline against real site content. + */ +const corpusDir = path.join(__dirname, '../../latex2js/test/corpus'); +const files = fs.readdirSync(corpusDir).filter((f) => f.endsWith('.tex')); + +beforeEach(() => { + Object.defineProperty(document.documentElement, 'clientWidth', { value: 1200, configurable: true }); + Object.defineProperty(window, 'innerWidth', { value: 1200, configurable: true }); + document.body.innerHTML = ''; +}); + +describe.each(files)('corpus render: %s', (file) => { + const tex = fs.readFileSync(path.join(corpusDir, file), 'utf8'); + + it('renders every pspicture to an SVG', () => { + const latex = new LaTeX2JS(); + const parsed = latex.parse(tex); + + const pictures = parsed.filter((e: any) => e.type === 'pspicture'); + + pictures.forEach((env: any) => { + let div: HTMLDivElement; + expect(() => { + div = pspicture(env); + document.body.appendChild(div); + }).not.toThrow(); + expect(div!.querySelector('svg')).not.toBeNull(); + expect(div!.querySelector('svg')!.children.length).toBeGreaterThan(0); + }); + + // math/text-only documents (no pspicture) must still parse cleanly + expect(parsed.length).toBeGreaterThan(0); + + // parse diagnostics must be clean for real site content + const unknown = latex.lastDiagnostics.filter((d: any) => d.message.includes('unknown command')); + expect(unknown).toEqual([]); + }); +}); diff --git a/packages/html5/test/pspicture.test.ts b/packages/html5/test/pspicture.test.ts new file mode 100644 index 00000000..b2a1711a --- /dev/null +++ b/packages/html5/test/pspicture.test.ts @@ -0,0 +1,276 @@ +/** @jest-environment jsdom */ +import LaTeX2JS from 'latex2js'; +import pspicture from '../src/components/pspicture'; +import math from '../src/components/math'; +import verbatim from '../src/components/verbatim'; +import list from '../src/components/list'; + +function stubViewport(width: number): void { + Object.defineProperty(document.documentElement, 'clientWidth', { value: width, configurable: true }); + Object.defineProperty(window, 'innerWidth', { value: width, configurable: true }); +} + +function parsePspicture(tex: string): any { + const latex = new LaTeX2JS(); + const parsed = latex.parse(tex); + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env).toBeDefined(); + return env; +} + +beforeEach(() => { + stubViewport(1200); + document.body.innerHTML = ''; +}); + +describe('pspicture component (SVG rendering)', () => { + it('renders psline, pscircle and psframe into an SVG', () => { + const env = parsePspicture(` +\\begin{pspicture}(-5,-5)(5,5) +\\psline{->}(0,-3.75)(0,3.75) +\\pscircle(0,0){ 3 } +\\psframe(-2,-2)(2,2) +\\end{pspicture} + `); + + const div = pspicture(env); + document.body.appendChild(div); + + const svg = div.querySelector('svg'); + expect(svg).not.toBeNull(); + expect(div.className).toBe('pspicture'); + + // psframe → 4 elements + const lines = div.querySelectorAll('svg line'); + expect(lines).toHaveLength(4); + + // pscircle with xunit=50, r=3 → X(0)=250, Y(0)=250, r=150 + const circles = div.querySelectorAll('svg circle'); + expect(circles).toHaveLength(1); + expect(circles[0].getAttribute('cx')).toBe('250'); + expect(circles[0].getAttribute('cy')).toBe('250'); + expect(circles[0].getAttribute('r')).toBe('150'); + + // psline{->} → 1 line path + 1 arrowhead path + const paths = div.querySelectorAll('svg path'); + expect(paths.length).toBeGreaterThanOrEqual(2); + const arrowPaths = Array.from(paths).filter((p) => p.getAttribute('d')?.endsWith('Z')); + expect(arrowPaths.length).toBeGreaterThanOrEqual(1); + }); + + it('respects dashed linestyle and linecolor options', () => { + const env = parsePspicture(` +\\begin{pspicture}(0,0)(4,4) +\\psline[linestyle=dashed,linecolor=red](0,0)(1,1) +\\end{pspicture} + `); + + const div = pspicture(env); + document.body.appendChild(div); + + const path = div.querySelector('svg path')!; + expect(path.style.strokeDasharray).toBe('9,5'); + expect(path.style.stroke).toBe('red'); + }); + + it('re-renders psplot when a slider changes', () => { + const env = parsePspicture(` +\\psset{unit=1cm} +\\begin{pspicture}(-3.5,-1)(3.75,3.5) +\\slider{1}{8}{n}{$N$}{4} +\\psplot[algebraic]{-3.14}{3.14}{cos(n*x)+1} +\\end{pspicture} + `); + + const div = pspicture(env); + document.body.appendChild(div); + + const input = div.querySelector('input[type="range"]')!; + expect(input).not.toBeNull(); + expect(div.querySelectorAll('svg path.psplot')).toHaveLength(1); + + input.setAttribute('value', '8'); + input.dispatchEvent(new Event('input', { bubbles: true })); + + // still exactly one plot path after the re-render… + expect(div.querySelectorAll('svg path.psplot')).toHaveLength(1); + // …and the variable used to compute it was updated + expect(env.env.variables.n).toBe(8); + }); + + it('handles an empty pspicture without throwing', () => { + const env = parsePspicture(` +\\begin{pspicture}(0,0)(2,2) +\\end{pspicture} + `); + const div = pspicture(env); + document.body.appendChild(div); + expect(div.querySelector('svg')).not.toBeNull(); + }); + + it('draws elements in source order (layering)', () => { + const env = parsePspicture(` +\\begin{pspicture}(0,0)(4,4) +\\pscircle(0,0){1} +\\psline(0,0)(1,1) +\\pscircle(1,1){2} +\\end{pspicture} + `); + + const div = pspicture(env); + document.body.appendChild(div); + + // the old parser grouped by command type (circles before lines); the new + // parser renders in document order: circle, line, circle + const svg = div.querySelector('svg')!; + const tags = Array.from(svg.children).map((el) => el.tagName); + expect(tags.filter((t) => t === 'circle' || t === 'path')).toEqual([ + 'circle', + 'path', + 'circle' + ]); + }); + + it('renders psdots as small circles', () => { + const env = parsePspicture(` +\\begin{pspicture}(0,0)(4,4) +\\psdots(1,1)(2,2) +\\end{pspicture} + `); + const div = pspicture(env); + document.body.appendChild(div); + expect(div.querySelectorAll('svg circle')).toHaveLength(2); + }); + + it('renders psgrid as grid lines', () => { + const env = parsePspicture(` +\\begin{pspicture}(0,0)(4,4) +\\psgrid +\\end{pspicture} + `); + const div = pspicture(env); + document.body.appendChild(div); + // 4 x-lines + 4 y-lines for a 4x4 grid at 1-unit spacing (xunit=50) + expect(div.querySelectorAll('svg line').length).toBeGreaterThanOrEqual(8); + }); + + it('renders psellipse as an SVG ellipse', () => { + const env = parsePspicture(` +\\begin{pspicture}(0,0)(4,4) +\\psellipse[fillstyle=solid,fillcolor=lightblue](2,2)(1,0.5) +\\end{pspicture} + `); + const div = pspicture(env); + document.body.appendChild(div); + const ellipse = div.querySelector('svg ellipse')!; + expect(ellipse).not.toBeNull(); + expect(ellipse.getAttribute('rx')).toBe('50'); + expect(ellipse.getAttribute('ry')).toBe('25'); + }); + + it('renders psbezier and pscurve as paths', () => { + const env = parsePspicture(` +\\begin{pspicture}(0,0)(4,4) +\\psbezier(0,0)(1,2)(2,2)(3,0) +\\pscurve(0,0)(1,1)(2,0) +\\psccurve(0,1)(1,2)(2,1) +\\end{pspicture} + `); + const div = pspicture(env); + document.body.appendChild(div); + const paths = div.querySelectorAll('svg path'); + expect(paths.length).toBe(3); + // the closed curve path should end with Z + expect(paths[2].getAttribute('d')?.endsWith('Z')).toBe(true); + }); + + it('renders pswedge as a filled pie slice', () => { + const env = parsePspicture(` +\\begin{pspicture}(0,0)(4,4) +\\pswedge[fillstyle=solid,fillcolor=gray!40](2,2){1}{0}{90} +\\end{pspicture} + `); + const div = pspicture(env); + document.body.appendChild(div); + const path = div.querySelector('svg path')!; + expect(path.getAttribute('d')).toContain('A'); + expect(path.getAttribute('d')?.endsWith('Z')).toBe(true); + }); + + it('renders pscustom as a filled path', () => { + const env = parsePspicture(` +\\begin{pspicture}(0,0)(8,4) +\\pscustom[fillstyle=solid,fillcolor=gray!40,linestyle=none]{ + \\psline(0,0)(4,1.2) + \\psline(4,1.2)(8,0) + \\psline(8,0)(4,-1.2) + \\psline(4,-1.2)(0,0) +} +\\end{pspicture} + `); + const div = pspicture(env); + document.body.appendChild(div); + const path = div.querySelector('svg path')!; + expect(path).not.toBeNull(); + const d = path.getAttribute('d') || ''; + expect(d.startsWith('M')).toBe(true); + // the diamond path is filled and closed + expect(d.endsWith('Z')).toBe(true); + }); + + it('fills star-variant primitives', () => { + const env = parsePspicture(` +\\begin{pspicture}(0,0)(4,4) +\\pscircle*(0,0){1} +\\psframe*[fillcolor=red](1,1)(2,2) +\\end{pspicture} + `); + const div = pspicture(env); + document.body.appendChild(div); + const circle = div.querySelector('svg circle')!; + expect(circle.style.fill).toBe('black'); // default fillcolor + const rect = div.querySelector('svg rect')!; + expect(rect.style.fill).toBe('red'); + }); + + it('renders multido-expanded commands', () => { + const env = parsePspicture(` +\\begin{pspicture}(0,0)(4,4) +\\multido{\\i=0+1}{3}{\\psline(\\i,0)(\\i,1)} +\\end{pspicture} + `); + const div = pspicture(env); + document.body.appendChild(div); + // 3 expanded lines (paths) + expect(div.querySelectorAll('svg path')).toHaveLength(3); + }); +}); + +describe('math + verbatim components', () => { + it('renders math lines as a span with raw TeX', () => { + const span = math({ type: 'math', lines: ['$$x^2 + y^2 = z^2$$'] }); + expect(span.className).toBe('math'); + expect(span.innerHTML).toContain('$$x^2 + y^2 = z^2$$'); + }); + + it('renders verbatim content inside a
    ', () => {
    +    const pre = verbatim({ type: 'verbatim', lines: ['\\psline{->}(0,0)(1,1)'] });
    +    expect(pre.tagName).toBe('PRE');
    +    expect(pre.className).toBe('verbatim');
    +    expect(pre.textContent).toContain('\\psline{->}(0,0)(1,1)');
    +  });
    +
    +  it('renders itemize and description lists', () => {
    +    const ul = list({ type: 'itemize', lines: ['\\item first', '\\item second'] });
    +    expect(ul.tagName).toBe('UL');
    +    expect(ul.querySelectorAll('li')).toHaveLength(2);
    +
    +    const dl = list({ type: 'description', lines: ['\\item[Term] definition'] });
    +    expect(dl.tagName).toBe('DL');
    +    expect(dl.querySelector('dt')?.textContent).toBe('Term');
    +    expect(dl.querySelector('dd')?.textContent).toBe('definition');
    +
    +    const ol = list({ type: 'enumerate', lines: ['\\item one'] });
    +    expect(ol.tagName).toBe('OL');
    +  });
    +});
    diff --git a/packages/html5/test/setup.ts b/packages/html5/test/setup.ts
    new file mode 100644
    index 00000000..7c08ea8e
    --- /dev/null
    +++ b/packages/html5/test/setup.ts
    @@ -0,0 +1,12 @@
    +/**
    + * Shared setup for DOM (jsdom) tests in the html5 package.
    + *
    + * jsdom does not implement requestAnimationFrame unless the environment is
    + * created with `pretendToBeVisual`, and psgraph's rput rendering relies on it.
    + * Polyfill with a timer so rendering tests are deterministic.
    + */
    +if (typeof (globalThis as any).requestAnimationFrame === 'undefined') {
    +  (globalThis as any).requestAnimationFrame = (cb: FrameRequestCallback) =>
    +    setTimeout(() => cb(Date.now()), 0) as unknown as number;
    +  (globalThis as any).cancelAnimationFrame = (id: number) => clearTimeout(id);
    +}
    diff --git a/packages/html5/tsconfig.json b/packages/html5/tsconfig.json
    index 1a9d5696..2b870caa 100644
    --- a/packages/html5/tsconfig.json
    +++ b/packages/html5/tsconfig.json
    @@ -2,7 +2,8 @@
       "extends": "../../tsconfig.json",
       "compilerOptions": {
         "outDir": "dist",
    -    "rootDir": "src/"
    +    "rootDir": "src/",
    +    "isolatedModules": true
       },
       "include": ["src/**/*.ts"],
       "exclude": ["dist", "node_modules", "**/*.spec.*", "**/*.test.*"]
    diff --git a/packages/latex2js/jest.config.ts b/packages/latex2js/jest.config.ts
    index c8fde8c9..8a47abc0 100644
    --- a/packages/latex2js/jest.config.ts
    +++ b/packages/latex2js/jest.config.ts
    @@ -8,6 +8,12 @@ const config: Config = {
       transform: {
         '^.+\\.ts$': 'ts-jest',
       },
    +  // Run against TypeScript sources (no build needed).
    +  moduleNameMapper: {
    +    '^@latex2js/pstricks$': '/../../packages/pstricks/src/index.ts',
    +    '^@latex2js/settings$': '/../../packages/settings/src/index.ts',
    +    '^@latex2js/utils$': '/../../packages/utils/src/index.ts',
    +  },
     };
     
     export default config;
    diff --git a/packages/latex2js/package.json b/packages/latex2js/package.json
    index d97b3fdf..9832eef6 100644
    --- a/packages/latex2js/package.json
    +++ b/packages/latex2js/package.json
    @@ -30,19 +30,24 @@
       ],
       "scripts": {
         "copy": "copyfiles -f ../../LICENSE README.md package.json latex2js.css latex2js.mathapedia.css dist",
    +    "copy:grammar": "mkdir -p dist/grammar dist/esm/grammar && cp src/grammar/parser.js src/grammar/parser.d.ts src/grammar/grammar.pegjs dist/grammar/ && cp src/grammar/parser.js dist/esm/grammar/",
         "clean": "rimraf dist",
    -    "build": "pnpm run clean && tsc && tsc -p tsconfig.esm.json && pnpm run copy && cp latex2js.css ../../bundle/latex2js.css",
    -    "build:dev": "pnpm run clean && tsc --declarationMap && tsc -p tsconfig.esm.json && pnpm run copy",
    +    "build": "pnpm run clean && tsc && tsc -p tsconfig.esm.json && pnpm run copy && pnpm run copy:grammar && cp latex2js.css ../../bundle/latex2js.css",
    +    "build:dev": "pnpm run clean && tsc --declarationMap && tsc -p tsconfig.esm.json && pnpm run copy && pnpm run copy:grammar",
    +    "grammar": "peggy -o src/grammar/parser.js --dts src/grammar/grammar.pegjs",
         "test": "jest",
         "test:watch": "jest --watch",
         "test:debug": "node --inspect node_modules/.bin/jest --runInBand"
       },
    -  "bugs": {
    -    "url": "https://github.com/Mathapedia/LaTeX2JS/issues"
    -  },
       "dependencies": {
         "@latex2js/pstricks": "workspace:^",
         "@latex2js/settings": "workspace:^",
         "@latex2js/utils": "workspace:^"
    +  },
    +  "devDependencies": {
    +    "peggy": "^5.1.0"
    +  },
    +  "bugs": {
    +    "url": "https://github.com/Mathapedia/LaTeX2JS/issues"
       }
     }
    diff --git a/packages/latex2js/src/grammar/grammar.pegjs b/packages/latex2js/src/grammar/grammar.pegjs
    new file mode 100644
    index 00000000..abca8a84
    --- /dev/null
    +++ b/packages/latex2js/src/grammar/grammar.pegjs
    @@ -0,0 +1,142 @@
    +// ============================================================================
    +// latex2js document grammar (Peggy)
    +//
    +// This grammar is deliberately GENERIC and STRUCTURAL: it tokenizes a
    +// LaTeX-ish document into an ordered tree of segments (environments, commands,
    +// text lines) with locations, but makes no semantic decisions. Interpretation
    +// (which environments are structural, which commands map to PSTricks data,
    +// text/header transforms) happens registry-driven in lib/parser.ts, so the
    +// runtime extension API (addEnvironment / addText / addHeaders) keeps working
    +// without regenerating the parser.
    +//
    +// What the grammar fixes versus the old line-based regex parser:
    +//   * inline `%` comments as a lexical rule (stripped everywhere)
    +//   * commands spanning multiple lines (args tracked by brace depth)
    +//   * an ordered tree → source-order rendering + real diagnostics
    +//   * verbatim/print swallow everything until their matching \end
    +// ============================================================================
    +
    +{
    +  let depth = 0;
    +
    +  function loc() {
    +    const l = location();
    +    return { line: l.start.line, column: l.start.column };
    +  }
    +}
    +
    +Document = segs:Segment* { return segs; }
    +
    +Segment
    +  = Env
    +  / StrayEnd
    +  / Line
    +
    +StrayEnd = e:EndTag { return { kind: 'strayEnd', name: e.name, raw: e.raw, loc: loc() }; }
    +
    +// ---------------------------------------------------------------------------
    +// Environments
    +// ---------------------------------------------------------------------------
    +
    +Env
    +  = VerbatimEnv
    +  / RegularEnv
    +
    +// verbatim and print swallow raw content (including \begin / \end of other
    +// environments) until their own matching \end — mirroring the old behavior.
    +VerbatimEnv
    +  = start:BeginVerb content:(!EndVerb .)* end:EndVerb {
    +      return {
    +        kind: 'env',
    +        name: start.name,
    +        verbatim: true,
    +        begin: start,
    +        end: { name: start.name, raw: '\\end{' + end + '}', loc: loc() },
    +        content: [{
    +          kind: 'verbatim',
    +          text: content.map((pair) => pair[1]).join('').replace(/\n$/, '')
    +        }],
    +        loc: loc()
    +      };
    +    }
    +
    +BeginVerb = "\\begin{" n:("verbatim" / "print") "}" { return { name: n, raw: '\\begin{' + n + '}', loc: loc() }; }
    +EndVerb = "\\end{" n:("verbatim" / "print") "}" { return n; }
    +
    +// Regular environments pair a generic \begin{name} with a generic \end{name}.
    +// The `end` is optional so unclosed environments surface as diagnostics
    +// instead of a hard parse error; name mismatch is reported in parser.ts.
    +RegularEnv
    +  = b:BeginTag _ content:EnvContent* _ e:EndTag? {
    +      return { kind: 'env', name: b.name, verbatim: false, begin: b, end: e || null, content: content, loc: loc() };
    +    }
    +
    +BeginTag
    +  = "\\begin{" name:EnvName "}" tail:Tail {
    +      return { name: name, raw: '\\begin{' + name + '}' + tail, loc: loc() };
    +    }
    +
    +EndTag
    +  = "\\end{" name:EnvName "}" {
    +      return { name: name, raw: '\\end{' + name + '}', loc: loc() };
    +    }
    +
    +EnvName = chars:[a-zA-Z*]+ { return chars.join(''); }
    +
    +EnvContent
    +  = Env
    +  / Command
    +  / Line
    +
    +// ---------------------------------------------------------------------------
    +// Commands
    +// ---------------------------------------------------------------------------
    +
    +Command
    +  = start:CommandStart tail:Tail {
    +      depth = 0;
    +      return { kind: 'command', name: start.name, raw: start.raw + tail, loc: loc() };
    +    }
    +
    +CommandStart = "\\" !("begin{") !("end{") chars:[a-zA-Z@]+ {
    +  return { name: chars.join(''), raw: '\\' + chars.join('') };
    +}
    +
    +// Tail consumes the argument groups ([...], {...}, (...)) of a command or a
    +// \begin tag. Brace/paren/bracket depth is tracked so newlines and comments
    +// are legal inside args but a command still ends at a newline or the start of
    +// the next command at depth 0.
    +Tail = parts:TailPart* { return parts.join(''); }
    +
    +TailPart
    +  = Comment
    +  / Open { depth++; return text(); }
    +  / Close { depth = Math.max(0, depth - 1); return text(); }
    +  / &{ return depth === 0; } !EOL !CommandStart !BeginStart !EndStart c:. { return c; }
    +  / &{ return depth > 0; } c:. { return c; }
    +
    +Comment = "%" (!EOL .)* { return ''; }
    +
    +Open = "[" / "{" / "("
    +Close = "]" / "}" / ")"
    +
    +BeginStart = "\\begin{"
    +EndStart = "\\end{"
    +
    +// ---------------------------------------------------------------------------
    +// Text lines
    +// ---------------------------------------------------------------------------
    +
    +Line
    +  = parts:LinePart+ eol:EOL? { return { kind: 'line', parts: parts, hasEol: !!eol, loc: loc() }; }
    +  / eol:EOL { return { kind: 'line', parts: [], hasEol: true, loc: loc() }; }
    +
    +LinePart
    +  = Comment
    +  / Command
    +  / !BeginStart !EndStart !EOL c:. { return { kind: 'char', c: c, loc: loc() }; }
    +
    +EOL = "\r\n" / "\n" / "\r"
    +
    +_ = [ \t]*
    +// drift
    diff --git a/packages/latex2js/src/grammar/parser.d.ts b/packages/latex2js/src/grammar/parser.d.ts
    new file mode 100644
    index 00000000..b727867c
    --- /dev/null
    +++ b/packages/latex2js/src/grammar/parser.d.ts
    @@ -0,0 +1,208 @@
    +/** Provides information pointing to a location within a source. */
    +export interface Location {
    +  /** Line in the parsed source (1-based). */
    +  readonly line: number;
    +  /** Column in the parsed source (1-based). */
    +  readonly column: number;
    +  /** Offset in the parsed source (0-based). */
    +  readonly offset: number;
    +}
    +
    +/**
    + * Anything that can successfully be converted to a string with `String()`
    + * so that it can be used in error messages.
    + *
    + * The GrammarLocation class in Peggy is a good example.
    + */
    +export interface GrammarSourceObject {
    +  readonly toString: () => string;
    +
    +  /**
    +   * If specified, allows the grammar source to be embedded in a larger file
    +   * at some offset.
    +   */
    +  readonly offset?: undefined | ((loc: Location) => Location);
    +}
    +
    +/**
    + * Most often, you just use a string with the file name.
    + */
    +export type GrammarSource = string | GrammarSourceObject;
    +
    +/** The `start` and `end` position's of an object within the source. */
    +export interface LocationRange {
    +  /**
    +   * A string or object that was supplied to the `parse()` call as the
    +   * `grammarSource` option.
    +   */
    +  readonly source: GrammarSource;
    +  /** Position at the beginning of the expression. */
    +  readonly start: Location;
    +  /** Position after the end of the expression. */
    +  readonly end: Location;
    +}
    +
    +/**
    + * Expected a literal string, like `"foo"i`.
    + */
    +export interface LiteralExpectation {
    +  readonly type: "literal";
    +  readonly text: string;
    +  readonly ignoreCase: boolean;
    +}
    +
    +/**
    + * Range of characters, like `a-z`
    + */
    +export type ClassRange = [
    +  start: string,
    +  end: string,
    +]
    +
    +export interface ClassParts extends Array {
    +}
    +
    +/**
    + * Expected a class, such as `[^acd-gz]i`
    + */
    +export interface ClassExpectation {
    +  readonly type: "class";
    +  readonly parts: ClassParts;
    +  readonly inverted: boolean;
    +  readonly ignoreCase: boolean;
    +}
    +
    +/**
    + * Expected any character, with `.`
    + */
    +export interface AnyExpectation {
    +  readonly type: "any";
    +}
    +
    +/**
    + * Expected the end of input.
    + */
    +export interface EndExpectation {
    +  readonly type: "end";
    +}
    +
    +/**
    + * Expected some other input.  These are specified with a rule's
    + * "human-readable name", or with the `expected(message, location)`
    + * function.
    + */
    +export interface OtherExpectation {
    +  readonly type: "other";
    +  readonly description: string;
    +}
    +
    +export type Expectation =
    +  | AnyExpectation
    +  | ClassExpectation
    +  | EndExpectation
    +  | LiteralExpectation
    +  | OtherExpectation;
    +
    +/**
    + * Pass an array of these into `SyntaxError.prototype.format()`
    + */
    +export interface SourceText {
    +  /**
    +   * Identifier of an input that was used as a grammarSource in parse().
    +   */
    +  readonly source: GrammarSource;
    +  /** Source text of the input. */
    +  readonly text: string;
    +}
    +
    +export declare class SyntaxError extends globalThis.SyntaxError {
    +  /**
    +   * Constructs the human-readable message from the machine representation.
    +   *
    +   * @param expected Array of expected items, generated by the parser
    +   * @param found Any text that will appear as found in the input instead of
    +   *   expected
    +   */
    +  static buildMessage(expected: Expectation[], found?: string | null | undefined): string;
    +  readonly expected: Expectation[];
    +  readonly found: string | null | undefined;
    +  readonly location: LocationRange;
    +  readonly name: string;
    +  constructor(
    +    message: string,
    +    expected: Expectation[],
    +    found: string | null,
    +    location: LocationRange,
    +  );
    +
    +  /**
    +   * With good sources, generates a feature-rich error message pointing to the
    +   * error in the input.
    +   * @param sources List of {source, text} objects that map to the input.
    +   */
    +  format(sources: SourceText[]): string;
    +}
    +
    +/**
    + * Trace execution of the parser.
    + */
    +export interface ParserTracer {
    +  trace: (event: ParserTracerEvent) => void;
    +}
    +
    +export type ParserTracerEvent
    +  = {
    +      readonly type: "rule.enter";
    +      readonly rule: string;
    +      readonly location: LocationRange
    +    }
    +  | {
    +      readonly type: "rule.fail";
    +      readonly rule: string;
    +      readonly location: LocationRange
    +    }
    +  | {
    +      readonly type: "rule.match";
    +      readonly rule: string;
    +      readonly location: LocationRange
    +      /** Return value from the rule. */
    +      readonly result: unknown;
    +    };
    +
    +export type StartRuleNames = "Document";
    +export interface ParseOptions {
    +  /**
    +   * String or object that will be attached to the each `LocationRange` object
    +   * created by the parser. For example, this can be path to the parsed file
    +   * or even the File object.
    +   */
    +  readonly grammarSource?: GrammarSource;
    +  readonly startRule?: T;
    +  readonly tracer?: ParserTracer;
    +
    +  // Internal use only:
    +  readonly peg$library?: boolean;
    +  // Internal use only:
    +  peg$currPos?: number;
    +  // Internal use only:
    +  peg$silentFails?: number;
    +  // Internal use only:
    +  peg$maxFailExpected?: Expectation[];
    +  // Extra application-specific properties
    +  [key: string]: unknown;
    +}
    +
    +export declare const StartRules: StartRuleNames[];
    +export declare const parse: typeof ParseFunction;
    +
    +// Overload of ParseFunction for each allowedStartRule
    +
    +declare function ParseFunction>(
    +  input: string,
    +  options?: Options,
    +): any;
    +
    +declare function ParseFunction>(
    +  input: string,
    +  options?: Options,
    +): any;
    diff --git a/packages/latex2js/src/grammar/parser.js b/packages/latex2js/src/grammar/parser.js
    new file mode 100644
    index 00000000..4f09feb7
    --- /dev/null
    +++ b/packages/latex2js/src/grammar/parser.js
    @@ -0,0 +1,1446 @@
    +// @generated by Peggy 5.1.0.
    +//
    +// https://peggyjs.org/
    +
    +"use strict";
    +
    +class peg$SyntaxError extends SyntaxError {
    +  constructor(message, expected, found, location) {
    +    super(message);
    +    this.expected = expected;
    +    this.found = found;
    +    this.location = location;
    +    this.name = "SyntaxError";
    +  }
    +
    +  format(sources) {
    +    let str = "Error: " + this.message;
    +    if (this.location) {
    +      let src = null;
    +      const st = sources.find(s => s.source === this.location.source);
    +      if (st) {
    +        src = st.text.split(/\r\n|\n|\r/g);
    +      }
    +      const s = this.location.start;
    +      const offset_s = (this.location.source && (typeof this.location.source.offset === "function"))
    +        ? this.location.source.offset(s)
    +        : s;
    +      const loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column;
    +      if (src) {
    +        const e = this.location.end;
    +        const filler = "".padEnd(offset_s.line.toString().length, " ");
    +        const line = src[s.line - 1];
    +        const last = s.line === e.line ? e.column : line.length + 1;
    +        const hatLen = (last - s.column) || 1;
    +        str += "\n --> " + loc + "\n"
    +            + filler + " |\n"
    +            + offset_s.line + " | " + line + "\n"
    +            + filler + " | " + "".padEnd(s.column - 1, " ")
    +            + "".padEnd(hatLen, "^");
    +      } else {
    +        str += "\n at " + loc;
    +      }
    +    }
    +    return str;
    +  }
    +
    +  static buildMessage(expected, found) {
    +    function hex(ch) {
    +      return ch.codePointAt(0).toString(16).toUpperCase();
    +    }
    +
    +    const nonPrintable = Object.prototype.hasOwnProperty.call(RegExp.prototype, "unicode")
    +      ? new RegExp("[\\p{C}\\p{Mn}\\p{Mc}]", "gu")
    +      : null;
    +    function unicodeEscape(s) {
    +      if (nonPrintable) {
    +        return s.replace(nonPrintable,  ch => "\\u{" + hex(ch) + "}");
    +      }
    +      return s;
    +    }
    +
    +    function literalEscape(s) {
    +      return unicodeEscape(s
    +        .replace(/\\/g, "\\\\")
    +        .replace(/"/g,  "\\\"")
    +        .replace(/\0/g, "\\0")
    +        .replace(/\t/g, "\\t")
    +        .replace(/\n/g, "\\n")
    +        .replace(/\r/g, "\\r")
    +        .replace(/[\x00-\x0F]/g,          ch => "\\x0" + hex(ch))
    +        .replace(/[\x10-\x1F\x7F-\x9F]/g, ch => "\\x"  + hex(ch)));
    +    }
    +
    +    function classEscape(s) {
    +      return unicodeEscape(s
    +        .replace(/\\/g, "\\\\")
    +        .replace(/\]/g, "\\]")
    +        .replace(/\^/g, "\\^")
    +        .replace(/-/g,  "\\-")
    +        .replace(/\0/g, "\\0")
    +        .replace(/\t/g, "\\t")
    +        .replace(/\n/g, "\\n")
    +        .replace(/\r/g, "\\r")
    +        .replace(/[\x00-\x0F]/g,          ch => "\\x0" + hex(ch))
    +        .replace(/[\x10-\x1F\x7F-\x9F]/g, ch => "\\x"  + hex(ch)));
    +    }
    +
    +    const DESCRIBE_EXPECTATION_FNS = {
    +      literal(expectation) {
    +        return "\"" + literalEscape(expectation.text) + "\"";
    +      },
    +
    +      class(expectation) {
    +        const escapedParts = expectation.parts.map(
    +          part => (Array.isArray(part)
    +            ? classEscape(part[0]) + "-" + classEscape(part[1])
    +            : classEscape(part))
    +        );
    +
    +        return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]" + (expectation.unicode ? "u" : "");
    +      },
    +
    +      any() {
    +        return "any character";
    +      },
    +
    +      end() {
    +        return "end of input";
    +      },
    +
    +      other(expectation) {
    +        return expectation.description;
    +      },
    +    };
    +
    +    function describeExpectation(expectation) {
    +      return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);
    +    }
    +
    +    function describeExpected(expected) {
    +      const descriptions = expected.map(describeExpectation);
    +      descriptions.sort();
    +
    +      if (descriptions.length > 0) {
    +        let j = 1;
    +        for (let i = 1; i < descriptions.length; i++) {
    +          if (descriptions[i - 1] !== descriptions[i]) {
    +            descriptions[j] = descriptions[i];
    +            j++;
    +          }
    +        }
    +        descriptions.length = j;
    +      }
    +
    +      switch (descriptions.length) {
    +        case 1:
    +          return descriptions[0];
    +
    +        case 2:
    +          return descriptions[0] + " or " + descriptions[1];
    +
    +        default:
    +          return descriptions.slice(0, -1).join(", ")
    +            + ", or "
    +            + descriptions[descriptions.length - 1];
    +      }
    +    }
    +
    +    function describeFound(found) {
    +      return found ? "\"" + literalEscape(found) + "\"" : "end of input";
    +    }
    +
    +    return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
    +  }
    +}
    +
    +function peg$parse(input, options) {
    +  options = options !== undefined ? options : {};
    +
    +  const peg$FAILED = {};
    +  const peg$source = options.grammarSource;
    +
    +  const peg$startRuleFunctions = {
    +    Document: peg$parseDocument,
    +  };
    +  let peg$startRuleFunction = peg$parseDocument;
    +
    +  const peg$c0 = "\\begin{";
    +  const peg$c1 = "verbatim";
    +  const peg$c2 = "print";
    +  const peg$c3 = "}";
    +  const peg$c4 = "\\end{";
    +  const peg$c5 = "\\";
    +  const peg$c6 = "begin{";
    +  const peg$c7 = "end{";
    +  const peg$c8 = "%";
    +  const peg$c9 = "\r\n";
    +
    +  const peg$r0 = /^[a-zA-Z*]/;
    +  const peg$r1 = /^[a-zA-Z@]/;
    +  const peg$r2 = /^[([{]/;
    +  const peg$r3 = /^[)\]}]/;
    +  const peg$r4 = /^[\n\r]/;
    +  const peg$r5 = /^[ \t]/;
    +
    +  const peg$e0 = peg$anyExpectation();
    +  const peg$e1 = peg$literalExpectation("\\begin{", false);
    +  const peg$e2 = peg$literalExpectation("verbatim", false);
    +  const peg$e3 = peg$literalExpectation("print", false);
    +  const peg$e4 = peg$literalExpectation("}", false);
    +  const peg$e5 = peg$literalExpectation("\\end{", false);
    +  const peg$e6 = peg$classExpectation([["a", "z"], ["A", "Z"], "*"], false, false, false);
    +  const peg$e7 = peg$literalExpectation("\\", false);
    +  const peg$e8 = peg$literalExpectation("begin{", false);
    +  const peg$e9 = peg$literalExpectation("end{", false);
    +  const peg$e10 = peg$classExpectation([["a", "z"], ["A", "Z"], "@"], false, false, false);
    +  const peg$e11 = peg$literalExpectation("%", false);
    +  const peg$e12 = peg$classExpectation(["(", "[", "{"], false, false, false);
    +  const peg$e13 = peg$classExpectation([")", "]", "}"], false, false, false);
    +  const peg$e14 = peg$literalExpectation("\r\n", false);
    +  const peg$e15 = peg$classExpectation(["\n", "\r"], false, false, false);
    +  const peg$e16 = peg$classExpectation([" ", "\t"], false, false, false);
    +
    +  function peg$f0(segs) {    return segs;  }
    +  function peg$f1(e) {    return { kind: 'strayEnd', name: e.name, raw: e.raw, loc: loc() };  }
    +  function peg$f2(start, content, end) {
    +    return {
    +      kind: 'env',
    +      name: start.name,
    +      verbatim: true,
    +      begin: start,
    +      end: { name: start.name, raw: '\\end{' + end + '}', loc: loc() },
    +      content: [{
    +        kind: 'verbatim',
    +        text: content.map((pair) => pair[1]).join('').replace(/\n$/, '')
    +      }],
    +      loc: loc()
    +    };
    +  }
    +  function peg$f3(n) {    return { name: n, raw: '\\begin{' + n + '}', loc: loc() };  }
    +  function peg$f4(n) {    return n;  }
    +  function peg$f5(b, content, e) {
    +    return { kind: 'env', name: b.name, verbatim: false, begin: b, end: e || null, content: content, loc: loc() };
    +  }
    +  function peg$f6(name, tail) {
    +    return { name: name, raw: '\\begin{' + name + '}' + tail, loc: loc() };
    +  }
    +  function peg$f7(name) {
    +    return { name: name, raw: '\\end{' + name + '}', loc: loc() };
    +  }
    +  function peg$f8(chars) {    return chars.join('');  }
    +  function peg$f9(start, tail) {
    +    depth = 0;
    +    return { kind: 'command', name: start.name, raw: start.raw + tail, loc: loc() };
    +  }
    +  function peg$f10(chars) {
    +    return { name: chars.join(''), raw: '\\' + chars.join('') };
    +  }
    +  function peg$f11(parts) {    return parts.join('');  }
    +  function peg$f12() {    depth++; return text();  }
    +  function peg$f13() {    depth = Math.max(0, depth - 1); return text();  }
    +  function peg$f14() {    return depth === 0;  }
    +  function peg$f15(c) {    return c;  }
    +  function peg$f16() {    return depth > 0;  }
    +  function peg$f17(c) {    return c;  }
    +  function peg$f18() {    return '';  }
    +  function peg$f19(parts, eol) {    return { kind: 'line', parts: parts, hasEol: !!eol, loc: loc() };  }
    +  function peg$f20(eol) {    return { kind: 'line', parts: [], hasEol: true, loc: loc() };  }
    +  function peg$f21(c) {    return { kind: 'char', c: c, loc: loc() };  }
    +  let peg$currPos = options.peg$currPos | 0;
    +  let peg$savedPos = peg$currPos;
    +  const peg$posDetailsCache = [{ line: 1, column: 1 }];
    +  let peg$maxFailPos = peg$currPos;
    +  let peg$maxFailExpected = options.peg$maxFailExpected || [];
    +  let peg$silentFails = options.peg$silentFails | 0;
    +
    +  let peg$result;
    +
    +  if (options.startRule) {
    +    if (!(options.startRule in peg$startRuleFunctions)) {
    +      throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
    +    }
    +
    +    peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
    +  }
    +
    +  function text() {
    +    return input.substring(peg$savedPos, peg$currPos);
    +  }
    +
    +  function offset() {
    +    return peg$savedPos;
    +  }
    +
    +  function range() {
    +    return {
    +      source: peg$source,
    +      start: peg$savedPos,
    +      end: peg$currPos,
    +    };
    +  }
    +
    +  function location() {
    +    return peg$computeLocation(peg$savedPos, peg$currPos);
    +  }
    +
    +  function expected(description, location) {
    +    location = location !== undefined
    +      ? location
    +      : peg$computeLocation(peg$savedPos, peg$currPos);
    +
    +    throw peg$buildStructuredError(
    +      [peg$otherExpectation(description)],
    +      input.substring(peg$savedPos, peg$currPos),
    +      location
    +    );
    +  }
    +
    +  function error(message, location) {
    +    location = location !== undefined
    +      ? location
    +      : peg$computeLocation(peg$savedPos, peg$currPos);
    +
    +    throw peg$buildSimpleError(message, location);
    +  }
    +
    +  function peg$getUnicode(pos = peg$currPos) {
    +    const cp = input.codePointAt(pos);
    +    if (cp === undefined) {
    +      return "";
    +    }
    +    return String.fromCodePoint(cp);
    +  }
    +
    +  function peg$literalExpectation(text, ignoreCase) {
    +    return { type: "literal", text, ignoreCase };
    +  }
    +
    +  function peg$classExpectation(parts, inverted, ignoreCase, unicode) {
    +    return { type: "class", parts, inverted, ignoreCase, unicode };
    +  }
    +
    +  function peg$anyExpectation() {
    +    return { type: "any" };
    +  }
    +
    +  function peg$endExpectation() {
    +    return { type: "end" };
    +  }
    +
    +  function peg$otherExpectation(description) {
    +    return { type: "other", description };
    +  }
    +
    +  function peg$computePosDetails(pos) {
    +    let details = peg$posDetailsCache[pos];
    +    let p;
    +
    +    if (details) {
    +      return details;
    +    } else {
    +      if (pos >= peg$posDetailsCache.length) {
    +        p = peg$posDetailsCache.length - 1;
    +      } else {
    +        p = pos;
    +        while (!peg$posDetailsCache[--p]) {}
    +      }
    +
    +      details = peg$posDetailsCache[p];
    +      details = {
    +        line: details.line,
    +        column: details.column,
    +      };
    +
    +      while (p < pos) {
    +        if (input.charCodeAt(p) === 10) {
    +          details.line++;
    +          details.column = 1;
    +        } else {
    +          details.column++;
    +        }
    +
    +        p++;
    +      }
    +
    +      peg$posDetailsCache[pos] = details;
    +
    +      return details;
    +    }
    +  }
    +
    +  function peg$computeLocation(startPos, endPos, offset) {
    +    const startPosDetails = peg$computePosDetails(startPos);
    +    const endPosDetails = peg$computePosDetails(endPos);
    +
    +    const res = {
    +      source: peg$source,
    +      start: {
    +        offset: startPos,
    +        line: startPosDetails.line,
    +        column: startPosDetails.column,
    +      },
    +      end: {
    +        offset: endPos,
    +        line: endPosDetails.line,
    +        column: endPosDetails.column,
    +      },
    +    };
    +    if (offset && peg$source && (typeof peg$source.offset === "function")) {
    +      res.start = peg$source.offset(res.start);
    +      res.end = peg$source.offset(res.end);
    +    }
    +    return res;
    +  }
    +
    +  function peg$fail(expected) {
    +    if (peg$currPos < peg$maxFailPos) { return; }
    +
    +    if (peg$currPos > peg$maxFailPos) {
    +      peg$maxFailPos = peg$currPos;
    +      peg$maxFailExpected = [];
    +    }
    +
    +    peg$maxFailExpected.push(expected);
    +  }
    +
    +  function peg$buildSimpleError(message, location) {
    +    return new peg$SyntaxError(message, null, null, location);
    +  }
    +
    +  function peg$buildStructuredError(expected, found, location) {
    +    return new peg$SyntaxError(
    +      peg$SyntaxError.buildMessage(expected, found),
    +      expected,
    +      found,
    +      location
    +    );
    +  }
    +
    +  function peg$parseDocument() {
    +    let s0, s1, s2;
    +
    +    s0 = peg$currPos;
    +    s1 = [];
    +    s2 = peg$parseSegment();
    +    while (s2 !== peg$FAILED) {
    +      s1.push(s2);
    +      s2 = peg$parseSegment();
    +    }
    +    peg$savedPos = s0;
    +    s1 = peg$f0(s1);
    +    s0 = s1;
    +
    +    return s0;
    +  }
    +
    +  function peg$parseSegment() {
    +    let s0;
    +
    +    s0 = peg$parseEnv();
    +    if (s0 === peg$FAILED) {
    +      s0 = peg$parseStrayEnd();
    +      if (s0 === peg$FAILED) {
    +        s0 = peg$parseLine();
    +      }
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseStrayEnd() {
    +    let s0, s1;
    +
    +    s0 = peg$currPos;
    +    s1 = peg$parseEndTag();
    +    if (s1 !== peg$FAILED) {
    +      peg$savedPos = s0;
    +      s1 = peg$f1(s1);
    +    }
    +    s0 = s1;
    +
    +    return s0;
    +  }
    +
    +  function peg$parseEnv() {
    +    let s0;
    +
    +    s0 = peg$parseVerbatimEnv();
    +    if (s0 === peg$FAILED) {
    +      s0 = peg$parseRegularEnv();
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseVerbatimEnv() {
    +    let s0, s1, s2, s3, s4, s5;
    +
    +    s0 = peg$currPos;
    +    s1 = peg$parseBeginVerb();
    +    if (s1 !== peg$FAILED) {
    +      s2 = [];
    +      s3 = peg$currPos;
    +      s4 = peg$currPos;
    +      peg$silentFails++;
    +      s5 = peg$parseEndVerb();
    +      peg$silentFails--;
    +      if (s5 === peg$FAILED) {
    +        s4 = undefined;
    +      } else {
    +        peg$currPos = s4;
    +        s4 = peg$FAILED;
    +      }
    +      if (s4 !== peg$FAILED) {
    +        if (input.length > peg$currPos) {
    +          s5 = input.charAt(peg$currPos);
    +          peg$currPos++;
    +        } else {
    +          s5 = peg$FAILED;
    +          if (peg$silentFails === 0) { peg$fail(peg$e0); }
    +        }
    +        if (s5 !== peg$FAILED) {
    +          s4 = [s4, s5];
    +          s3 = s4;
    +        } else {
    +          peg$currPos = s3;
    +          s3 = peg$FAILED;
    +        }
    +      } else {
    +        peg$currPos = s3;
    +        s3 = peg$FAILED;
    +      }
    +      while (s3 !== peg$FAILED) {
    +        s2.push(s3);
    +        s3 = peg$currPos;
    +        s4 = peg$currPos;
    +        peg$silentFails++;
    +        s5 = peg$parseEndVerb();
    +        peg$silentFails--;
    +        if (s5 === peg$FAILED) {
    +          s4 = undefined;
    +        } else {
    +          peg$currPos = s4;
    +          s4 = peg$FAILED;
    +        }
    +        if (s4 !== peg$FAILED) {
    +          if (input.length > peg$currPos) {
    +            s5 = input.charAt(peg$currPos);
    +            peg$currPos++;
    +          } else {
    +            s5 = peg$FAILED;
    +            if (peg$silentFails === 0) { peg$fail(peg$e0); }
    +          }
    +          if (s5 !== peg$FAILED) {
    +            s4 = [s4, s5];
    +            s3 = s4;
    +          } else {
    +            peg$currPos = s3;
    +            s3 = peg$FAILED;
    +          }
    +        } else {
    +          peg$currPos = s3;
    +          s3 = peg$FAILED;
    +        }
    +      }
    +      s3 = peg$parseEndVerb();
    +      if (s3 !== peg$FAILED) {
    +        peg$savedPos = s0;
    +        s0 = peg$f2(s1, s2, s3);
    +      } else {
    +        peg$currPos = s0;
    +        s0 = peg$FAILED;
    +      }
    +    } else {
    +      peg$currPos = s0;
    +      s0 = peg$FAILED;
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseBeginVerb() {
    +    let s0, s1, s2, s3;
    +
    +    s0 = peg$currPos;
    +    if (input.substr(peg$currPos, 7) === peg$c0) {
    +      s1 = peg$c0;
    +      peg$currPos += 7;
    +    } else {
    +      s1 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e1); }
    +    }
    +    if (s1 !== peg$FAILED) {
    +      if (input.substr(peg$currPos, 8) === peg$c1) {
    +        s2 = peg$c1;
    +        peg$currPos += 8;
    +      } else {
    +        s2 = peg$FAILED;
    +        if (peg$silentFails === 0) { peg$fail(peg$e2); }
    +      }
    +      if (s2 === peg$FAILED) {
    +        if (input.substr(peg$currPos, 5) === peg$c2) {
    +          s2 = peg$c2;
    +          peg$currPos += 5;
    +        } else {
    +          s2 = peg$FAILED;
    +          if (peg$silentFails === 0) { peg$fail(peg$e3); }
    +        }
    +      }
    +      if (s2 !== peg$FAILED) {
    +        if (input.charCodeAt(peg$currPos) === 125) {
    +          s3 = peg$c3;
    +          peg$currPos++;
    +        } else {
    +          s3 = peg$FAILED;
    +          if (peg$silentFails === 0) { peg$fail(peg$e4); }
    +        }
    +        if (s3 !== peg$FAILED) {
    +          peg$savedPos = s0;
    +          s0 = peg$f3(s2);
    +        } else {
    +          peg$currPos = s0;
    +          s0 = peg$FAILED;
    +        }
    +      } else {
    +        peg$currPos = s0;
    +        s0 = peg$FAILED;
    +      }
    +    } else {
    +      peg$currPos = s0;
    +      s0 = peg$FAILED;
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseEndVerb() {
    +    let s0, s1, s2, s3;
    +
    +    s0 = peg$currPos;
    +    if (input.substr(peg$currPos, 5) === peg$c4) {
    +      s1 = peg$c4;
    +      peg$currPos += 5;
    +    } else {
    +      s1 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e5); }
    +    }
    +    if (s1 !== peg$FAILED) {
    +      if (input.substr(peg$currPos, 8) === peg$c1) {
    +        s2 = peg$c1;
    +        peg$currPos += 8;
    +      } else {
    +        s2 = peg$FAILED;
    +        if (peg$silentFails === 0) { peg$fail(peg$e2); }
    +      }
    +      if (s2 === peg$FAILED) {
    +        if (input.substr(peg$currPos, 5) === peg$c2) {
    +          s2 = peg$c2;
    +          peg$currPos += 5;
    +        } else {
    +          s2 = peg$FAILED;
    +          if (peg$silentFails === 0) { peg$fail(peg$e3); }
    +        }
    +      }
    +      if (s2 !== peg$FAILED) {
    +        if (input.charCodeAt(peg$currPos) === 125) {
    +          s3 = peg$c3;
    +          peg$currPos++;
    +        } else {
    +          s3 = peg$FAILED;
    +          if (peg$silentFails === 0) { peg$fail(peg$e4); }
    +        }
    +        if (s3 !== peg$FAILED) {
    +          peg$savedPos = s0;
    +          s0 = peg$f4(s2);
    +        } else {
    +          peg$currPos = s0;
    +          s0 = peg$FAILED;
    +        }
    +      } else {
    +        peg$currPos = s0;
    +        s0 = peg$FAILED;
    +      }
    +    } else {
    +      peg$currPos = s0;
    +      s0 = peg$FAILED;
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseRegularEnv() {
    +    let s0, s1, s2, s3, s4, s5;
    +
    +    s0 = peg$currPos;
    +    s1 = peg$parseBeginTag();
    +    if (s1 !== peg$FAILED) {
    +      s2 = peg$parse_();
    +      s3 = [];
    +      s4 = peg$parseEnvContent();
    +      while (s4 !== peg$FAILED) {
    +        s3.push(s4);
    +        s4 = peg$parseEnvContent();
    +      }
    +      s4 = peg$parse_();
    +      s5 = peg$parseEndTag();
    +      if (s5 === peg$FAILED) {
    +        s5 = null;
    +      }
    +      peg$savedPos = s0;
    +      s0 = peg$f5(s1, s3, s5);
    +    } else {
    +      peg$currPos = s0;
    +      s0 = peg$FAILED;
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseBeginTag() {
    +    let s0, s1, s2, s3, s4;
    +
    +    s0 = peg$currPos;
    +    if (input.substr(peg$currPos, 7) === peg$c0) {
    +      s1 = peg$c0;
    +      peg$currPos += 7;
    +    } else {
    +      s1 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e1); }
    +    }
    +    if (s1 !== peg$FAILED) {
    +      s2 = peg$parseEnvName();
    +      if (s2 !== peg$FAILED) {
    +        if (input.charCodeAt(peg$currPos) === 125) {
    +          s3 = peg$c3;
    +          peg$currPos++;
    +        } else {
    +          s3 = peg$FAILED;
    +          if (peg$silentFails === 0) { peg$fail(peg$e4); }
    +        }
    +        if (s3 !== peg$FAILED) {
    +          s4 = peg$parseTail();
    +          peg$savedPos = s0;
    +          s0 = peg$f6(s2, s4);
    +        } else {
    +          peg$currPos = s0;
    +          s0 = peg$FAILED;
    +        }
    +      } else {
    +        peg$currPos = s0;
    +        s0 = peg$FAILED;
    +      }
    +    } else {
    +      peg$currPos = s0;
    +      s0 = peg$FAILED;
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseEndTag() {
    +    let s0, s1, s2, s3;
    +
    +    s0 = peg$currPos;
    +    if (input.substr(peg$currPos, 5) === peg$c4) {
    +      s1 = peg$c4;
    +      peg$currPos += 5;
    +    } else {
    +      s1 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e5); }
    +    }
    +    if (s1 !== peg$FAILED) {
    +      s2 = peg$parseEnvName();
    +      if (s2 !== peg$FAILED) {
    +        if (input.charCodeAt(peg$currPos) === 125) {
    +          s3 = peg$c3;
    +          peg$currPos++;
    +        } else {
    +          s3 = peg$FAILED;
    +          if (peg$silentFails === 0) { peg$fail(peg$e4); }
    +        }
    +        if (s3 !== peg$FAILED) {
    +          peg$savedPos = s0;
    +          s0 = peg$f7(s2);
    +        } else {
    +          peg$currPos = s0;
    +          s0 = peg$FAILED;
    +        }
    +      } else {
    +        peg$currPos = s0;
    +        s0 = peg$FAILED;
    +      }
    +    } else {
    +      peg$currPos = s0;
    +      s0 = peg$FAILED;
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseEnvName() {
    +    let s0, s1, s2;
    +
    +    s0 = peg$currPos;
    +    s1 = [];
    +    s2 = input.charAt(peg$currPos);
    +    if (peg$r0.test(s2)) {
    +      peg$currPos++;
    +    } else {
    +      s2 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e6); }
    +    }
    +    if (s2 !== peg$FAILED) {
    +      while (s2 !== peg$FAILED) {
    +        s1.push(s2);
    +        s2 = input.charAt(peg$currPos);
    +        if (peg$r0.test(s2)) {
    +          peg$currPos++;
    +        } else {
    +          s2 = peg$FAILED;
    +          if (peg$silentFails === 0) { peg$fail(peg$e6); }
    +        }
    +      }
    +    } else {
    +      s1 = peg$FAILED;
    +    }
    +    if (s1 !== peg$FAILED) {
    +      peg$savedPos = s0;
    +      s1 = peg$f8(s1);
    +    }
    +    s0 = s1;
    +
    +    return s0;
    +  }
    +
    +  function peg$parseEnvContent() {
    +    let s0;
    +
    +    s0 = peg$parseEnv();
    +    if (s0 === peg$FAILED) {
    +      s0 = peg$parseCommand();
    +      if (s0 === peg$FAILED) {
    +        s0 = peg$parseLine();
    +      }
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseCommand() {
    +    let s0, s1, s2;
    +
    +    s0 = peg$currPos;
    +    s1 = peg$parseCommandStart();
    +    if (s1 !== peg$FAILED) {
    +      s2 = peg$parseTail();
    +      peg$savedPos = s0;
    +      s0 = peg$f9(s1, s2);
    +    } else {
    +      peg$currPos = s0;
    +      s0 = peg$FAILED;
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseCommandStart() {
    +    let s0, s1, s2, s3, s4, s5;
    +
    +    s0 = peg$currPos;
    +    if (input.charCodeAt(peg$currPos) === 92) {
    +      s1 = peg$c5;
    +      peg$currPos++;
    +    } else {
    +      s1 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e7); }
    +    }
    +    if (s1 !== peg$FAILED) {
    +      s2 = peg$currPos;
    +      peg$silentFails++;
    +      if (input.substr(peg$currPos, 6) === peg$c6) {
    +        s3 = peg$c6;
    +        peg$currPos += 6;
    +      } else {
    +        s3 = peg$FAILED;
    +        if (peg$silentFails === 0) { peg$fail(peg$e8); }
    +      }
    +      peg$silentFails--;
    +      if (s3 === peg$FAILED) {
    +        s2 = undefined;
    +      } else {
    +        peg$currPos = s2;
    +        s2 = peg$FAILED;
    +      }
    +      if (s2 !== peg$FAILED) {
    +        s3 = peg$currPos;
    +        peg$silentFails++;
    +        if (input.substr(peg$currPos, 4) === peg$c7) {
    +          s4 = peg$c7;
    +          peg$currPos += 4;
    +        } else {
    +          s4 = peg$FAILED;
    +          if (peg$silentFails === 0) { peg$fail(peg$e9); }
    +        }
    +        peg$silentFails--;
    +        if (s4 === peg$FAILED) {
    +          s3 = undefined;
    +        } else {
    +          peg$currPos = s3;
    +          s3 = peg$FAILED;
    +        }
    +        if (s3 !== peg$FAILED) {
    +          s4 = [];
    +          s5 = input.charAt(peg$currPos);
    +          if (peg$r1.test(s5)) {
    +            peg$currPos++;
    +          } else {
    +            s5 = peg$FAILED;
    +            if (peg$silentFails === 0) { peg$fail(peg$e10); }
    +          }
    +          if (s5 !== peg$FAILED) {
    +            while (s5 !== peg$FAILED) {
    +              s4.push(s5);
    +              s5 = input.charAt(peg$currPos);
    +              if (peg$r1.test(s5)) {
    +                peg$currPos++;
    +              } else {
    +                s5 = peg$FAILED;
    +                if (peg$silentFails === 0) { peg$fail(peg$e10); }
    +              }
    +            }
    +          } else {
    +            s4 = peg$FAILED;
    +          }
    +          if (s4 !== peg$FAILED) {
    +            peg$savedPos = s0;
    +            s0 = peg$f10(s4);
    +          } else {
    +            peg$currPos = s0;
    +            s0 = peg$FAILED;
    +          }
    +        } else {
    +          peg$currPos = s0;
    +          s0 = peg$FAILED;
    +        }
    +      } else {
    +        peg$currPos = s0;
    +        s0 = peg$FAILED;
    +      }
    +    } else {
    +      peg$currPos = s0;
    +      s0 = peg$FAILED;
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseTail() {
    +    let s0, s1, s2;
    +
    +    s0 = peg$currPos;
    +    s1 = [];
    +    s2 = peg$parseTailPart();
    +    while (s2 !== peg$FAILED) {
    +      s1.push(s2);
    +      s2 = peg$parseTailPart();
    +    }
    +    peg$savedPos = s0;
    +    s1 = peg$f11(s1);
    +    s0 = s1;
    +
    +    return s0;
    +  }
    +
    +  function peg$parseTailPart() {
    +    let s0, s1, s2, s3, s4, s5, s6;
    +
    +    s0 = peg$parseComment();
    +    if (s0 === peg$FAILED) {
    +      s0 = peg$currPos;
    +      s1 = peg$parseOpen();
    +      if (s1 !== peg$FAILED) {
    +        peg$savedPos = s0;
    +        s1 = peg$f12();
    +      }
    +      s0 = s1;
    +      if (s0 === peg$FAILED) {
    +        s0 = peg$currPos;
    +        s1 = peg$parseClose();
    +        if (s1 !== peg$FAILED) {
    +          peg$savedPos = s0;
    +          s1 = peg$f13();
    +        }
    +        s0 = s1;
    +        if (s0 === peg$FAILED) {
    +          s0 = peg$currPos;
    +          peg$savedPos = peg$currPos;
    +          s1 = peg$f14();
    +          if (s1) {
    +            s1 = undefined;
    +          } else {
    +            s1 = peg$FAILED;
    +          }
    +          if (s1 !== peg$FAILED) {
    +            s2 = peg$currPos;
    +            peg$silentFails++;
    +            s3 = peg$parseEOL();
    +            peg$silentFails--;
    +            if (s3 === peg$FAILED) {
    +              s2 = undefined;
    +            } else {
    +              peg$currPos = s2;
    +              s2 = peg$FAILED;
    +            }
    +            if (s2 !== peg$FAILED) {
    +              s3 = peg$currPos;
    +              peg$silentFails++;
    +              s4 = peg$parseCommandStart();
    +              peg$silentFails--;
    +              if (s4 === peg$FAILED) {
    +                s3 = undefined;
    +              } else {
    +                peg$currPos = s3;
    +                s3 = peg$FAILED;
    +              }
    +              if (s3 !== peg$FAILED) {
    +                s4 = peg$currPos;
    +                peg$silentFails++;
    +                s5 = peg$parseBeginStart();
    +                peg$silentFails--;
    +                if (s5 === peg$FAILED) {
    +                  s4 = undefined;
    +                } else {
    +                  peg$currPos = s4;
    +                  s4 = peg$FAILED;
    +                }
    +                if (s4 !== peg$FAILED) {
    +                  s5 = peg$currPos;
    +                  peg$silentFails++;
    +                  s6 = peg$parseEndStart();
    +                  peg$silentFails--;
    +                  if (s6 === peg$FAILED) {
    +                    s5 = undefined;
    +                  } else {
    +                    peg$currPos = s5;
    +                    s5 = peg$FAILED;
    +                  }
    +                  if (s5 !== peg$FAILED) {
    +                    if (input.length > peg$currPos) {
    +                      s6 = input.charAt(peg$currPos);
    +                      peg$currPos++;
    +                    } else {
    +                      s6 = peg$FAILED;
    +                      if (peg$silentFails === 0) { peg$fail(peg$e0); }
    +                    }
    +                    if (s6 !== peg$FAILED) {
    +                      peg$savedPos = s0;
    +                      s0 = peg$f15(s6);
    +                    } else {
    +                      peg$currPos = s0;
    +                      s0 = peg$FAILED;
    +                    }
    +                  } else {
    +                    peg$currPos = s0;
    +                    s0 = peg$FAILED;
    +                  }
    +                } else {
    +                  peg$currPos = s0;
    +                  s0 = peg$FAILED;
    +                }
    +              } else {
    +                peg$currPos = s0;
    +                s0 = peg$FAILED;
    +              }
    +            } else {
    +              peg$currPos = s0;
    +              s0 = peg$FAILED;
    +            }
    +          } else {
    +            peg$currPos = s0;
    +            s0 = peg$FAILED;
    +          }
    +          if (s0 === peg$FAILED) {
    +            s0 = peg$currPos;
    +            peg$savedPos = peg$currPos;
    +            s1 = peg$f16();
    +            if (s1) {
    +              s1 = undefined;
    +            } else {
    +              s1 = peg$FAILED;
    +            }
    +            if (s1 !== peg$FAILED) {
    +              if (input.length > peg$currPos) {
    +                s2 = input.charAt(peg$currPos);
    +                peg$currPos++;
    +              } else {
    +                s2 = peg$FAILED;
    +                if (peg$silentFails === 0) { peg$fail(peg$e0); }
    +              }
    +              if (s2 !== peg$FAILED) {
    +                peg$savedPos = s0;
    +                s0 = peg$f17(s2);
    +              } else {
    +                peg$currPos = s0;
    +                s0 = peg$FAILED;
    +              }
    +            } else {
    +              peg$currPos = s0;
    +              s0 = peg$FAILED;
    +            }
    +          }
    +        }
    +      }
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseComment() {
    +    let s0, s1, s2, s3, s4, s5;
    +
    +    s0 = peg$currPos;
    +    if (input.charCodeAt(peg$currPos) === 37) {
    +      s1 = peg$c8;
    +      peg$currPos++;
    +    } else {
    +      s1 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e11); }
    +    }
    +    if (s1 !== peg$FAILED) {
    +      s2 = [];
    +      s3 = peg$currPos;
    +      s4 = peg$currPos;
    +      peg$silentFails++;
    +      s5 = peg$parseEOL();
    +      peg$silentFails--;
    +      if (s5 === peg$FAILED) {
    +        s4 = undefined;
    +      } else {
    +        peg$currPos = s4;
    +        s4 = peg$FAILED;
    +      }
    +      if (s4 !== peg$FAILED) {
    +        if (input.length > peg$currPos) {
    +          s5 = input.charAt(peg$currPos);
    +          peg$currPos++;
    +        } else {
    +          s5 = peg$FAILED;
    +          if (peg$silentFails === 0) { peg$fail(peg$e0); }
    +        }
    +        if (s5 !== peg$FAILED) {
    +          s4 = [s4, s5];
    +          s3 = s4;
    +        } else {
    +          peg$currPos = s3;
    +          s3 = peg$FAILED;
    +        }
    +      } else {
    +        peg$currPos = s3;
    +        s3 = peg$FAILED;
    +      }
    +      while (s3 !== peg$FAILED) {
    +        s2.push(s3);
    +        s3 = peg$currPos;
    +        s4 = peg$currPos;
    +        peg$silentFails++;
    +        s5 = peg$parseEOL();
    +        peg$silentFails--;
    +        if (s5 === peg$FAILED) {
    +          s4 = undefined;
    +        } else {
    +          peg$currPos = s4;
    +          s4 = peg$FAILED;
    +        }
    +        if (s4 !== peg$FAILED) {
    +          if (input.length > peg$currPos) {
    +            s5 = input.charAt(peg$currPos);
    +            peg$currPos++;
    +          } else {
    +            s5 = peg$FAILED;
    +            if (peg$silentFails === 0) { peg$fail(peg$e0); }
    +          }
    +          if (s5 !== peg$FAILED) {
    +            s4 = [s4, s5];
    +            s3 = s4;
    +          } else {
    +            peg$currPos = s3;
    +            s3 = peg$FAILED;
    +          }
    +        } else {
    +          peg$currPos = s3;
    +          s3 = peg$FAILED;
    +        }
    +      }
    +      peg$savedPos = s0;
    +      s0 = peg$f18();
    +    } else {
    +      peg$currPos = s0;
    +      s0 = peg$FAILED;
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseOpen() {
    +    let s0;
    +
    +    s0 = input.charAt(peg$currPos);
    +    if (peg$r2.test(s0)) {
    +      peg$currPos++;
    +    } else {
    +      s0 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e12); }
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseClose() {
    +    let s0;
    +
    +    s0 = input.charAt(peg$currPos);
    +    if (peg$r3.test(s0)) {
    +      peg$currPos++;
    +    } else {
    +      s0 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e13); }
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseBeginStart() {
    +    let s0;
    +
    +    if (input.substr(peg$currPos, 7) === peg$c0) {
    +      s0 = peg$c0;
    +      peg$currPos += 7;
    +    } else {
    +      s0 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e1); }
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseEndStart() {
    +    let s0;
    +
    +    if (input.substr(peg$currPos, 5) === peg$c4) {
    +      s0 = peg$c4;
    +      peg$currPos += 5;
    +    } else {
    +      s0 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e5); }
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseLine() {
    +    let s0, s1, s2;
    +
    +    s0 = peg$currPos;
    +    s1 = [];
    +    s2 = peg$parseLinePart();
    +    if (s2 !== peg$FAILED) {
    +      while (s2 !== peg$FAILED) {
    +        s1.push(s2);
    +        s2 = peg$parseLinePart();
    +      }
    +    } else {
    +      s1 = peg$FAILED;
    +    }
    +    if (s1 !== peg$FAILED) {
    +      s2 = peg$parseEOL();
    +      if (s2 === peg$FAILED) {
    +        s2 = null;
    +      }
    +      peg$savedPos = s0;
    +      s0 = peg$f19(s1, s2);
    +    } else {
    +      peg$currPos = s0;
    +      s0 = peg$FAILED;
    +    }
    +    if (s0 === peg$FAILED) {
    +      s0 = peg$currPos;
    +      s1 = peg$parseEOL();
    +      if (s1 !== peg$FAILED) {
    +        peg$savedPos = s0;
    +        s1 = peg$f20(s1);
    +      }
    +      s0 = s1;
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseLinePart() {
    +    let s0, s1, s2, s3, s4;
    +
    +    s0 = peg$parseComment();
    +    if (s0 === peg$FAILED) {
    +      s0 = peg$parseCommand();
    +      if (s0 === peg$FAILED) {
    +        s0 = peg$currPos;
    +        s1 = peg$currPos;
    +        peg$silentFails++;
    +        s2 = peg$parseBeginStart();
    +        peg$silentFails--;
    +        if (s2 === peg$FAILED) {
    +          s1 = undefined;
    +        } else {
    +          peg$currPos = s1;
    +          s1 = peg$FAILED;
    +        }
    +        if (s1 !== peg$FAILED) {
    +          s2 = peg$currPos;
    +          peg$silentFails++;
    +          s3 = peg$parseEndStart();
    +          peg$silentFails--;
    +          if (s3 === peg$FAILED) {
    +            s2 = undefined;
    +          } else {
    +            peg$currPos = s2;
    +            s2 = peg$FAILED;
    +          }
    +          if (s2 !== peg$FAILED) {
    +            s3 = peg$currPos;
    +            peg$silentFails++;
    +            s4 = peg$parseEOL();
    +            peg$silentFails--;
    +            if (s4 === peg$FAILED) {
    +              s3 = undefined;
    +            } else {
    +              peg$currPos = s3;
    +              s3 = peg$FAILED;
    +            }
    +            if (s3 !== peg$FAILED) {
    +              if (input.length > peg$currPos) {
    +                s4 = input.charAt(peg$currPos);
    +                peg$currPos++;
    +              } else {
    +                s4 = peg$FAILED;
    +                if (peg$silentFails === 0) { peg$fail(peg$e0); }
    +              }
    +              if (s4 !== peg$FAILED) {
    +                peg$savedPos = s0;
    +                s0 = peg$f21(s4);
    +              } else {
    +                peg$currPos = s0;
    +                s0 = peg$FAILED;
    +              }
    +            } else {
    +              peg$currPos = s0;
    +              s0 = peg$FAILED;
    +            }
    +          } else {
    +            peg$currPos = s0;
    +            s0 = peg$FAILED;
    +          }
    +        } else {
    +          peg$currPos = s0;
    +          s0 = peg$FAILED;
    +        }
    +      }
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parseEOL() {
    +    let s0;
    +
    +    if (input.substr(peg$currPos, 2) === peg$c9) {
    +      s0 = peg$c9;
    +      peg$currPos += 2;
    +    } else {
    +      s0 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e14); }
    +    }
    +    if (s0 === peg$FAILED) {
    +      s0 = input.charAt(peg$currPos);
    +      if (peg$r4.test(s0)) {
    +        peg$currPos++;
    +      } else {
    +        s0 = peg$FAILED;
    +        if (peg$silentFails === 0) { peg$fail(peg$e15); }
    +      }
    +    }
    +
    +    return s0;
    +  }
    +
    +  function peg$parse_() {
    +    let s0, s1;
    +
    +    s0 = [];
    +    s1 = input.charAt(peg$currPos);
    +    if (peg$r5.test(s1)) {
    +      peg$currPos++;
    +    } else {
    +      s1 = peg$FAILED;
    +      if (peg$silentFails === 0) { peg$fail(peg$e16); }
    +    }
    +    while (s1 !== peg$FAILED) {
    +      s0.push(s1);
    +      s1 = input.charAt(peg$currPos);
    +      if (peg$r5.test(s1)) {
    +        peg$currPos++;
    +      } else {
    +        s1 = peg$FAILED;
    +        if (peg$silentFails === 0) { peg$fail(peg$e16); }
    +      }
    +    }
    +
    +    return s0;
    +  }
    +
    +
    +  let depth = 0;
    +
    +  function loc() {
    +    const l = location();
    +    return { line: l.start.line, column: l.start.column };
    +  }
    +
    +  peg$result = peg$startRuleFunction();
    +
    +  const peg$success = (peg$result !== peg$FAILED && peg$currPos === input.length);
    +  function peg$throw() {
    +    if (peg$result !== peg$FAILED && peg$currPos < input.length) {
    +      peg$fail(peg$endExpectation());
    +    }
    +
    +    throw peg$buildStructuredError(
    +      peg$maxFailExpected,
    +      peg$maxFailPos < input.length ? peg$getUnicode(peg$maxFailPos) : null,
    +      peg$maxFailPos < input.length
    +        ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)
    +        : peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
    +    );
    +  }
    +  if (options.peg$library) {
    +    return /** @type {any} */ ({
    +      peg$result,
    +      peg$currPos,
    +      peg$FAILED,
    +      peg$maxFailExpected,
    +      peg$maxFailPos,
    +      peg$success,
    +      peg$throw: peg$success ? undefined : peg$throw,
    +    });
    +  }
    +  if (peg$success) {
    +    return peg$result;
    +  } else {
    +    peg$throw();
    +  }
    +}
    +
    +module.exports = {
    +  StartRules: ["Document"],
    +  SyntaxError: peg$SyntaxError,
    +  parse: peg$parse,
    +};
    diff --git a/packages/latex2js/src/index.ts b/packages/latex2js/src/index.ts
    index 5688feaf..57078c2a 100644
    --- a/packages/latex2js/src/index.ts
    +++ b/packages/latex2js/src/index.ts
    @@ -14,6 +14,7 @@ export default class LaTeX2HTML5 {
       PSTricks: any;
       Views: any;
       Delimiters: any;
    +  lastDiagnostics: any[] = [];
     
       constructor(
         Text = TextExt,
    @@ -79,6 +80,7 @@ export default class LaTeX2HTML5 {
       parse(text: string): any[] {
         const parser = new Parser(this);
         const parsed = parser.parse(text);
    +    this.lastDiagnostics = parser.diagnostics;
         parsed.forEach((element) => {
           if (!element.hasOwnProperty('type')) {
             throw new Error('no type!');
    diff --git a/packages/latex2js/src/lib/environments.ts b/packages/latex2js/src/lib/environments.ts
    index f8b02b22..0e05cab4 100644
    --- a/packages/latex2js/src/lib/environments.ts
    +++ b/packages/latex2js/src/lib/environments.ts
    @@ -1,3 +1,3 @@
    -const environments: string[] = ['pspicture', 'verbatim', 'enumerate', 'print', 'nicebox'];
    +const environments: string[] = ['pspicture', 'verbatim', 'enumerate', 'print', 'nicebox', 'itemize', 'description'];
     
     export default environments;
    diff --git a/packages/latex2js/src/lib/headers.ts b/packages/latex2js/src/lib/headers.ts
    index 5cdbebd6..6df77df0 100644
    --- a/packages/latex2js/src/lib/headers.ts
    +++ b/packages/latex2js/src/lib/headers.ts
    @@ -3,10 +3,24 @@ export const Expressions = {
       claim: /\\begin\{claim\}/,
       corollary: /\\begin\{corollary\}/,
       definition: /\\begin\{definition\}/,
    +  lemma: /\\begin\{lemma\}/,
    +  proposition: /\\begin\{proposition\}/,
    +  axiom: /\\begin\{axiom\}/,
    +  remark: /\\begin\{remark\}/,
    +  note: /\\begin\{note\}/,
    +  exercise: /\\begin\{exercise\}/,
    +  question: /\\begin\{question\}/,
       endclaim: /\\end\{claim\}/,
    -  endcorallary: /\\end\{corallary\}/,
    +  endcorollary: /\\end\{corollary\}/,
       enddefinition: /\\end\{definition\}/,
       endexample: /\\end\{example\}/,
    +  endlemma: /\\end\{lemma\}/,
    +  endproposition: /\\end\{proposition\}/,
    +  endaxiom: /\\end\{axiom\}/,
    +  endremark: /\\end\{remark\}/,
    +  endnote: /\\end\{note\}/,
    +  endexercise: /\\end\{exercise\}/,
    +  endquestion: /\\end\{question\}/,
       endproblem: /\\end\{problem\}/,
       endsolution: /\\end\{solution\}/,
       endtheorem: /\\end\{theorem\}/,
    @@ -24,10 +38,24 @@ export const Functions = {
       claim: () => '

    Claim

    ', corollary: () => '

    Corollary

    ', definition: () => '

    Definition

    ', + lemma: () => '

    Lemma

    ', + proposition: () => '

    Proposition

    ', + axiom: () => '

    Axiom

    ', + remark: () => '

    Remark

    ', + note: () => '

    Note

    ', + exercise: () => '

    Exercise

    ', + question: () => '

    Question

    ', endclaim: () => '', endcorollary: () => '', enddefinition: () => '', endexample: () => '', + endlemma: () => '', + endproposition: () => '', + endaxiom: () => '', + endremark: () => '', + endnote: () => '', + endexercise: () => '', + endquestion: () => '', endproblem: () => '', endsolution: () => '', endtheorem: () => '', diff --git a/packages/latex2js/src/lib/parser.ts b/packages/latex2js/src/lib/parser.ts index 7f2180b4..f321dd0a 100644 --- a/packages/latex2js/src/lib/parser.ts +++ b/packages/latex2js/src/lib/parser.ts @@ -1,3 +1,60 @@ +import * as pegParser from '../grammar/parser.js'; + +export interface Diagnostic { + severity: 'error' | 'warning'; + message: string; + line?: number; + column?: number; +} + +interface Loc { + line: number; + column: number; +} + +interface LineNode { + kind: 'line'; + parts: Array<{ kind: 'char'; c: string } | { kind: 'comment' } | CommandNode>; + hasEol: boolean; + loc: Loc; +} + +interface CommandNode { + kind: 'command'; + name: string; + raw: string; + loc: Loc; +} + +interface EnvNode { + kind: 'env'; + name: string; + verbatim: boolean; + begin: { name: string; raw: string; loc: Loc }; + end: { name: string; raw: string; loc: Loc } | null; + content: Array; + loc: Loc; +} + +type Segment = + | LineNode + | CommandNode + | EnvNode + | { kind: 'strayEnd'; name: string; raw: string; loc: Loc } + | { kind: 'raw'; text: string }; + +/** + * Parser: turns a LaTeX-ish document into the flat environment objects the + * components consume ({type, lines, env, plot}) — but driven by the Peggy + * grammar in src/grammar instead of per-line regular expressions. + * + * The grammar tokenizes structure (balanced environments, commands with args, + * comments, verbatim). This class interprets that tree using the registries + * (Text / Headers / Ignore / PSTricks / Delimiters), so the runtime extension + * API (addEnvironment / addText / addHeaders) keeps working. It also collects + * diagnostics (unclosed environments, unknown commands, syntax errors) that + * were previously silent. + */ class Parser { Ignore: any; Delimiters: any; @@ -7,6 +64,7 @@ class Parser { objects: any[]; environment: any; settings: any; + diagnostics: Diagnostic[]; constructor(LaTeX2JS: any) { this.Ignore = LaTeX2JS.Ignore; @@ -20,32 +78,211 @@ class Parser { '', 'units=1cm,linecolor=black,linestyle=solid,fillstyle=none' ]); + this.diagnostics = []; } + + // ------------------------------------------------------------------------- + // Public API + // ------------------------------------------------------------------------- + parse(text: string): any[] { + this.diagnostics = []; if (!text) return []; - var lines = text.split('\n'); - this.parseEnvText(lines); - this.parseEnv(lines); - + const tree = this.parseTree(text); + this.walk(tree); this.objects.forEach((obj) => { if (obj.type.match(/pspicture/)) { - obj.plot = this.parsePSTricks(obj.lines, obj.env); + obj.plot = this.parsePSTricks(obj.commands || [], obj.env); + delete obj.commands; } }); return this.objects; } - newEnvironment(type: string): void { - if (this.environment && this.environment.lines.length) { - this.environment.settings = { ...this.settings }; - this.objects.push(this.environment); + // ------------------------------------------------------------------------- + // Grammar integration + // ------------------------------------------------------------------------- + + parseTree(text: string): Segment[] { + try { + return pegParser.parse(text); + } catch (err: any) { + const loc = err.location || { start: { line: 1, column: 1 } }; + this.diagnostics.push({ + severity: 'error', + message: `parse error: ${err.message || String(err)}`, + line: loc.start.line, + column: loc.start.column + }); + // Degraded fallback: treat the whole input as a math text block. + return [{ kind: 'raw', text: text }]; } - this.environment = { - type: type, - lines: [] - }; } + // ------------------------------------------------------------------------- + // Tree walk + // ------------------------------------------------------------------------- + + walk(segments: Segment[]): void { + this.objects = []; + this.environment = { type: 'math', lines: [] }; + segments.forEach((seg) => this.walkSegment(seg)); + this.newEnvironment('math'); + } + + walkSegment(seg: Segment): void { + if (seg.kind === 'raw') { + seg.text.split('\n').forEach((line: string) => this.pushMathLine(line)); + return; + } + switch (seg.kind) { + case 'line': + this.walkContent(seg); + break; + case 'env': + this.walkEnv(seg as EnvNode); + break; + case 'strayEnd': + if (this.isIgnored(seg.raw)) return; + this.diagnose('warning', `unexpected \\end{${seg.name}}`, seg.loc); + break; + } + } + + walkEnv(env: EnvNode): void { + const name = env.name; + + // Ignored wrapper environments (center, document, interactive…) are + // dropped, but their content is still walked in the current context. + if (this.isIgnoredEnv(name)) { + env.content.forEach((c) => this.walkContent(c)); + return; + } + + const structural = env.verbatim || !!this.Delimiters[name]; + if (!structural) { + // Non-structural environments (theorem, proof, quotation…) flatten into + // the current environment as header text (handled by the Headers pass). + const inPspicture = this.inPspicture(); + if (inPspicture) this.pushLine(env.begin.raw); + else this.pushMathLine(env.begin.raw); + env.content.forEach((c) => this.walkContent(c)); + if (env.end) { + if (inPspicture) this.pushLine(env.end.raw); + else this.pushMathLine(env.end.raw); + } else { + this.diagnose('warning', `unclosed \\begin{${name}}`, env.begin.loc); + } + return; + } + + // Structural environment: close the current one and open a new one. + this.newEnvironment(name); + if (!env.verbatim) this.metaData(name, env); + + if (env.verbatim) { + const v = env.content[0]; + this.environment.lines = v && v.kind === 'verbatim' ? v.text.split('\n') : []; + } else if (name.match(/pspicture/)) { + this.environment.commands = []; + env.content.forEach((c) => this.walkContent(c)); + } else { + // enumerate / nicebox: content is text lines (with transforms). + env.content.forEach((c) => this.walkContent(c)); + } + + if (env.end && env.end.name !== name) { + this.diagnose( + 'warning', + `\\end{${env.end.name}} does not match \\begin{${name}}`, + env.end.loc + ); + } else if (!env.end) { + this.diagnose('warning', `unclosed environment '${name}'`, env.begin.loc); + } + this.newEnvironment('math'); + } + + /** + * Walk one node of environment content. Behavior depends on the current + * environment: inside pspicture we collect commands (and raw lines) for plot + * extraction; elsewhere lines go through the text/header passes. + */ + walkContent(node: any): void { + const inPspicture = this.inPspicture(); + + switch (node.kind) { + case 'line': { + // Comment-only lines are dropped (mirrors the old /^%/ ignore rule). + const allComments = + node.parts.length > 0 && node.parts.every((p: any) => p.kind === 'comment'); + if (allComments) return; + if (node.parts.length === 0) { + this.pushBlankLine(inPspicture); + return; + } + const text = this.lineToString(node); + if (inPspicture) this.pushLine(text); + else this.pushMathLine(text); + break; + } + case 'command': { + if (node.name === 'psset') { + this.parseUnits(node.raw); + return; + } + if (inPspicture) this.environment.commands.push(node); + else this.pushMathLine(node.raw); + break; + } + case 'env': + this.walkEnv(node); + break; + default: + break; + } + } + + /** + * Convert a Line node's parts back to a string, dropping comment fragments. + */ + lineToString(line: LineNode): string { + return line.parts + .filter((p) => p.kind !== 'comment') + .map((p) => (p.kind === 'char' ? p.c : (p as CommandNode).raw)) + .join(''); + } + + // ------------------------------------------------------------------------- + // Line handling + // ------------------------------------------------------------------------- + + inPspicture(): boolean { + return !!(this.environment && this.environment.type.match(/pspicture/)); + } + + pushBlankLine(inPspicture: boolean): void { + if (inPspicture) return; + if (this.inPspicture()) return; + this.environment.lines.push('
    '); + } + + pushMathLine(text: string): void { + if (this.isIgnored(text)) return; + if (!text.trim().length) { + this.environment.lines.push('
    '); + return; + } + if (this.PSTricks.Expressions.psset.test(text)) { + this.parseUnits(text); + return; + } + const processed = this.parseText(text); + if (processed.trim().length) this.environment.lines.push(processed); + } + + /** Raw line inside a pspicture: no text/header transforms (they corrupt + * PSTricks content). */ pushLine(line: string): void { var add = true; this.Ignore.forEach((exp: RegExp) => { @@ -53,27 +290,58 @@ class Parser { add = false; } }); - if (add) { - if (typeof line === 'string' && line.trim().length) { - if (this.PSTricks.Expressions.psset.test(line)) { - this.parseUnits(line); - } else { - this.environment.lines.push(line); - } + if (add && typeof line === 'string' && line.trim().length) { + if (this.PSTricks.Expressions.psset.test(line)) { + this.parseUnits(line); + } else { + this.environment.lines.push(line); } } } + isIgnored(line: string): boolean { + return this.Ignore.some((exp: RegExp) => exp.test(line)); + } + + isIgnoredEnv(name: string): boolean { + return this.isIgnored('\\begin{' + name + '}'); + } + + newEnvironment(type: string): void { + if ( + this.environment && + (this.environment.lines.length || this.environment.type !== 'math') + ) { + this.environment.settings = { ...this.settings }; + this.objects.push(this.environment); + } + this.environment = { + type: type, + lines: [] + }; + } + parseUnits(line: string): void { - var m = line.match(this.PSTricks.Expressions.psset); + var m = line.replace(/\n/g, ' ').match(this.PSTricks.Expressions.psset); Object.assign(this.settings, this.PSTricks.Functions.psset.call(this, m)); } - metaData(environment: string, line: string): void { + metaData(environment: string, envNode: EnvNode): void { if (this.PSTricks.Expressions.hasOwnProperty(environment)) { - this.environment.match = line.match( - this.PSTricks.Expressions[environment] - ); + this.environment.match = envNode.begin.raw + .replace(/\n/g, ' ') + .match(this.PSTricks.Expressions[environment]); + if (!this.environment.match) { + this.diagnose( + 'error', + `could not parse \\begin{${environment}} arguments`, + envNode.begin.loc + ); + this.environment.env = {}; + this.environment.env.xunit = this.settings.xunit; + this.environment.env.yunit = this.settings.yunit; + return; + } this.environment.env = this.PSTricks.Functions[environment].call( this.settings, this.environment.match @@ -89,144 +357,161 @@ class Parser { } } - parseEnv(lines: string[]): void { - this.objects = []; - this.environment = { - type: 'math', - lines: [] - }; - const Delimiters = this.Delimiters; - - lines.forEach((line) => { - var isDelim = false; - Object.entries(Delimiters).forEach(([env, type]: [string, any]) => { - Object.entries(type).forEach(([k, delim]: [string, any]) => { - if (line.match(delim)) { - isDelim = true; - if (k.match(/begin/)) { - if (this.environment.type.match(/verbatim/)) { - isDelim = false; - } else if (this.environment.type.match(/print/)) { - isDelim = false; - } else { - this.newEnvironment(env); - this.metaData(env, line); - } - } else if (k.match(/end/)) { - if (this.environment.type.match(/verbatim/)) { - if (env.match(/verbatim/)) { - this.newEnvironment('math'); - } else { - isDelim = false; - } - } else if (this.environment.type.match(/print/)) { - if (env.match(/print/)) { - this.newEnvironment('math'); - } else { - isDelim = false; - } - } else { - this.newEnvironment('math'); - } - } - } - }); - }); - if (!isDelim) this.pushLine(line); // } + // ------------------------------------------------------------------------- + // PSTricks command extraction (ordered) + // ------------------------------------------------------------------------- + + /** + * Extract plot data from the ordered command nodes of a pspicture. + * Returns the grouped `plot` map (keyed by command type, used by the + * interactive re-render paths) and records the ordered `elements` list on + * the env for source-order initial rendering. + */ + parsePSTricks(commands: CommandNode[], env: any): any { + var plot: { [key: string]: any[] } = {}; + const entries = Object.entries(this.PSTricks.Expressions); + entries.forEach(([k, _exp]) => { + plot[k] = []; }); - // push last! - this.newEnvironment('math'); + const elements: any[] = []; + this.extractCommands(commands, env, plot, elements); + env.elements = elements; + return plot; } - parseEnvText(lines: string[]): void { - var _env = 'math'; - const Delimiters = this.Delimiters; - lines.forEach((line, i) => { - var isDelim = false; - Object.entries(Delimiters).forEach(([env, type]: [string, any]) => { - Object.entries(type).forEach(([k, delim]: [string, any]) => { - if (line.match(delim)) { - isDelim = true; - if (k.match(/begin/)) { - if (!_env.match(/verbatim/)) { - _env = env; - } else { - isDelim = false; - } - } else if (k.match(/end/)) { - if (!_env.match(/verbatim/)) { - _env = 'math'; - } else { - if (!env.match(/verbatim/)) { - isDelim = false; - } else { - _env = 'math'; - } - } - } - } - }); - }); - if (!isDelim) { - if (!_env.match(/verbatim/)) { - lines[i] = this.parseText(line); - } - if (!line.trim().length) { - lines[i] = '
    '; + /** + * Extract one command node into `plot` (grouped) and `elements` (ordered). + * Recurses into `\multido` bodies (expanded, counter substituted) and + * `\pscustom` bodies (the renderer re-parses those itself — the command is + * kept as a single element with its raw body). + */ + extractCommands( + commands: CommandNode[], + env: any, + plot: { [key: string]: any[] }, + elements: any[] + ): void { + commands.forEach((node) => { + const k = node.name; + const exp = this.PSTricks.Expressions[k]; + if (!exp) { + this.diagnose('warning', `unknown command \\${k} in pspicture`, node.loc); + return; + } + // The grammar captures commands across lines; the semantic regexes are + // single-line, so collapse internal newlines before matching. + const raw = node.raw.replace(/\n/g, ' '); + const m = raw.match(exp); + if (!m) { + this.diagnose( + 'warning', + `could not parse \\${k}: ${JSON.stringify(node.raw)}`, + node.loc + ); + return; + } + const data = this.PSTricks.Functions[k].call(env, m); + + // \multido{var=start+step}{count}{body} — expand and recurse. + if (k === 'multido') { + this.expandMultido(data, env, plot, elements, node); + return; + } + + // \pscustom{...} — pre-extract the inner commands into pixel data so + // the renderer can build a single filled/stroked path. + if (k === 'pscustom' && data.body) { + data.commands = this.extractCustomBody(data.body, env); + } + + plot[k].push({ data: data, env: env, match: m, fn: this.PSTricks.Functions[k] }); + elements.push({ name: k, data: data, match: m, fn: this.PSTricks.Functions[k], loc: node.loc }); + + // side effects preserved from the old parser: + if (k === 'psaxes' && plot[k].length > 0) { + const axesData = plot[k][plot[k].length - 1].data; + if (axesData && axesData.dx !== undefined) { + env.dx = axesData.dx; + env.dy = axesData.dy; + env.origin = axesData.origin; } } + if (k === 'uservariable') { + env.variables = env.variables || {}; + env.variables[data.name] = data.value; + } }); } - parsePSExpression(line: string, exp: RegExp, plot: any, k: string, env: any): boolean { - var match = line.match(exp); - if (match) { - plot[k].push({ - data: this.PSTricks.Functions[k].call(env, match), - env: env, - match: match, - fn: this.PSTricks.Functions[k] + /** Expand a \multido loop into its constituent commands. */ + expandMultido( + data: any, + env: any, + plot: { [key: string]: any[] }, + elements: any[], + node: CommandNode + ): void { + if (!data.variable || !(data.count > 0) || !data.body) return; + const re = new RegExp('\\\\' + data.variable + '\\b', 'g'); + for (let i = 0; i < data.count; i++) { + const value = data.start + i * data.step; + const body = data.body.replace(re, String(value)); + this.commandNodesFrom(this.parseTree(body)).forEach((cmd) => { + this.extractCommands([cmd], env, plot, elements); }); - return true; } - return false; } - parsePSVariables(line: string, exp: RegExp, _plot: any, k: string, env: any): void { - var match = line.match(exp); - if (match) { - if (k.match(/uservariable/)) { - var dd = this.PSTricks.Functions[k].call(env, match); - env.variables = env.variables || {}; - env.variables[dd.name] = dd.value; + /** + * Extract the inner commands of a \pscustom body into pixel data for the + * renderer. Commands that need DOM/runtime handling (rput, slider, psset, + * nested pscustom, multido) are skipped. + */ + extractCustomBody(body: string, env: any): any[] { + const out: any[] = []; + const skip = ['rput', 'slider', 'psset', 'pspicture', 'pscustom', 'multido', 'uservariable']; + this.commandNodesFrom(this.parseTree(body)).forEach((node) => { + const k = node.name; + if (skip.indexOf(k) !== -1) return; + const exp = this.PSTricks.Expressions[k]; + if (!exp) return; + const m = node.raw.replace(/\n/g, ' ').match(exp); + if (!m) return; + try { + const data = this.PSTricks.Functions[k].call(env, m); + if (data) out.push({ key: k, data: data }); + } catch (err) { + /* ignore malformed inner commands */ } - } + }); + return out; } - parsePSTricks(lines: string[], env: any): any { - var plot: { [key: string]: any[] } = {}; - const entries = Object.entries(this.PSTricks.Expressions); - entries.forEach(([k, _exp]) => { - plot[k] = []; - }); - lines.forEach((line) => { - entries.forEach(([k, exp]: [string, any]) => { - this.parsePSVariables(line, exp, plot, k, env); - const result = this.parsePSExpression(line, exp, plot, k, env); - if (result && k === 'psaxes' && plot[k].length > 0) { - const axesData = plot[k][plot[k].length - 1].data; - if (axesData && axesData.dx !== undefined) { - env.dx = axesData.dx; - env.dy = axesData.dy; - env.origin = axesData.origin; - } - } - }); - }); - return plot; + /** + * Flatten parsed segments into an ordered list of command nodes, walking + * into line parts and nested environments. + */ + commandNodesFrom(segs: Segment[]): CommandNode[] { + const out: CommandNode[] = []; + const walk = (seg: any): void => { + if (seg.kind === 'command') out.push(seg); + else if (seg.kind === 'line') { + (seg.parts || []).forEach((p: any) => { + if (p.kind === 'command') out.push(p); + }); + } else if (seg.kind === 'env') { + (seg.content || []).forEach(walk); + } + }; + segs.forEach(walk); + return out; } + // ------------------------------------------------------------------------- + // Text / header transforms (reused from the old parser, string-based) + // ------------------------------------------------------------------------- + parseTextExpression(line: string, exp: RegExp, k: string, contents: string): string { var match = line.match(exp); if (match) { @@ -257,6 +542,19 @@ class Parser { return contents; } + + // ------------------------------------------------------------------------- + // Diagnostics + // ------------------------------------------------------------------------- + + diagnose(severity: 'error' | 'warning', message: string, loc?: Loc): void { + this.diagnostics.push({ + severity: severity, + message: message, + line: loc ? loc.line : undefined, + column: loc ? loc.column : undefined + }); + } } export default Parser; diff --git a/packages/latex2js/src/lib/text.ts b/packages/latex2js/src/lib/text.ts index 9de29540..839bbb0c 100644 --- a/packages/latex2js/src/lib/text.ts +++ b/packages/latex2js/src/lib/text.ts @@ -20,6 +20,24 @@ export const Expressions = { set: /\\set\{[^}]*\}/g, youtube: /\\youtube\{[^}]*\}/g, euler: /Euler\^/g, + textbf: /\\textbf\{[^}]*\}/g, + textit: /\\textit\{[^}]*\}/g, + texttt: /\\texttt\{[^}]*\}/g, + textrm: /\\textrm\{[^}]*\}/g, + textsc: /\\textsc\{[^}]*\}/g, + underline: /\\underline\{[^}]*\}/g, + overline: /\\overline\{[^}]*\}/g, + section: /\\section\{[^}]*\}/, + subsection: /\\subsection\{[^}]*\}/, + subsubsection: /\\subsubsection\{[^}]*\}/, + paragraph: /\\paragraph\{[^}]*\}/, + hspace: /\\hspace\{[^}]*\}/, + noindent: /\\noindent/g, + newpage: /\\newpage/g, + hrule: /\\hrule/g, + rule: /\\rule\{[^}]*\}\{[^}]*\}/g, + textcolor: /\\textcolor\{[^}]*\}\{[^}]*\}/g, + footnote: /\\footnote\{[^}]*\}/g, }; export const Functions = { @@ -90,6 +108,60 @@ export const Functions = { vspace: simplerepl(/\\vspace/g, '
    '), TeX: simplerepl(/\\TeX\\|\\TeX/g, '$\\TeX$'), LaTeX: simplerepl(/\\LaTeX\\|\\LaTeX/g, '$\\LaTeX$'), + textbf: matchrepl(/\\textbf\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '' + m[1] + ''; + }), + textit: matchrepl(/\\textit\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '' + m[1] + ''; + }), + texttt: matchrepl(/\\texttt\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '' + m[1] + ''; + }), + textrm: matchrepl(/\\textrm\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '' + m[1] + ''; + }), + textsc: matchrepl(/\\textsc\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '' + m[1] + ''; + }), + underline: matchrepl(/\\underline\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '' + m[1] + ''; + }), + overline: matchrepl(/\\overline\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '' + m[1] + ''; + }), + section: matchrepl(/\\section\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '

    ' + m[1] + '

    '; + }), + subsection: matchrepl(/\\subsection\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '

    ' + m[1] + '

    '; + }), + subsubsection: matchrepl(/\\subsubsection\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '

    ' + m[1] + '

    '; + }), + paragraph: matchrepl(/\\paragraph\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '
    ' + m[1] + '
    '; + }), + hspace: matchrepl(/\\hspace\{([^}]*)\}/, function(_m: RegExpMatchArray) { + return '  '; + }), + noindent: simplerepl(/\\noindent/g, ''), + newpage: simplerepl(/\\newpage/g, '

    '), + hrule: simplerepl(/\\hrule/g, '
    '), + rule: matchrepl(/\\rule\{([^}]*)\}\{([^}]*)\}/, function(m: RegExpMatchArray) { + return ( + '' + ); + }), + textcolor: matchrepl(/\\textcolor\{([^}]*)\}\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '' + m[2] + ''; + }), + footnote: matchrepl(/\\footnote\{([^}]*)\}/, function(m: RegExpMatchArray) { + return '' + m[1] + ''; + }), }; export default { diff --git a/packages/latex2js/test/__snapshots__/parser.test.ts.snap b/packages/latex2js/test/__snapshots__/parser.test.ts.snap index 9a22c7d3..a13511ea 100644 --- a/packages/latex2js/test/__snapshots__/parser.test.ts.snap +++ b/packages/latex2js/test/__snapshots__/parser.test.ts.snap @@ -7,6 +7,7 @@ exports[`Parser parse 1`] = ` "
    ", "Let's get to the point. The core of PSTricks is graphics!", "
    ", + "
    ", ], "settings": { "fillstyle": "none", @@ -21,6 +22,109 @@ exports[`Parser parse 1`] = ` }, { "env": { + "elements": [ + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 250, + "x2": 250, + "y1": 437.5, + "y2": 62.5, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 6, + }, + "match": [ + "\\psline{->}(0,-3.75)(0,3.75)", + undefined, + "{->}", + "0", + "-3.75", + "(0,3.75)", + "0", + "3.75", + ], + "name": "psline", + }, + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 62.5, + "x2": 437.5, + "y1": 250, + "y2": 250, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 7, + }, + "match": [ + "\\psline{->}(-3.75,0)(3.75,0)", + undefined, + "{->}", + "-3.75", + "0", + "(3.75,0)", + "3.75", + "0", + ], + "name": "psline", + }, + { + "data": { + "cx": 250, + "cy": 250, + "fillcolor": "black", + "filled": false, + "fillstyle": "none", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "r": 150, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 8, + }, + "match": [ + "\\pscircle(0,0){ 3 }", + "0", + "0", + " 3 ", + ], + "name": "pscircle", + }, + ], "h": 10, "w": 10, "x0": -5, @@ -30,11 +134,7 @@ exports[`Parser parse 1`] = ` "y1": 5, "yunit": 50, }, - "lines": [ - "\\psline{->}(0,-3.75)(0,3.75)", - "\\psline{->}(-3.75,0)(3.75,0)", - "\\pscircle(0,0){ 3 }", - ], + "lines": [], "match": [ "\\begin{pspicture}(-5,-5)(5,5)", "-5", @@ -43,16 +143,128 @@ exports[`Parser parse 1`] = ` "5", ], "plot": { + "multido": [], "psarc": [], "psaxes": [], + "psbezier": [], + "psccurve": [], "pscircle": [ { "data": { "cx": 250, "cy": 250, + "fillcolor": "black", + "filled": false, + "fillstyle": "none", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, "r": 150, }, "env": { + "elements": [ + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 250, + "x2": 250, + "y1": 437.5, + "y2": 62.5, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 6, + }, + "match": [ + "\\psline{->}(0,-3.75)(0,3.75)", + undefined, + "{->}", + "0", + "-3.75", + "(0,3.75)", + "0", + "3.75", + ], + "name": "psline", + }, + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 62.5, + "x2": 437.5, + "y1": 250, + "y2": 250, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 7, + }, + "match": [ + "\\psline{->}(-3.75,0)(3.75,0)", + undefined, + "{->}", + "-3.75", + "0", + "(3.75,0)", + "3.75", + "0", + ], + "name": "psline", + }, + { + "data": { + "cx": 250, + "cy": 250, + "fillcolor": "black", + "filled": false, + "fillstyle": "none", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "r": 150, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 8, + }, + "match": [ + "\\pscircle(0,0){ 3 }", + "0", + "0", + " 3 ", + ], + "name": "pscircle", + }, + ], "h": 10, "w": 10, "x0": -5, @@ -71,7 +283,13 @@ exports[`Parser parse 1`] = ` ], }, ], + "pscurve": [], + "pscustom": [], + "psdots": [], + "psecurve": [], + "psellipse": [], "psframe": [], + "psgrid": [], "psline": [ { "data": { @@ -84,6 +302,7 @@ exports[`Parser parse 1`] = ` 0, ], "fillcolor": "black", + "filled": false, "fillstyle": "solid", "linecolor": "black", "linestyle": "solid", @@ -94,6 +313,109 @@ exports[`Parser parse 1`] = ` "y2": 62.5, }, "env": { + "elements": [ + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 250, + "x2": 250, + "y1": 437.5, + "y2": 62.5, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 6, + }, + "match": [ + "\\psline{->}(0,-3.75)(0,3.75)", + undefined, + "{->}", + "0", + "-3.75", + "(0,3.75)", + "0", + "3.75", + ], + "name": "psline", + }, + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 62.5, + "x2": 437.5, + "y1": 250, + "y2": 250, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 7, + }, + "match": [ + "\\psline{->}(-3.75,0)(3.75,0)", + undefined, + "{->}", + "-3.75", + "0", + "(3.75,0)", + "3.75", + "0", + ], + "name": "psline", + }, + { + "data": { + "cx": 250, + "cy": 250, + "fillcolor": "black", + "filled": false, + "fillstyle": "none", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "r": 150, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 8, + }, + "match": [ + "\\pscircle(0,0){ 3 }", + "0", + "0", + " 3 ", + ], + "name": "pscircle", + }, + ], "h": 10, "w": 10, "x0": -5, @@ -126,6 +448,7 @@ exports[`Parser parse 1`] = ` 0, ], "fillcolor": "black", + "filled": false, "fillstyle": "solid", "linecolor": "black", "linestyle": "solid", @@ -136,6 +459,109 @@ exports[`Parser parse 1`] = ` "y2": 250, }, "env": { + "elements": [ + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 250, + "x2": 250, + "y1": 437.5, + "y2": 62.5, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 6, + }, + "match": [ + "\\psline{->}(0,-3.75)(0,3.75)", + undefined, + "{->}", + "0", + "-3.75", + "(0,3.75)", + "0", + "3.75", + ], + "name": "psline", + }, + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 62.5, + "x2": 437.5, + "y1": 250, + "y2": 250, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 7, + }, + "match": [ + "\\psline{->}(-3.75,0)(3.75,0)", + undefined, + "{->}", + "-3.75", + "0", + "(3.75,0)", + "3.75", + "0", + ], + "name": "psline", + }, + { + "data": { + "cx": 250, + "cy": 250, + "fillcolor": "black", + "filled": false, + "fillstyle": "none", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "r": 150, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 8, + }, + "match": [ + "\\pscircle(0,0){ 3 }", + "0", + "0", + " 3 ", + ], + "name": "pscircle", + }, + ], "h": 10, "w": 10, "x0": -5, @@ -162,6 +588,7 @@ exports[`Parser parse 1`] = ` "psplot": [], "pspolygon": [], "psset": [], + "pswedge": [], "rput": [], "slider": [], "userline": [], @@ -186,6 +613,8 @@ exports[`Parser parse 1`] = ` }, { "lines": [ + "
    ", + "
    ", "
    ", "which can be produced using the following $\\TeX$:", "
    ", @@ -209,11 +638,14 @@ exports[`Parser parse 1`] = ` }, { "lines": [ + "", + "\\begin{center}", "\\begin{pspicture}(-5,-5)(5,5)", "\\psline{->}(0,-3.75)(0,3.75)", "\\psline{->}(-3.75,0)(3.75,0)", "\\pscircle(0,0){ 3 }", "\\end{pspicture}", + "\\end{center}", ], "settings": { "fillstyle": "none", @@ -235,6 +667,7 @@ exports[`Parser parse 1`] = ` { "lines": [ "
    ", + "
    ", ], "settings": { "fillstyle": "none", @@ -263,6 +696,7 @@ exports[`Parser parser 1`] = ` "
    ", "Let's get to the point. The core of PSTricks is graphics!", "
    ", + "
    ", ], "settings": { "fillstyle": "none", @@ -277,6 +711,109 @@ exports[`Parser parser 1`] = ` }, { "env": { + "elements": [ + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 250, + "x2": 250, + "y1": 437.5, + "y2": 62.5, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 6, + }, + "match": [ + "\\psline{->}(0,-3.75)(0,3.75)", + undefined, + "{->}", + "0", + "-3.75", + "(0,3.75)", + "0", + "3.75", + ], + "name": "psline", + }, + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 62.5, + "x2": 437.5, + "y1": 250, + "y2": 250, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 7, + }, + "match": [ + "\\psline{->}(-3.75,0)(3.75,0)", + undefined, + "{->}", + "-3.75", + "0", + "(3.75,0)", + "3.75", + "0", + ], + "name": "psline", + }, + { + "data": { + "cx": 250, + "cy": 250, + "fillcolor": "black", + "filled": false, + "fillstyle": "none", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "r": 150, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 8, + }, + "match": [ + "\\pscircle(0,0){ 3 }", + "0", + "0", + " 3 ", + ], + "name": "pscircle", + }, + ], "h": 10, "w": 10, "x0": -5, @@ -286,11 +823,7 @@ exports[`Parser parser 1`] = ` "y1": 5, "yunit": 50, }, - "lines": [ - "\\psline{->}(0,-3.75)(0,3.75)", - "\\psline{->}(-3.75,0)(3.75,0)", - "\\pscircle(0,0){ 3 }", - ], + "lines": [], "match": [ "\\begin{pspicture}(-5,-5)(5,5)", "-5", @@ -299,16 +832,128 @@ exports[`Parser parser 1`] = ` "5", ], "plot": { + "multido": [], "psarc": [], "psaxes": [], + "psbezier": [], + "psccurve": [], "pscircle": [ { "data": { "cx": 250, "cy": 250, + "fillcolor": "black", + "filled": false, + "fillstyle": "none", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, "r": 150, }, "env": { + "elements": [ + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 250, + "x2": 250, + "y1": 437.5, + "y2": 62.5, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 6, + }, + "match": [ + "\\psline{->}(0,-3.75)(0,3.75)", + undefined, + "{->}", + "0", + "-3.75", + "(0,3.75)", + "0", + "3.75", + ], + "name": "psline", + }, + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 62.5, + "x2": 437.5, + "y1": 250, + "y2": 250, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 7, + }, + "match": [ + "\\psline{->}(-3.75,0)(3.75,0)", + undefined, + "{->}", + "-3.75", + "0", + "(3.75,0)", + "3.75", + "0", + ], + "name": "psline", + }, + { + "data": { + "cx": 250, + "cy": 250, + "fillcolor": "black", + "filled": false, + "fillstyle": "none", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "r": 150, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 8, + }, + "match": [ + "\\pscircle(0,0){ 3 }", + "0", + "0", + " 3 ", + ], + "name": "pscircle", + }, + ], "h": 10, "w": 10, "x0": -5, @@ -327,7 +972,13 @@ exports[`Parser parser 1`] = ` ], }, ], + "pscurve": [], + "pscustom": [], + "psdots": [], + "psecurve": [], + "psellipse": [], "psframe": [], + "psgrid": [], "psline": [ { "data": { @@ -340,6 +991,7 @@ exports[`Parser parser 1`] = ` 0, ], "fillcolor": "black", + "filled": false, "fillstyle": "solid", "linecolor": "black", "linestyle": "solid", @@ -350,6 +1002,109 @@ exports[`Parser parser 1`] = ` "y2": 62.5, }, "env": { + "elements": [ + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 250, + "x2": 250, + "y1": 437.5, + "y2": 62.5, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 6, + }, + "match": [ + "\\psline{->}(0,-3.75)(0,3.75)", + undefined, + "{->}", + "0", + "-3.75", + "(0,3.75)", + "0", + "3.75", + ], + "name": "psline", + }, + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 62.5, + "x2": 437.5, + "y1": 250, + "y2": 250, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 7, + }, + "match": [ + "\\psline{->}(-3.75,0)(3.75,0)", + undefined, + "{->}", + "-3.75", + "0", + "(3.75,0)", + "3.75", + "0", + ], + "name": "psline", + }, + { + "data": { + "cx": 250, + "cy": 250, + "fillcolor": "black", + "filled": false, + "fillstyle": "none", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "r": 150, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 8, + }, + "match": [ + "\\pscircle(0,0){ 3 }", + "0", + "0", + " 3 ", + ], + "name": "pscircle", + }, + ], "h": 10, "w": 10, "x0": -5, @@ -382,6 +1137,7 @@ exports[`Parser parser 1`] = ` 0, ], "fillcolor": "black", + "filled": false, "fillstyle": "solid", "linecolor": "black", "linestyle": "solid", @@ -392,6 +1148,109 @@ exports[`Parser parser 1`] = ` "y2": 250, }, "env": { + "elements": [ + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 250, + "x2": 250, + "y1": 437.5, + "y2": 62.5, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 6, + }, + "match": [ + "\\psline{->}(0,-3.75)(0,3.75)", + undefined, + "{->}", + "0", + "-3.75", + "(0,3.75)", + "0", + "3.75", + ], + "name": "psline", + }, + { + "data": { + "arrows": [ + 0, + 1, + ], + "dots": [ + 0, + 0, + ], + "fillcolor": "black", + "filled": false, + "fillstyle": "solid", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "x1": 62.5, + "x2": 437.5, + "y1": 250, + "y2": 250, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 7, + }, + "match": [ + "\\psline{->}(-3.75,0)(3.75,0)", + undefined, + "{->}", + "-3.75", + "0", + "(3.75,0)", + "3.75", + "0", + ], + "name": "psline", + }, + { + "data": { + "cx": 250, + "cy": 250, + "fillcolor": "black", + "filled": false, + "fillstyle": "none", + "linecolor": "black", + "linestyle": "solid", + "linewidth": 2, + "r": 150, + }, + "fn": [Function], + "loc": { + "column": 1, + "line": 8, + }, + "match": [ + "\\pscircle(0,0){ 3 }", + "0", + "0", + " 3 ", + ], + "name": "pscircle", + }, + ], "h": 10, "w": 10, "x0": -5, @@ -418,6 +1277,7 @@ exports[`Parser parser 1`] = ` "psplot": [], "pspolygon": [], "psset": [], + "pswedge": [], "rput": [], "slider": [], "userline": [], @@ -442,6 +1302,8 @@ exports[`Parser parser 1`] = ` }, { "lines": [ + "
    ", + "
    ", "
    ", "which can be produced using the following $\\TeX$:", "
    ", @@ -465,11 +1327,14 @@ exports[`Parser parser 1`] = ` }, { "lines": [ + "", + "\\begin{center}", "\\begin{pspicture}(-5,-5)(5,5)", "\\psline{->}(0,-3.75)(0,3.75)", "\\psline{->}(-3.75,0)(3.75,0)", "\\pscircle(0,0){ 3 }", "\\end{pspicture}", + "\\end{center}", ], "settings": { "fillstyle": "none", @@ -491,6 +1356,7 @@ exports[`Parser parser 1`] = ` { "lines": [ "
    ", + "
    ", ], "settings": { "fillstyle": "none", diff --git a/packages/latex2js/test/corpus.test.ts b/packages/latex2js/test/corpus.test.ts new file mode 100644 index 00000000..cd3600bd --- /dev/null +++ b/packages/latex2js/test/corpus.test.ts @@ -0,0 +1,45 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import LaTeX2JS from '../src'; + +/** + * Golden-corpus test: every example that ships on latex2js.com must parse + * without errors or unknown-command warnings. This guards the site's content + * against parser/engine regressions and documents which commands must exist. + */ +const corpusDir = path.join(__dirname, 'corpus'); +const files = fs.readdirSync(corpusDir).filter((f) => f.endsWith('.tex')); + +const latex = new LaTeX2JS(); + +describe.each(files)('corpus: %s', (file) => { + const tex = fs.readFileSync(path.join(corpusDir, file), 'utf8'); + + it('parses without errors or unknown-command warnings', () => { + const parsed = latex.parse(tex); + expect(parsed.length).toBeGreaterThan(0); + + const errors = latex.lastDiagnostics.filter((d: any) => d.severity === 'error'); + expect(errors).toEqual([]); + + const unknown = latex.lastDiagnostics.filter((d: any) => + d.message.includes('unknown command') + ); + expect(unknown).toEqual([]); + }); + + it('produces plot data for every pspicture', () => { + const parsed = latex.parse(tex); + parsed + .filter((e: any) => e.type === 'pspicture') + .forEach((env: any) => { + expect(env.plot).toBeDefined(); + expect(env.env.elements).toBeDefined(); + // every element must have resolvable data + env.env.elements.forEach((el: any) => { + expect(el.data).toBeDefined(); + expect(typeof el.name).toBe('string'); + }); + }); + }); +}); diff --git a/packages/latex2js/test/corpus/01.tex b/packages/latex2js/test/corpus/01.tex new file mode 100644 index 00000000..2f9c57f8 --- /dev/null +++ b/packages/latex2js/test/corpus/01.tex @@ -0,0 +1,25 @@ +\begin{pspicture}(0,-3)(8,3) +\rput(0,0){$x(t)$} +\rput(4,1.5){$f(t)$} +\rput(4,-1.5){$g(t)$} +\rput(8.2,0){$y(t)$} +\rput(1.5,-2){$h(t)$} +\psframe(1,-2.5)(7,2.5) +\psframe(3,1)(5,2) +\psframe(3,-1)(5,-2) +\rput(4,0){$X_k = \frac{1}{p} \sum \limits_{n=\langle p\rangle}x(n)e^{-ik\omega_0n}$} +\psline{->}(0.5,0)(1.5,0) +\psline{->}(1.5,1.5)(3,1.5) +\psline{->}(1.5,-1.5)(3,-1.5) +\psline{->}(6.5,1.5)(6.5,0.25) +\psline{->}(6.5,-1.5)(6.5,-0.25) +\psline{->}(6.75,0)(7.75,0) +\psline(1.5,-1.5)(1.5,1.5) +\psline(5,1.5)(6.5,1.5) +\psline(5,-1.5)(6.5,-1.5) +\psline(6,-1.5)(6.5,-1.5) +\pscircle(6.5,0){0.25} +\psline(6.25,0)(6.75,0) +\psline(6.5,0.5)(6.5,-0.5) +\end{pspicture} + diff --git a/packages/latex2js/test/corpus/02.tex b/packages/latex2js/test/corpus/02.tex new file mode 100644 index 00000000..27f46ae5 --- /dev/null +++ b/packages/latex2js/test/corpus/02.tex @@ -0,0 +1,4 @@ +\begin{pspicture}(-2,-2)(2,2) +\psframe(-2,-2)(2,2) +\userline[linewidth=1.5 pt]{->}(0,0)(2,2) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/03.tex b/packages/latex2js/test/corpus/03.tex new file mode 100644 index 00000000..b0b736d7 --- /dev/null +++ b/packages/latex2js/test/corpus/03.tex @@ -0,0 +1,7 @@ +\begin{pspicture}(-2,-2)(2,2) +\psframe(-2,-2)(2,2) +\userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} +\userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} +\userline[linewidth=2pt,linecolor=purple]{->}(0,0)(2,2){-x}{cos(y)} +\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2)(sin(x)}{-y} +\end{pspicture} diff --git a/packages/latex2js/test/corpus/04.tex b/packages/latex2js/test/corpus/04.tex new file mode 100644 index 00000000..b804a1ac --- /dev/null +++ b/packages/latex2js/test/corpus/04.tex @@ -0,0 +1,30 @@ +\begin{pspicture}(-5,-5)(5,5) + +% y-axis +\rput(0.3,3.75){ $Im$ } +\psline{->}(0,-3.75)(0,3.75) + +% x-axis +\rput(3.75,0.3){ $Re$ } +\psline{->}(-3.75,0)(3.75,0) + +% the circle +\pscircle(0,0){ 3 } + + % new vector +\rput(2.3,1){$e^{i\omega}-\alpha$} +\userline[linewidth=1.5 pt]{->}(1.500,0.000)(2.121,2.121) +\userline[linewidth=1.5 pt,linecolor=blue]{->}(0,0.000)(2.121,2.121){(x>0) ? 3 * cos( atan(-y/x) ) : -3 * cos( atan(-y/x) ) }{ (x>0) ? -3 * sin( atan(-y/x) ) : 3 * sin( atan(-y/x) )} + +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){x}{0}{x}{y} +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){0}{y}{x}{y} + +\rput(-0.75,-4.25){$1+\alpha$} +\rput(2.25,-4.25){$1-\alpha$} +\psline{<->}(-3,-4)(1.5,-4) +\psline{<->}(1.5,-4)(3,-4) +\psline[linestyle=dashed](3,-4.5)(3,0) +\psline[linestyle=dashed](-3,-4.5)(-3,0) +\psline[linestyle=dashed](1.5,-4.5)(1.5,0) + +\end{pspicture} diff --git a/packages/latex2js/test/corpus/05.tex b/packages/latex2js/test/corpus/05.tex new file mode 100644 index 00000000..ea99ef22 --- /dev/null +++ b/packages/latex2js/test/corpus/05.tex @@ -0,0 +1,6 @@ +\begin{pspicture}(-4,-3)(4,3) +\uservariable{alpha}(0,0){x} +\psplot[algebraic,linewidth=2pt,fillstyle=solid, fillcolor=lightblue]{-4}{alpha}{sin(x)} +\psplot[algebraic,linewidth=2pt]{-4}{4}{sin(x)} +\psline{->}(-4,0)(4,0) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/06.tex b/packages/latex2js/test/corpus/06.tex new file mode 100644 index 00000000..2bc6894d --- /dev/null +++ b/packages/latex2js/test/corpus/06.tex @@ -0,0 +1,7 @@ +\begin{pspicture}(-4,-3)(4,3) +\uservariable{alpha}(0,0){x} +\uservariable{beta}(0,0){y} +\psplot[algebraic,linewidth=2pt,fillstyle=solid, fillcolor=lightblue]{alpha-3}{alpha}{beta + sin(x)} +\psplot[algebraic,linewidth=2pt]{-4}{4}{beta + sin(x)} +\psline{->}(-4,0)(4,0) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/07.tex b/packages/latex2js/test/corpus/07.tex new file mode 100644 index 00000000..3ef6903c --- /dev/null +++ b/packages/latex2js/test/corpus/07.tex @@ -0,0 +1,21 @@ +\psset{unit=1cm} +\begin{pspicture}(-3.5,-1)(3.75,3.5) + +\slider{1}{8}{n}{$N$}{4} + +\psplot[algebraic,linewidth=1.5pt,plotpoints=1000]{-3.14}{3.14}{cos(n*x/2)+1.3} +\psaxes[showorigin=false,labels=none, Dx=1.62](0,0)(-3.25,0)(3.25,2.5) + +\psline[linestyle=dashed](-3.14,0.3)(3.14,0.3) +\psline[linestyle=dashed](-3.14,2.3)(3.14,2.3) +\rput(3.6,2.3){$\frac{1}{1-\alpha}$} +\rput(3.6,0.3){$\frac{1}{1+\alpha}$} + + +\rput(3.14, -0.35){$\pi$} +\rput(1.62, -0.35){$\pi/2$} +\rput(-1.62, -0.35){$-\pi/2$} +\rput(-3.14, -0.35){$-\pi$} +\rput(0, -0.35){$0$} + +\end{pspicture} diff --git a/packages/latex2js/test/corpus/08.tex b/packages/latex2js/test/corpus/08.tex new file mode 100644 index 00000000..b8d9b9a0 --- /dev/null +++ b/packages/latex2js/test/corpus/08.tex @@ -0,0 +1,12 @@ +\psset{unit=0.5cm} +\begin{pspicture}(-13,-5)(13,10) + +\slider{1}{8}{a}{amplitude}{4} +\slider{1}{8}{n}{frequency}{4} + +\psplot[algebraic,linewidth=1.5pt,plotpoints=1000]{-12.56}{12.56}{a*sin(n*x)/(n*x)} +\psaxes[showorigin=false,labels=none, Dx=3.14](0,0)(-12.6,0)(12.6,0) + +\rput(0, -0.5){$0$} + +\end{pspicture} diff --git a/packages/latex2js/test/corpus/09.tex b/packages/latex2js/test/corpus/09.tex new file mode 100644 index 00000000..48c224e2 --- /dev/null +++ b/packages/latex2js/test/corpus/09.tex @@ -0,0 +1,7 @@ +\psset{unit=0.75cm} +\begin{pspicture}(-4,-3)(4,6) +\uservariable{alpha}(0.1,0){x} +\psplot[algebraic,linewidth=2pt]{-4}{4}{pow(x,2)} +\psplot[algebraic,linecolor=blue,linewidth=3]{-4}{4}{4*(x-alpha)*alpha} +\psline{->}(-4,0)(4,0) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/10.tex b/packages/latex2js/test/corpus/10.tex new file mode 100644 index 00000000..25de723d --- /dev/null +++ b/packages/latex2js/test/corpus/10.tex @@ -0,0 +1,33 @@ +\psset{unit=1cm} +\begin{pspicture}(-1,-3)(9,4) + +\pscustom[fillstyle=solid,fillcolor=gray!40,linestyle=none]{ + \psline[linewidth=1 pt](0,0)(4,1.2) + \psline[linewidth=1 pt](4,1.2)(8.4,0) + \psline[linewidth=1 pt](8.4,0)(4,-1.2) + \psline[linewidth=1 pt](4,-1.2)(0,0) +} + +\psline[linewidth=1 pt](0,0)(4,1.2) +\psline[linewidth=1 pt](4,1.2)(8.4,0) +\psline[linewidth=1 pt](8.4,0)(4,-1.2) +\psline[linewidth=1 pt](4,-1.2)(0,0) + +\rput(0.78,0){$W$} + + % new vector +\rput(6,3.3){$x$} +\psline[linewidth=1.5 pt,linecolor=red]{->}(2.2,0.2)(6,3) + + % new vector +\rput(6.35,1.5){$\mathcal{E}_N$} +\psline[linewidth=1.5 pt]{->}(6,0)(6,3) + + % new vector +\rput(4,-0.3){$\hat{x}_N$} +\psline[linewidth=1.5 pt]{->}(2.2,0.2)(6,0) + + % new vector +\psline[linewidth=1.5 pt](2.2,0.2)(6,0) + +\end{pspicture} diff --git a/packages/latex2js/test/corpus/11.tex b/packages/latex2js/test/corpus/11.tex new file mode 100644 index 00000000..655b25c8 --- /dev/null +++ b/packages/latex2js/test/corpus/11.tex @@ -0,0 +1,22 @@ +\begin{pspicture}(-5,-5)(5,5) +\rput(0.3,3.75){ $Im$ } +\psline{->}(0,-3.75)(0,3.75) +\rput(3.75,0.3){ $Re$ } +\psline{->}(-3.75,0)(3.75,0) +\pscircle(0,0){ 3 } +\pscircle(0,0){ 2 } +\pscircle(0,0){ 1 } +\rput(2.3,1){$e^{i\omega}-\alpha$} +\userline[linewidth=1.5 pt]{->}(1.500,0.000)(1.121,2.121) +\userline[linewidth=1.5 pt]{->}(1.500,0.000)(2.121,2.121) +\userline[linewidth=1.5 pt,linecolor=blue]{->}(0,0.000)(2.121,2.121){(x>0) ? 3 * cos( atan(-y/x) ) : -3 * cos( atan(-y/x) ) }{ (x>0) ? -3 * sin( atan(-y/x) ) : 3 * sin( atan(-y/x) )} +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){x}{0}{x}{y} +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){0}{y}{x}{y} +\rput(-0.75,-4.25){$1+\alpha$} +\rput(2.25,-4.25){$1-\alpha$} +\psline{<->}(-3,-4)(1.5,-4) +\psline{<->}(1.5,-4)(3,-4) +\psline[linestyle=dashed](3,-4.5)(3,0) +\psline[linestyle=dashed](-3,-4.5)(-3,0) +\psline[linestyle=dashed](1.5,-4.5)(1.5,0) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/12.tex b/packages/latex2js/test/corpus/12.tex new file mode 100644 index 00000000..da6d26df --- /dev/null +++ b/packages/latex2js/test/corpus/12.tex @@ -0,0 +1,51 @@ +\begin{pspicture}(-3,-5)(8,3) + +% in from x +\rput(-3.2,0){$x(t)$} +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(-2.7,0)(-0.25,0) +% out to y +\rput(2,0.3){$x_g(t)$} +\rput(8.6,0){$y_g(t)$} +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(0.25,0)(2.7,0) +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(6,0)(8,0) +% up arrow +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(0,-1.65)(0,-0.25) + +% multiplier +\pscircle(0,0){0.25} +\psline(-0.175,0.175)(0.175,-0.175) +\psline(0.175,0.175)(-0.175,-0.175) + +\rput(3.2, 0.5){$H(\omega)$} +\rput(5.3, 0.6){$(T_s)$} +\psline{->}(3.25, 0)(5.5,0) +\psline(3.75, 0.0)(3.75, 0.5) +\psline(4.75, 0.0)(4.75, 0.5) +\psline(3.75, 0.5)(4.75, 0.5) + +\psline(3.75, 0.1)(3.75, -0.1) +\rput(3.7, -0.45){$-\frac{\omega_s}{2}$} +\psline(4.75, 0.1)(4.75, -0.1) +\rput(4.7, -0.45){$\frac{\omega_s}{2}$} + +\psframe(2.65, -0.75)(6, 1) + + +% impulses +\rput(2.3,-1.7){$g(t) = \sum \limits_{k = -\infty}^{\infty}\delta(t-kT_S)$} +\rput(-1.1,-2.1){$(1)$} +\rput(-1.3,-2.5){$\cdots$} +\rput(1.3,-2.5){$\cdots$} +\psline{->}(-1.5,-3)(1.5,-3) +\psline[linewidth=1.25pt]{->}(-0.75,-3)(-0.75,-2) +\rput(-0.75,-3.3){$-T_s$} +\psline[linewidth=1.25pt]{->}(0,-3)(0,-2) +\rput(0,-3.3){$0$} +\psline[linewidth=1.25pt]{->}(0.75,-3)(0.75,-2) +\rput(0.75,-3.3){$T_s$} + + +% box +\psframe(-1.75,-3.65)(7, 1.2) + +\end{pspicture} diff --git a/packages/latex2js/test/corpus/13.tex b/packages/latex2js/test/corpus/13.tex new file mode 100644 index 00000000..d0bf6207 --- /dev/null +++ b/packages/latex2js/test/corpus/13.tex @@ -0,0 +1,42 @@ +\begin{pspicture}(0,-3.5)(8,2) + +\uservariable{alpha}(0,0){x} +\uservariable{beta}(0,0){x} +\uservariable{theta}(0,0){y} +\uservariable{phi}(0,0){20} + +\rput(0,0){$x$} +\rput(7.2,0){$y$} +\rput(5.5,-1.25){$z^{-1}$} +\userline[linewidth=2pt,linecolor=lightblue]{->}(3.5,-2)(2,2) +\pscircle(3.5,-2){1} + +\psline{->}(3,-2)(4,-2) +\psline{->}(3.5,-2.75)(3.5,-0.75) + +\rput(3.85,-1.6){$\alpha$} +\rput(1.5,-2.5){$H$} + +% plus or minus for adder +\rput(2.1,-0.5){$-$} +\rput(1.45,0.35){$+$} + +\psframe(1,-3)(6.5,1) + +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(0.25,0)(1.5,0) +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(1.75,-2)(1.75,-0.25) +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(5.5,-2)(4.5,-2) +\psline[linewidth=1.25 pt](2.5,-2)(1.75,-2) +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(2,0)(7,0) +\psline[linewidth=1.25 pt](5.5,-1.7)(5.5,-2) +\psline[linewidth=1.25 pt]{->}(5.5,0)(5.5,-0.75) + +\psframe(5, -1.7)(6,-0.75) + +\pscircle(1.75,0){0.25} +\psline(1.5,0)(2,0) +\psline(1.75,0.25)(1.75,-0.25) + +\psplot[algebraic,linewidth=3pt,linecolor=red]{-3.14}{7}{alpha * sin(theta*x)/( x * phi )} + +\end{pspicture} diff --git a/packages/latex2js/test/corpus/14-bar-chart.tex b/packages/latex2js/test/corpus/14-bar-chart.tex new file mode 100644 index 00000000..fd108296 --- /dev/null +++ b/packages/latex2js/test/corpus/14-bar-chart.tex @@ -0,0 +1,8 @@ +\psset{unit=0.55cm} +\begin{pspicture}(0,-1)(9,9.5) +\psaxes[labels=none,ticks=none]{->}(0,0)(0,-0.6)(8.6,8.6) +\multido{\i=1+1}{8}{% + \psframe*[fillcolor=lightblue](\i,0)(\i.8,\i) + \rput(\i.4,-0.4){$\i$} +} +\end{pspicture} diff --git a/packages/latex2js/test/corpus/15-scatter.tex b/packages/latex2js/test/corpus/15-scatter.tex new file mode 100644 index 00000000..bd985237 --- /dev/null +++ b/packages/latex2js/test/corpus/15-scatter.tex @@ -0,0 +1,7 @@ +\psset{unit=0.75cm} +\begin{pspicture}(0,0)(8,6) +\psgrid(0,0)(8,6) +\psdots[linecolor=blue](1,1)(2,3)(3,2)(4,4.5)(5,2.5)(6,5)(7,3.5) +\psellipse[fillstyle=solid,fillcolor=lightblue](4,3)(3.2,2.2) +\rput(4,3){$\mu$} +\end{pspicture} diff --git a/packages/latex2js/test/corpus/16-curves.tex b/packages/latex2js/test/corpus/16-curves.tex new file mode 100644 index 00000000..cd56ce73 --- /dev/null +++ b/packages/latex2js/test/corpus/16-curves.tex @@ -0,0 +1,8 @@ +\psset{unit=0.8cm} +\begin{pspicture}(0,0)(10,7) +\psline{->}(0,0)(10,0) +\psline{->}(0,0)(0,7) +\psbezier[linecolor=red,linewidth=2pt](1,1)(3,5)(6,2)(8,6) +\pscurve[linecolor=blue,linewidth=2pt](0.5,1.5)(2,3.5)(4,2)(5.5,4.5)(7,3) +\psccurve[linecolor=green,linewidth=2pt](7.5,1)(8.5,2.5)(9.5,1.5) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/17-pie.tex b/packages/latex2js/test/corpus/17-pie.tex new file mode 100644 index 00000000..045924b5 --- /dev/null +++ b/packages/latex2js/test/corpus/17-pie.tex @@ -0,0 +1,9 @@ +\psset{unit=1cm} +\begin{pspicture}(-4,-4)(4,4) +\pswedge[fillstyle=solid,fillcolor=red](0,0){3}{0}{72} +\pswedge[fillstyle=solid,fillcolor=orange](0,0){3}{72}{144} +\pswedge[fillstyle=solid,fillcolor=green](0,0){3}{144}{216} +\pswedge[fillstyle=solid,fillcolor=blue](0,0){3}{216}{288} +\pswedge[fillstyle=solid,fillcolor=purple](0,0){3}{288}{360} +\pscircle(0,0){3} +\end{pspicture} diff --git a/packages/latex2js/test/corpus/18-fills.tex b/packages/latex2js/test/corpus/18-fills.tex new file mode 100644 index 00000000..02d6c511 --- /dev/null +++ b/packages/latex2js/test/corpus/18-fills.tex @@ -0,0 +1,15 @@ +\psset{unit=0.75cm} +\begin{pspicture}(0,0)(12,8) +\psframe*[fillcolor=lightgray](0,0)(12,8) +\pscircle*(1,1){0.8} +\pspolygon*(3,1)(4,2)(3,3)(2,2) +\psframe*[fillcolor=red](5,1)(7,2) +\psellipse*[fillcolor=yellow](9,1.5)(2,0.8) +\pscustom[fillstyle=solid,fillcolor=lightblue,linestyle=none]{ + \psline(0.5,5)(3,7.5) + \psline(3,7.5)(5.5,5) + \psline(5.5,5)(3,2.5) + \psline(3,2.5)(0.5,5) +} +\psarc*[fillcolor=orange](8,5.5){1.8}{0}{180} +\end{pspicture} diff --git a/packages/latex2js/test/corpus/19-algebraic-plot.tex b/packages/latex2js/test/corpus/19-algebraic-plot.tex new file mode 100644 index 00000000..89f65e1a --- /dev/null +++ b/packages/latex2js/test/corpus/19-algebraic-plot.tex @@ -0,0 +1,8 @@ +\psset{unit=0.8cm} +\begin{pspicture}(-4,-4)(4,4) +\psaxes{->}(0,0)(-3.5,-3.5)(3.5,3.5) +\psplot[algebraic,linecolor=red,linewidth=2pt,plotpoints=200]{-3}{3}{x^2 - 2} +\psplot[algebraic,linecolor=blue,linewidth=2pt,plotpoints=200]{-3}{3}{0.5x^3} +\rput(3,-0.6){$x$} +\rput(0.5,3.2){$y$} +\end{pspicture} diff --git a/packages/latex2js/test/corpus/20-document.tex b/packages/latex2js/test/corpus/20-document.tex new file mode 100644 index 00000000..fa7ca82d --- /dev/null +++ b/packages/latex2js/test/corpus/20-document.tex @@ -0,0 +1,22 @@ +\section{The Cauchy--Schwarz Inequality} + +\begin{theorem} +If $u, v \in V$ then $|\langle u, v \rangle| \le \|u\| \|v\|$. +\end{theorem} + +\begin{proof} +Consider the quadratic polynomial +$$p(t) = \langle u + t v, u + t v \rangle \ge 0.$$ +\end{proof} + +\begin{itemize} +\item First item with \textbf{bold} text +\item Second with \textit{italic} and \textcolor{red}{color} +\end{itemize} + +\begin{align} +(a+b)^2 &= a^2 + 2ab + b^2 \\ +(a-b)^2 &= a^2 - 2ab + b^2 +\end{align} + +\noindent And a \emph{paragraph} of plain text with an \href{https://latex2js.com}{inline link} and a footnote\footnote{like this}. diff --git a/packages/latex2js/test/corpus/graph.tex b/packages/latex2js/test/corpus/graph.tex new file mode 100644 index 00000000..c8413243 --- /dev/null +++ b/packages/latex2js/test/corpus/graph.tex @@ -0,0 +1,79 @@ + +Thanks for sharing this \href{https://twitter.com/oliviawalch}{Olivia}, and also Austin and Swarna for the great examples! \href{http://www-personal.umich.edu/~ojwalch/swarna-austin/index.html}{original source} + +{\bf Math Myths} + +created by Austin Rife, Swarna Shil + + +When looking at a power function, we can imagine the exponent as Mt. Olympus where the gods live. The variable is the mortal humans, but we have a magic mortal-making potion called the power rule. We can use this potion to throw one of the gods down to live with mortals, which multiplies the power of mortals but leaves Mt. Olympus with one less god. + + +$$ \frac{d}{dx} x^5 = 5x^4 $$ +\begin{center} +\psset{unit=0.75cm} +\begin{pspicture}(-4,-3)(4,6) +\uservariable{alpha}(0.1,0){x} +\psplot[algebraic,linewidth=2pt]{-4}{4}{pow(x,5)} +\psplot[algebraic,linecolor=blue,linewidth=3]{-4}{4}{5*(x-alpha)*pow(alpha,4) + pow(alpha,5)} +\psline{->}(-4,0)(4,0) +\end{pspicture} +\end{center} + + +\begin{center} +\psset{unit=0.75cm} +\begin{pspicture}(-4,-3)(4,6) +\uservariable{alpha}(0.1,0){x} +\psplot[algebraic,linewidth=2pt]{-4}{4}{pow(x,5)} +\psplot[algebraic,linecolor=blue,linewidth=3]{-4}{4}{5*(x-alpha)*pow(alpha,4) + pow(alpha,5)} +\psplot[algebraic,linecolor=green,linewidth=3]{-4}{4}{5*x*x*x*x} +\psplot[plotstyle=dots, plotpoints=1,dotstyle=*,dotsize=10pt]{alpha-.1}{alpha+.1}{5*pow(alpha,4)} +\psline{->}(-4,0)(4,0) +\end{pspicture} +\end{center} + +$a^x$ was a narcissist. He always liked to lean ($\ln$) down to see his reflection in the pool +($a$). + +$$f(x) = a^x$$ +$$f ’(x) = a^x \ln(a)$$ + +\begin{center} +\psset{unit=0.75cm} +\begin{pspicture}(-4,-3)(4,6) +\uservariable{alpha}(0.1,0){x} +\psplot[algebraic,linewidth=2pt]{-4}{4}{pow(2,x)} +\psplot[algebraic,linecolor=blue,linewidth=3]{-4}{4}{(x-alpha)*pow(2,alpha)*log(2) + pow(2,alpha)} +\psline{->}(-4,0)(4,0) +\end{pspicture} +\end{center} + + +\begin{center} +\psset{unit=0.75cm} +\begin{pspicture}(-4,-3)(4,6) +\uservariable{alpha}(0.1,0){x} +\psplot[algebraic,linewidth=2pt]{-4}{4}{pow(2,x)} +\psplot[algebraic,linecolor=blue,linewidth=3]{-4}{4}{(x-alpha)*pow(2,alpha)*log(2) + pow(2,alpha)} +\psplot[algebraic,linecolor=green,linewidth=3]{-4}{4}{pow(2,x)*log(2)} +\psplot[plotstyle=dots, plotpoints=1,dotstyle=*,dotsize=10pt]{alpha-.1}{alpha+.1}{log(2)*pow(2,alpha)} +\psline{->}(-4,0)(4,0) +\end{pspicture} +\end{center} + +Apollo and Artemis are a famous pair for their moody behaviors in Mt. Olympus and the mortal world, but no one knows the true mystery behind these two. Apollo ($\sin(x)$) is actually Artemis ($\cos(x)$) when he uses the $\frac{d}{dx}$ potion. When he uses the $\frac{d}{dx}$ potion again as Artemis, he becomes the evil Apollo $–\sin(x)$. And when evil Apollo takes it once more, he becomes evil Artemis. Evil Artemis transforms into good Apollo with another boost of the $\frac{d}{dx}$ potion. Thus, this moody behavior of the divine “twins” continues in an endless cycle. + + +\begin{center} +\psset{unit=0.75cm} +\begin{pspicture}(-10,-6)(10,6) +\uservariable{alpha}(0.1,0){x} +\psplot[algebraic,linewidth=2pt]{-10}{10}{sin(x)} +\psplot[algebraic,linecolor=blue,linewidth=3]{-10}{10}{(cos(alpha))*(x-alpha) + sin(alpha)} +\psplot[algebraic,linecolor=green,linewidth=3]{-10}{10}{cos(x)} +\psplot[plotstyle=dots, plotpoints=1,dotstyle=*,dotsize=10pt]{alpha-.1}{alpha+.1}{cos(alpha)} + +\psline{->}(-10,0)(10,0) +\end{pspicture} +\end{center} diff --git a/packages/latex2js/test/corpus/site-examples-index-1.tex b/packages/latex2js/test/corpus/site-examples-index-1.tex new file mode 100644 index 00000000..d93ed23a --- /dev/null +++ b/packages/latex2js/test/corpus/site-examples-index-1.tex @@ -0,0 +1,678 @@ + +
    +
    +\begin{pspicture}(0,-3)(8,3) +\rput(0,0){$x(t)$} +\rput(4,1.5){$f(t)$} +\rput(4,-1.5){$g(t)$} +\rput(8.2,0){$y(t)$} +\rput(1.5,-2){$h(t)$} +\psframe(1,-2.5)(7,2.5) +\psframe(3,1)(5,2) +\psframe(3,-1)(5,-2) +\rput(4,0){$X_k = \frac{1}{p} \sum \limits_{n=\langle p\rangle}x(n)e^{-ik\omega_0n}$} +\psline{->}(0.5,0)(1.5,0) +\psline{->}(1.5,1.5)(3,1.5) +\psline{->}(1.5,-1.5)(3,-1.5) +\psline{->}(6.5,1.5)(6.5,0.25) +\psline{->}(6.5,-1.5)(6.5,-0.25) +\psline{->}(6.75,0)(7.75,0) +\psline(1.5,-1.5)(1.5,1.5) +\psline(5,1.5)(6.5,1.5) +\psline(5,-1.5)(6.5,-1.5) +\psline(6,-1.5)(6.5,-1.5) +\pscircle(6.5,0){0.25} +\psline(6.25,0)(6.75,0) +\psline(6.5,0.5)(6.5,-0.5) +\end{pspicture} + + +
    +source: +
    +\begin{verbatim} +\begin{pspicture}(0,-3)(8,3) +\rput(0,0){$x(t)$} +\rput(4,1.5){$f(t)$} +\rput(4,-1.5){$g(t)$} +\rput(8.2,0){$y(t)$} +\rput(1.5,-2){$h(t)$} +\psframe(1,-2.5)(7,2.5) +\psframe(3,1)(5,2) +\psframe(3,-1)(5,-2) +\rput(4,0){$X_k = \frac{1}{p} \sum \limits_{n=\langle p\rangle}x(n)e^{-ik\omega_0n}$} +\psline{->}(0.5,0)(1.5,0) +\psline{->}(1.5,1.5)(3,1.5) +\psline{->}(1.5,-1.5)(3,-1.5) +\psline{->}(6.5,1.5)(6.5,0.25) +\psline{->}(6.5,-1.5)(6.5,-0.25) +\psline{->}(6.75,0)(7.75,0) +\psline(1.5,-1.5)(1.5,1.5) +\psline(5,1.5)(6.5,1.5) +\psline(5,-1.5)(6.5,-1.5) +\psline(6,-1.5)(6.5,-1.5) +\pscircle(6.5,0){0.25} +\psline(6.25,0)(6.75,0) +\psline(6.5,0.5)(6.5,-0.5) +\end{pspicture} + + +\end{verbatim} +
    +
    +
    +
    +\begin{pspicture}(-2,-2)(2,2) +\psframe(-2,-2)(2,2) +\userline[linewidth=1.5 pt]{->}(0,0)(2,2) +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\begin{pspicture}(-2,-2)(2,2) +\psframe(-2,-2)(2,2) +\userline[linewidth=1.5 pt]{->}(0,0)(2,2) +\end{pspicture} + +\end{verbatim} +
    +
    +
    +
    +\begin{pspicture}(-2,-2)(2,2) +\psframe(-2,-2)(2,2) +\userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} +\userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} +\userline[linewidth=2pt,linecolor=purple]{->}(0,0)(2,2){-x}{cos(y)} +\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2)(sin(x)}{-y} +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\begin{pspicture}(-2,-2)(2,2) +\psframe(-2,-2)(2,2) +\userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} +\userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} +\userline[linewidth=2pt,linecolor=purple]{->}(0,0)(2,2){-x}{cos(y)} +\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2)(sin(x)}{-y} +\end{pspicture} + +\end{verbatim} +
    +
    +
    +
    +\begin{pspicture}(-5,-5)(5,5) + +% y-axis +\rput(0.3,3.75){ $Im$ } +\psline{->}(0,-3.75)(0,3.75) + +% x-axis +\rput(3.75,0.3){ $Re$ } +\psline{->}(-3.75,0)(3.75,0) + +% the circle +\pscircle(0,0){ 3 } + + % new vector +\rput(2.3,1){$e^{i\omega}-\alpha$} +\userline[linewidth=1.5 pt]{->}(1.500,0.000)(2.121,2.121) +\userline[linewidth=1.5 pt,linecolor=blue]{->}(0,0.000)(2.121,2.121){(x>0) ? 3 * cos( atan(-y/x) ) : -3 * cos( atan(-y/x) ) }{ (x>0) ? -3 * sin( atan(-y/x) ) : 3 * sin( atan(-y/x) )} + +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){x}{0}{x}{y} +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){0}{y}{x}{y} + +\rput(-0.75,-4.25){$1+\alpha$} +\rput(2.25,-4.25){$1-\alpha$} +\psline{<->}(-3,-4)(1.5,-4) +\psline{<->}(1.5,-4)(3,-4) +\psline[linestyle=dashed](3,-4.5)(3,0) +\psline[linestyle=dashed](-3,-4.5)(-3,0) +\psline[linestyle=dashed](1.5,-4.5)(1.5,0) + +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\begin{pspicture}(-5,-5)(5,5) + +% y-axis +\rput(0.3,3.75){ $Im$ } +\psline{->}(0,-3.75)(0,3.75) + +% x-axis +\rput(3.75,0.3){ $Re$ } +\psline{->}(-3.75,0)(3.75,0) + +% the circle +\pscircle(0,0){ 3 } + + % new vector +\rput(2.3,1){$e^{i\omega}-\alpha$} +\userline[linewidth=1.5 pt]{->}(1.500,0.000)(2.121,2.121) +\userline[linewidth=1.5 pt,linecolor=blue]{->}(0,0.000)(2.121,2.121){(x>0) ? 3 * cos( atan(-y/x) ) : -3 * cos( atan(-y/x) ) }{ (x>0) ? -3 * sin( atan(-y/x) ) : 3 * sin( atan(-y/x) )} + +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){x}{0}{x}{y} +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){0}{y}{x}{y} + +\rput(-0.75,-4.25){$1+\alpha$} +\rput(2.25,-4.25){$1-\alpha$} +\psline{<->}(-3,-4)(1.5,-4) +\psline{<->}(1.5,-4)(3,-4) +\psline[linestyle=dashed](3,-4.5)(3,0) +\psline[linestyle=dashed](-3,-4.5)(-3,0) +\psline[linestyle=dashed](1.5,-4.5)(1.5,0) + +\end{pspicture} + +\end{verbatim} +
    +
    +
    +
    +\begin{pspicture}(-4,-3)(4,3) +\uservariable{alpha}(0,0){x} +\psplot[algebraic,linewidth=2pt,fillstyle=solid, fillcolor=lightblue]{-4}{alpha}{sin(x)} +\psplot[algebraic,linewidth=2pt]{-4}{4}{sin(x)} +\psline{->}(-4,0)(4,0) +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\begin{pspicture}(-4,-3)(4,3) +\uservariable{alpha}(0,0){x} +\psplot[algebraic,linewidth=2pt,fillstyle=solid, fillcolor=lightblue]{-4}{alpha}{sin(x)} +\psplot[algebraic,linewidth=2pt]{-4}{4}{sin(x)} +\psline{->}(-4,0)(4,0) +\end{pspicture} + +\end{verbatim} +
    +
    +
    +
    +\begin{pspicture}(-4,-3)(4,3) +\uservariable{alpha}(0,0){x} +\uservariable{beta}(0,0){y} +\psplot[algebraic,linewidth=2pt,fillstyle=solid, fillcolor=lightblue]{alpha-3}{alpha}{beta + sin(x)} +\psplot[algebraic,linewidth=2pt]{-4}{4}{beta + sin(x)} +\psline{->}(-4,0)(4,0) +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\begin{pspicture}(-4,-3)(4,3) +\uservariable{alpha}(0,0){x} +\uservariable{beta}(0,0){y} +\psplot[algebraic,linewidth=2pt,fillstyle=solid, fillcolor=lightblue]{alpha-3}{alpha}{beta + sin(x)} +\psplot[algebraic,linewidth=2pt]{-4}{4}{beta + sin(x)} +\psline{->}(-4,0)(4,0) +\end{pspicture} + +\end{verbatim} +
    +
    +
    +
    +\psset{unit=1cm} +\begin{pspicture}(-3.5,-1)(3.75,3.5) + +\slider{1}{8}{n}{$N$}{4} + +\psplot[algebraic,linewidth=1.5pt,plotpoints=1000]{-3.14}{3.14}{cos(n*x/2)+1.3} +\psaxes[showorigin=false,labels=none, Dx=1.62](0,0)(-3.25,0)(3.25,2.5) + +\psline[linestyle=dashed](-3.14,0.3)(3.14,0.3) +\psline[linestyle=dashed](-3.14,2.3)(3.14,2.3) +\rput(3.6,2.3){$\frac{1}{1-\alpha}$} +\rput(3.6,0.3){$\frac{1}{1+\alpha}$} + + +\rput(3.14, -0.35){$\pi$} +\rput(1.62, -0.35){$\pi/2$} +\rput(-1.62, -0.35){$-\pi/2$} +\rput(-3.14, -0.35){$-\pi$} +\rput(0, -0.35){$0$} + +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\psset{unit=1cm} +\begin{pspicture}(-3.5,-1)(3.75,3.5) + +\slider{1}{8}{n}{$N$}{4} + +\psplot[algebraic,linewidth=1.5pt,plotpoints=1000]{-3.14}{3.14}{cos(n*x/2)+1.3} +\psaxes[showorigin=false,labels=none, Dx=1.62](0,0)(-3.25,0)(3.25,2.5) + +\psline[linestyle=dashed](-3.14,0.3)(3.14,0.3) +\psline[linestyle=dashed](-3.14,2.3)(3.14,2.3) +\rput(3.6,2.3){$\frac{1}{1-\alpha}$} +\rput(3.6,0.3){$\frac{1}{1+\alpha}$} + + +\rput(3.14, -0.35){$\pi$} +\rput(1.62, -0.35){$\pi/2$} +\rput(-1.62, -0.35){$-\pi/2$} +\rput(-3.14, -0.35){$-\pi$} +\rput(0, -0.35){$0$} + +\end{pspicture} + +\end{verbatim} +
    +
    +
    +
    +\psset{unit=0.5cm} +\begin{pspicture}(-13,-5)(13,10) + +\slider{1}{8}{a}{amplitude}{4} +\slider{1}{8}{n}{frequency}{4} + +\psplot[algebraic,linewidth=1.5pt,plotpoints=1000]{-12.56}{12.56}{a*sin(n*x)/(n*x)} +\psaxes[showorigin=false,labels=none, Dx=3.14](0,0)(-12.6,0)(12.6,0) + +\rput(0, -0.5){$0$} + +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\psset{unit=0.5cm} +\begin{pspicture}(-13,-5)(13,10) + +\slider{1}{8}{a}{amplitude}{4} +\slider{1}{8}{n}{frequency}{4} + +\psplot[algebraic,linewidth=1.5pt,plotpoints=1000]{-12.56}{12.56}{a*sin(n*x)/(n*x)} +\psaxes[showorigin=false,labels=none, Dx=3.14](0,0)(-12.6,0)(12.6,0) + +\rput(0, -0.5){$0$} + +\end{pspicture} + +\end{verbatim} +
    +
    +
    +
    +\psset{unit=0.75cm} +\begin{pspicture}(-4,-3)(4,6) +\uservariable{alpha}(0.1,0){x} +\psplot[algebraic,linewidth=2pt]{-4}{4}{pow(x,2)} +\psplot[algebraic,linecolor=blue,linewidth=3]{-4}{4}{4*(x-alpha)*alpha} +\psline{->}(-4,0)(4,0) +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\psset{unit=0.75cm} +\begin{pspicture}(-4,-3)(4,6) +\uservariable{alpha}(0.1,0){x} +\psplot[algebraic,linewidth=2pt]{-4}{4}{pow(x,2)} +\psplot[algebraic,linecolor=blue,linewidth=3]{-4}{4}{4*(x-alpha)*alpha} +\psline{->}(-4,0)(4,0) +\end{pspicture} + +\end{verbatim} +
    +
    +
    +
    +\psset{unit=1cm} +\begin{pspicture}(-1,-3)(9,4) + +\pscustom[fillstyle=solid,fillcolor=gray!40,linestyle=none]{ + \psline[linewidth=1 pt](0,0)(4,1.2) + \psline[linewidth=1 pt](4,1.2)(8.4,0) + \psline[linewidth=1 pt](8.4,0)(4,-1.2) + \psline[linewidth=1 pt](4,-1.2)(0,0) +} + +\psline[linewidth=1 pt](0,0)(4,1.2) +\psline[linewidth=1 pt](4,1.2)(8.4,0) +\psline[linewidth=1 pt](8.4,0)(4,-1.2) +\psline[linewidth=1 pt](4,-1.2)(0,0) + +\rput(0.78,0){$W$} + + % new vector +\rput(6,3.3){$x$} +\psline[linewidth=1.5 pt,linecolor=red]{->}(2.2,0.2)(6,3) + + % new vector +\rput(6.35,1.5){$\mathcal{E}_N$} +\psline[linewidth=1.5 pt]{->}(6,0)(6,3) + + % new vector +\rput(4,-0.3){$\hat{x}_N$} +\psline[linewidth=1.5 pt]{->}(2.2,0.2)(6,0) + + % new vector +\psline[linewidth=1.5 pt](2.2,0.2)(6,0) + +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\psset{unit=1cm} +\begin{pspicture}(-1,-3)(9,4) + +\pscustom[fillstyle=solid,fillcolor=gray!40,linestyle=none]{ + \psline[linewidth=1 pt](0,0)(4,1.2) + \psline[linewidth=1 pt](4,1.2)(8.4,0) + \psline[linewidth=1 pt](8.4,0)(4,-1.2) + \psline[linewidth=1 pt](4,-1.2)(0,0) +} + +\psline[linewidth=1 pt](0,0)(4,1.2) +\psline[linewidth=1 pt](4,1.2)(8.4,0) +\psline[linewidth=1 pt](8.4,0)(4,-1.2) +\psline[linewidth=1 pt](4,-1.2)(0,0) + +\rput(0.78,0){$W$} + + % new vector +\rput(6,3.3){$x$} +\psline[linewidth=1.5 pt,linecolor=red]{->}(2.2,0.2)(6,3) + + % new vector +\rput(6.35,1.5){$\mathcal{E}_N$} +\psline[linewidth=1.5 pt]{->}(6,0)(6,3) + + % new vector +\rput(4,-0.3){$\hat{x}_N$} +\psline[linewidth=1.5 pt]{->}(2.2,0.2)(6,0) + + % new vector +\psline[linewidth=1.5 pt](2.2,0.2)(6,0) + +\end{pspicture} + +\end{verbatim} +
    +
    +
    +
    +\begin{pspicture}(-5,-5)(5,5) +\rput(0.3,3.75){ $Im$ } +\psline{->}(0,-3.75)(0,3.75) +\rput(3.75,0.3){ $Re$ } +\psline{->}(-3.75,0)(3.75,0) +\pscircle(0,0){ 3 } +\pscircle(0,0){ 2 } +\pscircle(0,0){ 1 } +\rput(2.3,1){$e^{i\omega}-\alpha$} +\userline[linewidth=1.5 pt]{->}(1.500,0.000)(1.121,2.121) +\userline[linewidth=1.5 pt]{->}(1.500,0.000)(2.121,2.121) +\userline[linewidth=1.5 pt,linecolor=blue]{->}(0,0.000)(2.121,2.121){(x>0) ? 3 * cos( atan(-y/x) ) : -3 * cos( atan(-y/x) ) }{ (x>0) ? -3 * sin( atan(-y/x) ) : 3 * sin( atan(-y/x) )} +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){x}{0}{x}{y} +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){0}{y}{x}{y} +\rput(-0.75,-4.25){$1+\alpha$} +\rput(2.25,-4.25){$1-\alpha$} +\psline{<->}(-3,-4)(1.5,-4) +\psline{<->}(1.5,-4)(3,-4) +\psline[linestyle=dashed](3,-4.5)(3,0) +\psline[linestyle=dashed](-3,-4.5)(-3,0) +\psline[linestyle=dashed](1.5,-4.5)(1.5,0) +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\begin{pspicture}(-5,-5)(5,5) +\rput(0.3,3.75){ $Im$ } +\psline{->}(0,-3.75)(0,3.75) +\rput(3.75,0.3){ $Re$ } +\psline{->}(-3.75,0)(3.75,0) +\pscircle(0,0){ 3 } +\pscircle(0,0){ 2 } +\pscircle(0,0){ 1 } +\rput(2.3,1){$e^{i\omega}-\alpha$} +\userline[linewidth=1.5 pt]{->}(1.500,0.000)(1.121,2.121) +\userline[linewidth=1.5 pt]{->}(1.500,0.000)(2.121,2.121) +\userline[linewidth=1.5 pt,linecolor=blue]{->}(0,0.000)(2.121,2.121){(x>0) ? 3 * cos( atan(-y/x) ) : -3 * cos( atan(-y/x) ) }{ (x>0) ? -3 * sin( atan(-y/x) ) : 3 * sin( atan(-y/x) )} +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){x}{0}{x}{y} +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){0}{y}{x}{y} +\rput(-0.75,-4.25){$1+\alpha$} +\rput(2.25,-4.25){$1-\alpha$} +\psline{<->}(-3,-4)(1.5,-4) +\psline{<->}(1.5,-4)(3,-4) +\psline[linestyle=dashed](3,-4.5)(3,0) +\psline[linestyle=dashed](-3,-4.5)(-3,0) +\psline[linestyle=dashed](1.5,-4.5)(1.5,0) +\end{pspicture} + +\end{verbatim} +
    +
    +
    +
    +\begin{pspicture}(-3,-5)(8,3) + +% in from x +\rput(-3.2,0){$x(t)$} +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(-2.7,0)(-0.25,0) +% out to y +\rput(2,0.3){$x_g(t)$} +\rput(8.6,0){$y_g(t)$} +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(0.25,0)(2.7,0) +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(6,0)(8,0) +% up arrow +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(0,-1.65)(0,-0.25) + +% multiplier +\pscircle(0,0){0.25} +\psline(-0.175,0.175)(0.175,-0.175) +\psline(0.175,0.175)(-0.175,-0.175) + +\rput(3.2, 0.5){$H(\omega)$} +\rput(5.3, 0.6){$(T_s)$} +\psline{->}(3.25, 0)(5.5,0) +\psline(3.75, 0.0)(3.75, 0.5) +\psline(4.75, 0.0)(4.75, 0.5) +\psline(3.75, 0.5)(4.75, 0.5) + +\psline(3.75, 0.1)(3.75, -0.1) +\rput(3.7, -0.45){$-\frac{\omega_s}{2}$} +\psline(4.75, 0.1)(4.75, -0.1) +\rput(4.7, -0.45){$\frac{\omega_s}{2}$} + +\psframe(2.65, -0.75)(6, 1) + + +% impulses +\rput(2.3,-1.7){$g(t) = \sum \limits_{k = -\infty}^{\infty}\delta(t-kT_S)$} +\rput(-1.1,-2.1){$(1)$} +\rput(-1.3,-2.5){$\cdots$} +\rput(1.3,-2.5){$\cdots$} +\psline{->}(-1.5,-3)(1.5,-3) +\psline[linewidth=1.25pt]{->}(-0.75,-3)(-0.75,-2) +\rput(-0.75,-3.3){$-T_s$} +\psline[linewidth=1.25pt]{->}(0,-3)(0,-2) +\rput(0,-3.3){$0$} +\psline[linewidth=1.25pt]{->}(0.75,-3)(0.75,-2) +\rput(0.75,-3.3){$T_s$} + + +% box +\psframe(-1.75,-3.65)(7, 1.2) + +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\begin{pspicture}(-3,-5)(8,3) + +% in from x +\rput(-3.2,0){$x(t)$} +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(-2.7,0)(-0.25,0) +% out to y +\rput(2,0.3){$x_g(t)$} +\rput(8.6,0){$y_g(t)$} +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(0.25,0)(2.7,0) +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(6,0)(8,0) +% up arrow +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(0,-1.65)(0,-0.25) + +% multiplier +\pscircle(0,0){0.25} +\psline(-0.175,0.175)(0.175,-0.175) +\psline(0.175,0.175)(-0.175,-0.175) + +\rput(3.2, 0.5){$H(\omega)$} +\rput(5.3, 0.6){$(T_s)$} +\psline{->}(3.25, 0)(5.5,0) +\psline(3.75, 0.0)(3.75, 0.5) +\psline(4.75, 0.0)(4.75, 0.5) +\psline(3.75, 0.5)(4.75, 0.5) + +\psline(3.75, 0.1)(3.75, -0.1) +\rput(3.7, -0.45){$-\frac{\omega_s}{2}$} +\psline(4.75, 0.1)(4.75, -0.1) +\rput(4.7, -0.45){$\frac{\omega_s}{2}$} + +\psframe(2.65, -0.75)(6, 1) + + +% impulses +\rput(2.3,-1.7){$g(t) = \sum \limits_{k = -\infty}^{\infty}\delta(t-kT_S)$} +\rput(-1.1,-2.1){$(1)$} +\rput(-1.3,-2.5){$\cdots$} +\rput(1.3,-2.5){$\cdots$} +\psline{->}(-1.5,-3)(1.5,-3) +\psline[linewidth=1.25pt]{->}(-0.75,-3)(-0.75,-2) +\rput(-0.75,-3.3){$-T_s$} +\psline[linewidth=1.25pt]{->}(0,-3)(0,-2) +\rput(0,-3.3){$0$} +\psline[linewidth=1.25pt]{->}(0.75,-3)(0.75,-2) +\rput(0.75,-3.3){$T_s$} + + +% box +\psframe(-1.75,-3.65)(7, 1.2) + +\end{pspicture} + +\end{verbatim} +
    +
    +
    +
    +\begin{pspicture}(0,-3.5)(8,2) + +\uservariable{alpha}(0,0){x} +\uservariable{beta}(0,0){x} +\uservariable{theta}(0,0){y} +\uservariable{phi}(0,0){20} + +\rput(0,0){$x$} +\rput(7.2,0){$y$} +\rput(5.5,-1.25){$z^{-1}$} +\userline[linewidth=2pt,linecolor=lightblue]{->}(3.5,-2)(2,2) +\pscircle(3.5,-2){1} + +\psline{->}(3,-2)(4,-2) +\psline{->}(3.5,-2.75)(3.5,-0.75) + +\rput(3.85,-1.6){$\alpha$} +\rput(1.5,-2.5){$H$} + +% plus or minus for adder +\rput(2.1,-0.5){$-$} +\rput(1.45,0.35){$+$} + +\psframe(1,-3)(6.5,1) + +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(0.25,0)(1.5,0) +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(1.75,-2)(1.75,-0.25) +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(5.5,-2)(4.5,-2) +\psline[linewidth=1.25 pt](2.5,-2)(1.75,-2) +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(2,0)(7,0) +\psline[linewidth=1.25 pt](5.5,-1.7)(5.5,-2) +\psline[linewidth=1.25 pt]{->}(5.5,0)(5.5,-0.75) + +\psframe(5, -1.7)(6,-0.75) + +\pscircle(1.75,0){0.25} +\psline(1.5,0)(2,0) +\psline(1.75,0.25)(1.75,-0.25) + +\psplot[algebraic,linewidth=3pt,linecolor=red]{-3.14}{7}{alpha * sin(theta*x)/( x * phi )} + +\end{pspicture} + +
    +source: +
    +\begin{verbatim} +\begin{pspicture}(0,-3.5)(8,2) + +\uservariable{alpha}(0,0){x} +\uservariable{beta}(0,0){x} +\uservariable{theta}(0,0){y} +\uservariable{phi}(0,0){20} + +\rput(0,0){$x$} +\rput(7.2,0){$y$} +\rput(5.5,-1.25){$z^{-1}$} +\userline[linewidth=2pt,linecolor=lightblue]{->}(3.5,-2)(2,2) +\pscircle(3.5,-2){1} + +\psline{->}(3,-2)(4,-2) +\psline{->}(3.5,-2.75)(3.5,-0.75) + +\rput(3.85,-1.6){$\alpha$} +\rput(1.5,-2.5){$H$} + +% plus or minus for adder +\rput(2.1,-0.5){$-$} +\rput(1.45,0.35){$+$} + +\psframe(1,-3)(6.5,1) + +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(0.25,0)(1.5,0) +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(1.75,-2)(1.75,-0.25) +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(5.5,-2)(4.5,-2) +\psline[linewidth=1.25 pt](2.5,-2)(1.75,-2) +\psline[linewidth=1.25 pt, arrowscale=1.5]{->}(2,0)(7,0) +\psline[linewidth=1.25 pt](5.5,-1.7)(5.5,-2) +\psline[linewidth=1.25 pt]{->}(5.5,0)(5.5,-0.75) + +\psframe(5, -1.7)(6,-0.75) + +\pscircle(1.75,0){0.25} +\psline(1.5,0)(2,0) +\psline(1.75,0.25)(1.75,-0.25) + +\psplot[algebraic,linewidth=3pt,linecolor=red]{-3.14}{7}{alpha * sin(theta*x)/( x * phi )} + +\end{pspicture} + +\end{verbatim} +
    +
    diff --git a/packages/latex2js/test/corpus/site-index-1.tex b/packages/latex2js/test/corpus/site-index-1.tex new file mode 100644 index 00000000..70fa1cb0 --- /dev/null +++ b/packages/latex2js/test/corpus/site-index-1.tex @@ -0,0 +1,120 @@ + + +\begin{pspicture}(0,-3)(8,3) +\rput(0,0){$x(t)$} +\rput(4,1.5){$f(t)$} +\rput(4,-1.5){$g(t)$} +\rput(8.2,0){$y(t)$} +\rput(1.5,-2){$h(t)$} +\psframe(1,-2.5)(7,2.5) +\psframe(3,1)(5,2) +\psframe(3,-1)(5,-2) +\rput(4,0){$X_k = \frac{1}{p} \sum \limits_{n=\langle p\rangle}x(n)e^{-ik\omega_0n}$} +\psline{->}(0.5,0)(1.5,0) +\psline{->}(1.5,1.5)(3,1.5) +\psline{->}(1.5,-1.5)(3,-1.5) +\psline{->}(6.5,1.5)(6.5,0.25) +\psline{->}(6.5,-1.5)(6.5,-0.25) +\psline{->}(6.75,0)(7.75,0) +\psline(1.5,-1.5)(1.5,1.5) +\psline(5,1.5)(6.5,1.5) +\psline(5,-1.5)(6.5,-1.5) +\psline(6,-1.5)(6.5,-1.5) +\pscircle(6.5,0){0.25} +\psline(6.25,0)(6.75,0) +\psline(6.5,0.5)(6.5,-0.5) +\end{pspicture} + +Many of us think our thoughts using a language of some sort---there is usually some voice in our minds. Language in some ways, makes us who we are. Some even argue in the world of cognitive science that language is the foundation of our consciousness. + + +An author who has in their minds representations of intelligent concepts should be able to freely express herself through language with free association---digital expressions of these ideas in some cases requires total control of the computer and all of its processes. + + +The vision behind the personal computer was that any person could have full command of the functions of their device. I think this vision has come true to some degree, but not fully when it comes to creating graphics, especially mathematical diagrams online. + + +Does the common mathematician or professor have the ability to express concepts through web technology? The Web has its own language, and the goal of this project is to help blur the lines between what authoring the mathematical Web should be like and typesetting beautiful Math. + + +If you know \LaTeX, then get ready to author interactive diagrams in real-time (try using mouse or touch to interact with diagrams). + + +What matters most is minimizing the distance between our expression of an idea and the execution of that idea. For example, I can describe a vector at $(0,0)$ and initial value of the head at $(2,2)$ that will follow a user touch or mouse event. This will produce the following interaction: + + +\begin{center} +\begin{pspicture}(-2,-2)(2,2) +\psframe(-2,-2)(2,2) +\userline[linewidth=1.5 pt]{->}(0,0)(2,2) +\end{pspicture} +\end{center} + + +This was as easy as using this \TeX, which many math professors could understand. + +\begin{verbatim} +\begin{pspicture}(-2,-2)(2,2) +\psframe(-2,-2)(2,2) +\userline[linewidth=1.5 pt]{->}(0,0)(2,2) +\end{pspicture} +\end{verbatim} + +If you specify more arguments, you can create functions for the head and and tail of the vector, which each takes the current $x$ and $y$ position of the users finger or cursor as they move and produces the following interaction: + +\begin{center} +\begin{pspicture}(-2,-2)(2,2) +\psframe(-2,-2)(2,2) +\userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} +\userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} +\userline[linewidth=2pt,linecolor=purple]{->}(0,0)(2,2){-x}{cos(y)} +\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2)(sin(x)}{-y} +\end{pspicture} +\end{center} + +2 extra arguments provide functions for the head, 4 extra arguments allows you to control both and tail + +\begin{verbatim} +\userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} +\userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} +\userline[linewidth=2pt,linecolor=purple]{->}(0,0)(2,2){-x}{cos(y)} +\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2)(sin(x)}{-y} +\end{verbatim} + +I can also draw a more complex version, and start to make more useful diagrams to describe vectors: + +\begin{center} +\begin{pspicture}(-5,-5)(5,5) + +% y-axis +\rput(0.3,3.75){ $Im$ } +\psline{->}(0,-3.75)(0,3.75) + +% x-axis +\rput(3.75,0.3){ $Re$ } +\psline{->}(-3.75,0)(3.75,0) + +% the circle +\pscircle(0,0){ 3 } + + + % new vector +\rput(2.3,1){$e^{i\omega}-\alpha$} +\userline[linewidth=1.5 pt]{->}(1.500,0.000)(2.121,2.121) +\userline[linewidth=1.5 pt,linecolor=blue]{->}(0,0.000)(2.121,2.121){(x>0) ? 3 * cos( atan(-y/x) ) : -3 * cos( atan(-y/x) ) }{ (x>0) ? -3 * sin( atan(-y/x) ) : 3 * sin( atan(-y/x) )} + +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){x}{0}{x}{y} +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){0}{y}{x}{y} + +\rput(-0.75,-4.25){$1+\alpha$} +\rput(2.25,-4.25){$1-\alpha$} +\psline{<->}(-3,-4)(1.5,-4) +\psline{<->}(1.5,-4)(3,-4) +\psline[linestyle=dashed](3,-4.5)(3,0) +\psline[linestyle=dashed](-3,-4.5)(-3,0) +\psline[linestyle=dashed](1.5,-4.5)(1.5,0) + + +\end{pspicture} +\end{center} + diff --git a/packages/latex2js/test/corpus/site-index-2.tex b/packages/latex2js/test/corpus/site-index-2.tex new file mode 100644 index 00000000..c549394f --- /dev/null +++ b/packages/latex2js/test/corpus/site-index-2.tex @@ -0,0 +1,37 @@ + + +\begin{pspicture}(-3,-5)(8,3) +\rput(-3.2,0){$x(t)$} +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(-2.7,0)(-0.25,0) +\rput(2,0.3){$x_g(t)$} +\rput(8.6,0){$y_g(t)$} +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(0.25,0)(2.7,0) +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(6,0)(8,0) +\psline[linewidth=1.25 pt, arrowscale=1.1]{->}(0,-1.65)(0,-0.25) +\pscircle(0,0){0.25} +\psline(-0.175,0.175)(0.175,-0.175) +\psline(0.175,0.175)(-0.175,-0.175) +\rput(3.2, 0.5){$H(\omega)$} +\rput(5.3, 0.6){$(T_s)$} +\psline{->}(3.25, 0)(5.5,0) +\psline(3.75, 0.0)(3.75, 0.5) +\psline(4.75, 0.0)(4.75, 0.5) +\psline(3.75, 0.5)(4.75, 0.5) +\psline(3.75, 0.1)(3.75, -0.1) +\rput(3.7, -0.45){$-\frac{\omega_s}{2}$} +\psline(4.75, 0.1)(4.75, -0.1) +\rput(4.7, -0.45){$\frac{\omega_s}{2}$} +\psframe(2.65, -0.75)(6, 1) +\rput(2.3,-1.7){$g(t) = \sum \limits_{k = -\infty}^{\infty}\delta(t-kT_S)$} +\rput(-1.1,-2.1){$(1)$} +\rput(-1.3,-2.5){$\cdots$} +\rput(1.3,-2.5){$\cdots$} +\psline{->}(-1.5,-3)(1.5,-3) +\psline[linewidth=1.25pt]{->}(-0.75,-3)(-0.75,-2) +\rput(-0.75,-3.3){$-T_s$} +\psline[linewidth=1.25pt]{->}(0,-3)(0,-2) +\rput(0,-3.3){$0$} +\psline[linewidth=1.25pt]{->}(0.75,-3)(0.75,-2) +\rput(0.75,-3.3){$T_s$} +\psframe(-1.75,-3.65)(7, 1.2) +\end{pspicture} diff --git a/packages/latex2js/test/latex2js.test.ts b/packages/latex2js/test/latex2js.test.ts index e20c908b..1c1351cf 100644 --- a/packages/latex2js/test/latex2js.test.ts +++ b/packages/latex2js/test/latex2js.test.ts @@ -9,11 +9,15 @@ describe('class test', () => { 'enumerate', 'print', 'nicebox', + 'itemize', + 'description', ]); }); it('Delimiters', () => { expect(latex.Delimiters).toEqual({ + description: { begin: /\\begin\{description\}/, end: /\\end\{description\}/ }, enumerate: { begin: /\\begin\{enumerate\}/, end: /\\end\{enumerate\}/ }, + itemize: { begin: /\\begin\{itemize\}/, end: /\\end\{itemize\}/ }, nicebox: { begin: /\\begin\{nicebox\}/, end: /\\end\{nicebox\}/ }, print: { begin: /\\begin\{print\}/, end: /\\end\{print\}/ }, pspicture: { begin: /\\begin\{pspicture\}/, end: /\\end\{pspicture\}/ }, @@ -55,16 +59,34 @@ describe('class test', () => { 'closeq', 'emph', 'euler', + 'footnote', 'href', + 'hrule', + 'hspace', 'img', 'it', 'mdash', 'ndash', + 'newpage', + 'noindent', 'openq', + 'overline', + 'paragraph', 'rm', + 'rule', + 'section', 'set', 'sl', + 'subsection', + 'subsubsection', + 'textbf', + 'textcolor', + 'textit', + 'textrm', + 'textsc', + 'texttt', 'tt', + 'underline', 'vspace', 'youtube', ]); @@ -76,16 +98,34 @@ describe('class test', () => { 'closeq', 'emph', 'euler', + 'footnote', 'href', + 'hrule', + 'hspace', 'img', 'it', 'mdash', 'ndash', + 'newpage', + 'noindent', 'openq', + 'overline', + 'paragraph', 'rm', + 'rule', + 'section', 'set', 'sl', + 'subsection', + 'subsubsection', + 'textbf', + 'textcolor', + 'textit', + 'textrm', + 'textsc', + 'texttt', 'tt', + 'underline', 'vspace', 'youtube', ]); @@ -95,42 +135,70 @@ describe('class test', () => { expect(latex.Headers.Functions).toBeDefined(); expect(latex.Headers.Expressions).toBeDefined(); expect(Object.keys(latex.Headers.Functions).sort()).toEqual([ + 'axiom', 'bq', 'claim', 'corollary', 'definition', + 'endaxiom', 'endclaim', 'endcorollary', 'enddefinition', 'endexample', + 'endexercise', + 'endlemma', + 'endnote', 'endproblem', + 'endproposition', + 'endquestion', + 'endremark', 'endsolution', 'endtheorem', 'eq', 'example', + 'exercise', + 'lemma', + 'note', 'problem', 'proof', + 'proposition', 'qed', + 'question', + 'remark', 'solution', 'theorem', ]); expect(Object.keys(latex.Headers.Expressions).sort()).toEqual([ + 'axiom', 'bq', 'claim', 'corollary', 'definition', + 'endaxiom', 'endclaim', - 'endcorallary', + 'endcorollary', 'enddefinition', 'endexample', + 'endexercise', + 'endlemma', + 'endnote', 'endproblem', + 'endproposition', + 'endquestion', + 'endremark', 'endsolution', 'endtheorem', 'eq', 'example', + 'exercise', + 'lemma', + 'note', 'problem', 'proof', + 'proposition', 'qed', + 'question', + 'remark', 'solution', 'theorem', ]); diff --git a/packages/latex2js/test/parser-semantics.test.ts b/packages/latex2js/test/parser-semantics.test.ts new file mode 100644 index 00000000..fdba0a8b --- /dev/null +++ b/packages/latex2js/test/parser-semantics.test.ts @@ -0,0 +1,454 @@ +import LaTeX2JS from '../src'; + +const latex = new LaTeX2JS(); + +describe('PSTricks plot semantics', () => { + it('maps psline coordinates through X/Y with unit scaling', () => { + const parsed = latex.parse(` +\\begin{pspicture}(-5,-5)(5,5) +\\psline{->}(0,-3.75)(0,3.75) +\\end{pspicture} + `); + + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env).toBeDefined(); + // pspicture(-5,-5)(5,5): w = x1 - x0 = 10, xunit = 50 (1cm) + // X(v) = (w - (x1 - v)) * xunit → X(0) = (10 - (5 - 0)) * 50 = 250 + // Y(v) = (y1 - v) * yunit → Y(-3.75) = (5 + 3.75) * 50 = 437.5 + // → Y(3.75) = (5 - 3.75) * 50 = 62.5 + const line = env.plot.psline[0].data; + expect(line.x1).toBe(250); + expect(line.y1).toBe(437.5); + expect(line.x2).toBe(250); + expect(line.y2).toBe(62.5); + expect(line.arrows).toEqual([0, 1]); + expect(line.dots).toEqual([0, 0]); + }); + + it('parses dashed linestyle and linecolor options', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\psline[linestyle=dashed,linecolor=red](0,0)(1,1) +\\end{pspicture} + `); + + const env = parsed.find((e: any) => e.type === 'pspicture'); + const line = env.plot.psline[0].data; + expect(line.linestyle).toBe('dashed'); + expect(line.linecolor).toBe('red'); + }); + + it('computes pscircle center and radius', () => { + const parsed = latex.parse(` +\\begin{pspicture}(-5,-5)(5,5) +\\pscircle(0,0){3} +\\end{pspicture} + `); + + const env = parsed.find((e: any) => e.type === 'pspicture'); + const circle = env.plot.pscircle[0].data; + expect(circle.cx).toBe(250); + expect(circle.cy).toBe(250); + expect(circle.r).toBe(150); // xunit * 3 + }); + + it('captures sliders and seed variables from \\slider', () => { + const parsed = latex.parse(` +\\psset{unit=1cm} +\\begin{pspicture}(-3.5,-1)(3.75,3.5) +\\slider{1}{8}{n}{$N$}{4} +\\psplot[algebraic]{-3.14}{3.14}{cos(n*x)+1} +\\end{pspicture} + `); + + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env.env.sliders).toHaveLength(1); + expect(env.env.sliders[0]).toMatchObject({ min: 1, max: 8, variable: 'n', value: 4 }); + expect(env.env.variables.n).toBe(4); + expect(env.plot.psplot).toHaveLength(1); + }); + + it('keeps verbatim content untouched', () => { + const parsed = latex.parse(` +before +\\begin{verbatim} +\\begin{pspicture}(0,0)(4,4) +\\psline{->}(0,0)(1,1) +\\end{pspicture} +\\end{verbatim} +after + `); + + const verbatim = parsed.find((e: any) => e.type === 'verbatim'); + expect(verbatim.lines.join('\n')).toContain('\\psline{->}(0,0)(1,1)'); + }); + + it('converts header environments and text formatting', () => { + const parsed = latex.parse(` +\\begin{theorem} +If you know \\TeX, you can \\emph{author} math. +\\end{theorem} + `); + + const math = parsed.find((e: any) => e.type === 'math'); + const text = math.lines.join('\n'); + expect(text).toContain('

    Theorem

    '); + expect(text).toContain('author'); + expect(text).toContain('$\\TeX$'); + }); +}); + +describe('Peggy grammar parser (new)', () => { + it('parses commands that span multiple lines', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\rput(0,0){ + $\\frac{1}{2}$ +} +\\end{pspicture} + `); + + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env.plot.rput).toHaveLength(1); + expect(env.plot.rput[0].data.text).toContain('\\frac{1}{2}'); + expect(env.plot.rput[0].data.text).toContain('$'); + }); + + it('strips inline comments but keeps the command', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\psline(0,0)(1,1) % this is a comment +% a full comment line +\\pscircle(0,0){1} +\\end{pspicture} + `); + + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env.plot.psline).toHaveLength(1); + expect(env.plot.pscircle).toHaveLength(1); + // comment-only line must not end up in env.lines + expect(env.lines.some((l: string) => l.includes('%'))).toBe(false); + }); + + it('records source order in env.elements', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\pscircle(0,0){1} +\\psline(0,0)(1,1) +\\pscircle(1,1){2} +\\end{pspicture} + `); + + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env.env.elements.map((el: any) => el.name)).toEqual([ + 'pscircle', + 'psline', + 'pscircle' + ]); + // grouped plot still works for interactive redraws + expect(env.plot.pscircle).toHaveLength(2); + expect(env.plot.psline).toHaveLength(1); + }); + + it('captures multiple same-name commands on one line', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\pscircle(0,0){1} \\pscircle(1,1){2} +\\end{pspicture} + `); + + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env.plot.pscircle).toHaveLength(2); + }); + + it('does not corrupt pspicture content with text transforms', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\rput(1,1){$a--b$} +\\end{pspicture} + `); + + const env = parsed.find((e: any) => e.type === 'pspicture'); + // the old parser turned `--` into – inside the rput math + expect(env.plot.rput[0].data.text).toBe('$a--b$'); + }); + + it('collects diagnostics for unknown commands', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\psfractal(0,0) +\\psline(0,0)(1,1) +\\end{pspicture} + `); + + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env.plot.psline).toHaveLength(1); + const diag = latex.lastDiagnostics.find((d: any) => d.message.includes('psfractal')); + expect(diag).toBeDefined(); + expect(diag.severity).toBe('warning'); + expect(diag.line).toBeDefined(); + }); + + it('reports unclosed environments as warnings', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\psline(0,0)(1,1) + `); + + expect(parsed.find((e: any) => e.type === 'pspicture')).toBeDefined(); + const diag = latex.lastDiagnostics.find((d: any) => d.message.includes("unclosed environment 'pspicture'")); + expect(diag).toBeDefined(); + expect(diag.severity).toBe('warning'); + }); + + it('reports mismatched environment ends', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\psline(0,0)(1,1) +\\end{enumerate} + `); + + const diag = latex.lastDiagnostics.find((d: any) => d.message.includes('does not match')); + expect(diag).toBeDefined(); + expect(diag.severity).toBe('warning'); + }); + + it('parses linewidth with units instead of falling back to 2', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\psline[linewidth=1.5 pt]{->}(0,0)(1,1) +\\psline[linewidth=3](0,0)(1,1) +\\end{pspicture} + `); + + const env = parsed.find((e: any) => e.type === 'pspicture'); + // 1.5 pt → ~2px (1pt ≈ 1.333px) + expect(env.plot.psline[0].data.linewidth).toBeCloseTo(2, 1); + // bare number stays as-is + expect(env.plot.psline[1].data.linewidth).toBe(3); + }); +}); + +describe('feature port: PSTricks commands', () => { + it('psdots collects point coordinates', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\psdots(1,1)(2,2) +\\end{pspicture} + `); + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env.plot.psdots).toHaveLength(1); + expect(env.plot.psdots[0].data.data).toHaveLength(4); // 2 points + }); + + it('psgrid spans the pspicture bounds by default', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\psgrid +\\end{pspicture} + `); + const env = parsed.find((e: any) => e.type === 'pspicture'); + const grid = env.plot.psgrid[0].data; + expect(grid.x0).toBeLessThan(grid.x1); + expect(grid.y0).toBeLessThan(grid.y1); + expect(grid.xunit).toBeDefined(); + }); + + it('psellipse computes center and radii', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\psellipse[fillstyle=solid,fillcolor=lightblue](2,2)(1,0.5) +\\end{pspicture} + `); + const env = parsed.find((e: any) => e.type === 'pspicture'); + const el = env.plot.psellipse[0].data; + expect(el.fillstyle).toBe('solid'); + expect(el.fillcolor).toBe('lightblue'); + expect(el.rx).toBe(50); // 1 * xunit(50) + expect(el.ry).toBe(25); // 0.5 * yunit(50) + }); + + it('psbezier captures four control points', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\psbezier(0,0)(1,2)(2,2)(3,0) +\\end{pspicture} + `); + const env = parsed.find((e: any) => e.type === 'pspicture'); + const b = env.plot.psbezier[0].data; + expect(b.x1).toBeDefined(); + expect(b.x2).toBeDefined(); + expect(b.x3).toBeDefined(); + expect(b.x4).toBeDefined(); + }); + + it('pscurve/psccurve collect points and closure flag', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\pscurve(0,0)(1,1)(2,0) +\\psccurve(0,1)(1,2)(2,1) +\\end{pspicture} + `); + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env.plot.pscurve[0].data.closed).toBe(false); + expect(env.plot.pscurve[0].data.data.length).toBeGreaterThanOrEqual(6); + expect(env.plot.psccurve[0].data.closed).toBe(true); + }); + + it('pswedge computes pie-slice data', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\pswedge[fillstyle=solid,fillcolor=gray!40](2,2){1}{0}{90} +\\end{pspicture} + `); + const env = parsed.find((e: any) => e.type === 'pspicture'); + const w = env.plot.pswedge[0].data; + expect(w.r).toBe(50); + expect(w.angleA).toBe(0); + expect(w.angleB).toBeCloseTo(Math.PI / 2); + expect(w.A).toBeDefined(); + expect(w.B).toBeDefined(); + }); + + it('star variants are marked filled', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\pscircle*(0,0){1} +\\psframe*[fillcolor=red](1,1)(2,2) +\\pspolygon*(3,0)(4,1)(3,2) +\\psline*[linecolor=blue](0,3)(1,3) +\\psarc*(2,2){1}{0}{90} +\\end{pspicture} + `); + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env.plot.pscircle[0].data.filled).toBe(true); + expect(env.plot.psframe[0].data.filled).toBe(true); + expect(env.plot.psframe[0].data.fillcolor).toBe('red'); + expect(env.plot.pspolygon[0].data.filled).toBe(true); + expect(env.plot.psline[0].data.filled).toBe(true); + expect(env.plot.psarc[0].data.filled).toBe(true); + }); + + it('pscustom extracts its inner commands', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(8,4) +\\pscustom[fillstyle=solid,fillcolor=gray!40,linestyle=none]{ + \\psline(0,0)(4,1.2) + \\psline(4,1.2)(8,0) + \\psline(8,0)(4,-1.2) + \\psline(4,-1.2)(0,0) +} +\\end{pspicture} + `); + const env = parsed.find((e: any) => e.type === 'pspicture'); + const custom = env.plot.pscustom[0].data; + expect(custom.commands).toHaveLength(4); + expect(custom.commands.every((c: any) => c.key === 'psline')).toBe(true); + expect(custom.fillstyle).toBe('solid'); + }); + + it('multido expands its body with counter substitution', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\multido{\\i=0+1}{4}{\\psline(\\i,0)(\\i,1)} +\\end{pspicture} + `); + const env = parsed.find((e: any) => e.type === 'pspicture'); + expect(env.plot.psline).toHaveLength(4); + // first line at \i=0 → x1 = X(0); last at \i=3 → x1 = X(3) + expect(env.plot.psline[0].data.x1).toBeLessThan(env.plot.psline[3].data.x1); + expect(env.env.elements.map((el: any) => el.name)).toEqual(['psline', 'psline', 'psline', 'psline']); + }); + + it('psplot honors plotpoints option', () => { + const parsed = latex.parse(` +\\begin{pspicture}(0,0)(4,4) +\\psplot[algebraic,plotpoints=101]{0}{1}{x*x} +\\end{pspicture} + `); + const env = parsed.find((e: any) => e.type === 'pspicture'); + const data = env.plot.psplot[0].data.data; + // 101 samples → 202 numbers + expect(data.length).toBe(202); + }); +}); + +describe('feature port: text macros and headers', () => { + it('renders common text formatting macros', () => { + const parsed = latex.parse(` +\\textbf{bold} and \\textit{italic} and \\texttt{code} and \\underline{under} and \\textsc{scaps} +and \\textcolor{red}{colored} and \\section{Intro} + `); + const math = parsed.find((e: any) => e.type === 'math'); + const text = math.lines.join('\n'); + expect(text).toContain('bold'); + expect(text).toContain('italic'); + expect(text).toContain('code'); + expect(text).toContain('under'); + expect(text).toContain('font-variant: small-caps'); + expect(text).toContain('colored'); + expect(text).toContain('

    Intro

    '); + }); + + it('renders the added header environments', () => { + const parsed = latex.parse(` +\\begin{lemma}L\\end{lemma} +\\begin{proposition}P\\end{proposition} +\\begin{axiom}A\\end{axiom} +\\begin{remark}R\\end{remark} +\\begin{note}N\\end{note} +\\begin{exercise}E\\end{exercise} +\\begin{question}Q\\end{question} +\\begin{corollary}C\\end{corollary} + `); + const math = parsed.find((e: any) => e.type === 'math'); + const text = math.lines.join('\n'); + expect(text).toContain('

    Lemma

    '); + expect(text).toContain('

    Proposition

    '); + expect(text).toContain('

    Axiom

    '); + expect(text).toContain('

    Remark

    '); + expect(text).toContain('

    Note

    '); + expect(text).toContain('

    Exercise

    '); + expect(text).toContain('

    Question

    '); + expect(text).toContain('

    Corollary

    '); + }); + + it('does not crash on \\end{corollary} (old typo fix)', () => { + const parsed = latex.parse(` +\\begin{corollary}C\\end{corollary} + `); + const math = parsed.find((e: any) => e.type === 'math'); + expect(math.lines.join('\n')).toContain('

    Corollary

    '); + expect(latex.lastDiagnostics).toHaveLength(0); + }); + + it('parses itemize and description environments', () => { + const parsed = latex.parse(` +\\begin{itemize} +\\item first +\\item second +\\end{itemize} +\\begin{description} +\\item[Term] definition +\\end{description} + `); + const itemize = parsed.find((e: any) => e.type === 'itemize'); + expect(itemize.lines.join('\n')).toContain('\\item first'); + const description = parsed.find((e: any) => e.type === 'description'); + expect(description.lines.join('\n')).toContain('\\item[Term] definition'); + }); + + it('passes math environments through to MathJax', () => { + const parsed = latex.parse(` +\\begin{align} +x^2 + y^2 &= z^2 \\\\ +a &= b +\\end{align} + `); + const math = parsed.find((e: any) => e.type === 'math'); + const text = math.lines.join('\n'); + expect(text).toContain('\\begin{align}'); + expect(text).toContain('x^2 + y^2 &= z^2'); + expect(text).toContain('\\end{align}'); + }); +}); diff --git a/packages/latex2js/tsconfig.json b/packages/latex2js/tsconfig.json index 1a9d5696..2b870caa 100644 --- a/packages/latex2js/tsconfig.json +++ b/packages/latex2js/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "dist", - "rootDir": "src/" + "rootDir": "src/", + "isolatedModules": true }, "include": ["src/**/*.ts"], "exclude": ["dist", "node_modules", "**/*.spec.*", "**/*.test.*"] diff --git a/packages/pstricks/jest.config.ts b/packages/pstricks/jest.config.ts index c8fde8c9..c0ac2068 100644 --- a/packages/pstricks/jest.config.ts +++ b/packages/pstricks/jest.config.ts @@ -3,11 +3,16 @@ import type { Config } from 'jest'; const config: Config = { preset: 'ts-jest', testEnvironment: 'node', - roots: ['/src', '/test'], + roots: [''], testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], transform: { '^.+\\.ts$': 'ts-jest', }, + // Run against TypeScript sources (no build needed). + moduleNameMapper: { + '^@latex2js/utils$': '/../../packages/utils/src/index.ts', + '^@latex2js/settings$': '/../../packages/settings/src/index.ts', + }, }; export default config; diff --git a/packages/pstricks/src/lib/psgraph.ts b/packages/pstricks/src/lib/psgraph.ts index f8c352da..0e7c8cc7 100644 --- a/packages/pstricks/src/lib/psgraph.ts +++ b/packages/pstricks/src/lib/psgraph.ts @@ -35,6 +35,54 @@ function arrow(x1: number, y1: number, x2: number, y2: number) { return context.join(' '); } +/** + * Catmull-Rom → cubic Bézier path for a flat [x0,y0,x1,y1,...] point list. + * `closed` wraps the curve back to the start point. + */ +function buildCurvePath(data: number[], closed: boolean): string { + const pts: Array<[number, number]> = []; + for (let i = 0; i < data.length; i += 2) pts.push([data[i], data[i + 1]]); + const n = pts.length; + if (n < 2) return ''; + const at = (i: number) => pts[((i % n) + n) % n]; + let d = 'M ' + pts[0][0] + ' ' + pts[0][1]; + for (let i = 0; i < n - 1; i++) { + const p0 = closed ? at(i - 1) : i === 0 ? pts[0] : pts[i - 1]; + const p1 = pts[i]; + const p2 = pts[i + 1]; + const p3 = closed ? at(i + 2) : i + 2 < n ? pts[i + 2] : pts[i + 1]; + const c1x = p1[0] + (p2[0] - p0[0]) / 6; + const c1y = p1[1] + (p2[1] - p0[1]) / 6; + const c2x = p2[0] - (p3[0] - p1[0]) / 6; + const c2y = p2[1] - (p3[1] - p1[1]) / 6; + d += ' C ' + c1x + ' ' + c1y + ', ' + c2x + ' ' + c2y + ', ' + p2[0] + ' ' + p2[1]; + } + if (closed) { + const pn1 = pts[n - 1]; + const p0 = pts[0]; + const pn2 = pts[n - 2]; + const p1 = pts[1]; + const c1x = pn1[0] + (p0[0] - pn2[0]) / 6; + const c1y = pn1[1] + (p0[1] - pn2[1]) / 6; + const c2x = p0[0] - (p1[0] - pn1[0]) / 6; + const c2y = p0[1] - (p1[1] - pn1[1]) / 6; + d += ' C ' + c1x + ' ' + c1y + ', ' + c2x + ' ' + c2y + ', ' + p0[0] + ' ' + p0[1] + ' Z'; + } + return d; +} + +function curveRenderer(this: any, svg: any): void { + const d = buildCurvePath(this.data, !!this.closed); + if (!d) return; + svg + .append('svg:path') + .attr('d', d) + .style('stroke-width', this.linewidth) + .style('stroke', this.linecolor) + .style('stroke-opacity', 1) + .style('fill', this.fillstyle === 'solid' || this.filled ? this.fillcolor : 'none'); +} + const psgraph: any = { env: null as any, getSize(): { width: number; height: number } { @@ -56,6 +104,18 @@ const psgraph: any = { }, psframe(svg: any): void { + const filled = this.filled || this.fillstyle === 'solid'; + if (filled) { + svg + .append('svg:rect') + .attr('x', Math.min(this.x1, this.x2)) + .attr('y', Math.min(this.y1, this.y2)) + .attr('width', Math.abs(this.x2 - this.x1)) + .attr('height', Math.abs(this.y2 - this.y1)) + .style('fill', this.fillcolor) + .style('stroke', 'none'); + } + svg .append('svg:line') .attr('x1', this.x1) @@ -98,14 +158,15 @@ const psgraph: any = { }, pscircle: function (svg: any) { + const filled = this.filled || this.fillstyle === 'solid'; svg .append('svg:circle') .attr('cx', this.cx) .attr('cy', this.cy) .attr('r', this.r) - .style('stroke', 'black') - .style('fill', 'none') - .style('stroke-width', 2) + .style('stroke', this.linecolor) + .style('fill', filled ? this.fillcolor : 'none') + .style('stroke-width', this.linewidth) .style('stroke-opacity', 1); }, @@ -158,38 +219,29 @@ const psgraph: any = { .attr('d', context.join(' ')) .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'none' ? 'none' : this.fillcolor) + .style('fill', this.fillstyle === 'none' && !this.filled ? 'none' : this.fillcolor) .style('stroke', 'black'); }, psarc(svg: any): void { - var context = []; - context.push('M'); - context.push(this.cx); - context.push(this.cy); - context.push('L'); - context.push(this.A.x); - context.push(this.A.y); - - context.push('A'); - - context.push(this.A.x); - context.push(this.A.y); - - context.push(0); - context.push(0); - context.push(0); - - context.push(this.B.x); - context.push(this.B.y); - + const sweep = this.angleB - this.angleA > 0 ? 1 : 0; + const large = Math.abs(this.angleB - this.angleA) > Math.PI ? 1 : 0; + const filled = this.filled || this.fillstyle === 'solid'; + const d = filled + ? 'M ' + this.cx + ' ' + this.cy + + ' L ' + this.A.x + ' ' + this.A.y + + ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + + ' ' + this.B.x + ' ' + this.B.y + ' Z' + : 'M ' + this.A.x + ' ' + this.A.y + + ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + + ' ' + this.B.x + ' ' + this.B.y; svg .append('svg:path') - .attr('d', context.join(' ')) - .style('stroke-width', 2) + .attr('d', d) + .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', 'blue') - .style('stroke', 'black'); + .style('fill', filled ? this.fillcolor : 'none') + .style('stroke', this.linecolor); }, psaxes(svg: any): void { @@ -590,16 +642,29 @@ const psgraph: any = { var env = this.env; var el = this.$el; - Object.keys(this.plot).forEach((key) => { - const plot = this.plot[key]; - if (key.match(/rput/)) return; - if (psgraph.hasOwnProperty(key)) { - plot.forEach((data: any) => { - data.data.global = env; - psgraph[key].call(data.data, svg); - }); - } - }); + // Source-order initial draw: the parser records `env.elements` in + // document order, so layers (fills under lines, etc.) respect the author's + // order. Falls back to the old type-grouped iteration for legacy data. + const elements = env && env.elements; + if (elements && elements.length) { + elements.forEach((item: any) => { + if (!item || !item.name || item.name.match(/rput/)) return; + if (!psgraph.hasOwnProperty(item.name)) return; + item.data.global = env; + psgraph[item.name].call(item.data, svg); + }); + } else { + Object.keys(this.plot).forEach((key) => { + const plot = this.plot[key]; + if (key.match(/rput/)) return; + if (psgraph.hasOwnProperty(key)) { + plot.forEach((data: any) => { + data.data.global = env; + psgraph[key].call(data.data, svg); + }); + } + }); + } svg.on( 'touchmove', @@ -683,10 +748,144 @@ const psgraph: any = { }); } - // Enhanced cleanup and RPUT processing - psgraph.processRputElements.call(this, el); + // Enhanced cleanup and RPUT processing + psgraph.processRputElements.call(this, el); + }, + + psdots(svg: any): void { + for (let i = 0; i < this.data.length; i += 2) { + svg + .append('svg:circle') + .attr('cx', this.data[i]) + .attr('cy', this.data[i + 1]) + .attr('r', this.dotsize) + .style('fill', this.linecolor) + .style('stroke', 'none'); + } + }, + + psgrid(svg: any): void { + const x0 = this.x0, y0 = this.y0, x1 = this.x1, y1 = this.y1; + for (let x = x0; x <= x1 + 0.001; x += this.xunit) { + svg + .append('svg:line') + .attr('x1', x).attr('y1', y0) + .attr('x2', x).attr('y2', y1) + .style('stroke', this.linecolor) + .style('stroke-width', this.gridwidth) + .style('stroke-opacity', 1); + } + for (let y = y0; y <= y1 + 0.001; y += this.yunit) { + svg + .append('svg:line') + .attr('x1', x0).attr('y1', y) + .attr('x2', x1).attr('y2', y) + .style('stroke', this.linecolor) + .style('stroke-width', this.gridwidth) + .style('stroke-opacity', 1); + } + }, + + psellipse(svg: any): void { + svg + .append('svg:ellipse') + .attr('cx', this.cx) + .attr('cy', this.cy) + .attr('rx', this.rx) + .attr('ry', this.ry) + .style('stroke', this.linecolor) + .style('stroke-width', this.linewidth) + .style('stroke-opacity', 1) + .style('fill', this.fillstyle === 'solid' ? this.fillcolor : 'none'); + }, + + psbezier(svg: any): void { + svg + .append('svg:path') + .attr( + 'd', + 'M ' + this.x1 + ' ' + this.y1 + + ' C ' + this.x2 + ' ' + this.y2 + ', ' + this.x3 + ' ' + this.y3 + ', ' + this.x4 + ' ' + this.y4 + ) + .style('stroke-width', this.linewidth) + .style('stroke', this.linecolor) + .style('stroke-opacity', 1) + .style('fill', 'none'); }, + pscurve(svg: any): void { + const d = buildCurvePath(this.data, !!this.closed); + if (!d) return; + svg + .append('svg:path') + .attr('d', d) + .style('stroke-width', this.linewidth) + .style('stroke', this.linecolor) + .style('stroke-opacity', 1) + .style('fill', this.fillstyle === 'solid' || this.filled ? this.fillcolor : 'none'); + }, + + psecurve: curveRenderer, + psccurve: curveRenderer, + + pswedge(svg: any): void { + const sweep = this.angleB - this.angleA > 0 ? 1 : 0; + const large = Math.abs(this.angleB - this.angleA) > Math.PI ? 1 : 0; + svg + .append('svg:path') + .attr( + 'd', + 'M ' + this.cx + ' ' + this.cy + + ' L ' + this.A.x + ' ' + this.A.y + + ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + + ' ' + this.B.x + ' ' + this.B.y + ' Z' + ) + .style('stroke-width', this.linewidth) + .style('stroke', this.linecolor) + .style('stroke-opacity', 1) + .style('fill', this.fillstyle === 'solid' ? this.fillcolor : 'none'); + }, + + pscustom(svg: any): void { + const filled = this.filled || this.fillstyle === 'solid'; + let d = ''; + let started = false; + (this.commands || []).forEach((cmd: any) => { + const data = cmd.data; + if (!data) return; + if (cmd.key === 'psline' || cmd.key === 'userline' || cmd.key === 'psbezier') { + if (cmd.key === 'psbezier') { + if (!started) { d += 'M ' + data.x1 + ' ' + data.y1; started = true; } + d += ' C ' + data.x2 + ' ' + data.y2 + ', ' + data.x3 + ' ' + data.y3 + ', ' + data.x4 + ' ' + data.y4; + return; + } + if (!started) { d += 'M ' + data.x1 + ' ' + data.y1; started = true; } + d += ' L ' + data.x2 + ' ' + data.y2; + } else if (cmd.key === 'psframe') { + if (!started) { d += 'M ' + data.x1 + ' ' + data.y1; started = true; } + d += ' L ' + data.x2 + ' ' + data.y1 + + ' L ' + data.x2 + ' ' + data.y2 + + ' L ' + data.x1 + ' ' + data.y2 + ' Z'; + } else if (cmd.key === 'pspolygon' || cmd.key === 'pscurve') { + const pts = data.data || []; + if (pts.length < 2) return; + if (!started) { d += 'M ' + pts[0] + ' ' + pts[1]; started = true; } + for (let i = 2; i < pts.length; i += 2) d += ' L ' + pts[i] + ' ' + pts[i + 1]; + d += ' Z'; + } + }); + if (!started) return; + if (filled) d += ' Z'; + svg + .append('svg:path') + .attr('d', d) + .style('stroke-width', this.linewidth) + .style('stroke', this.linestyle === 'none' ? 'none' : this.linecolor) + .style('stroke-opacity', 1) + .style('fill', filled ? this.fillcolor : 'none'); + }, + + processRputElements(el: any): void { // Validate container if (!el || typeof el.querySelectorAll !== 'function') { diff --git a/packages/pstricks/src/lib/pstricks.ts b/packages/pstricks/src/lib/pstricks.ts index 05584213..eea8e4c4 100644 --- a/packages/pstricks/src/lib/pstricks.ts +++ b/packages/pstricks/src/lib/pstricks.ts @@ -3,6 +3,7 @@ import { parseOptions, parseArrows, evaluate, + parseExpression, X, Xinv, Y, @@ -11,12 +12,22 @@ import { import Settings from '@latex2js/settings'; +/** + * Parse a PSTricks linewidth value: a bare number is used as-is (SVG px), + * a `pt` value is converted to px (1pt ≈ 1.333px). + */ +function parseLinewidth(value: string): number { + const m = value.trim().match(/^([\d.]+)\s*(pt)?$/); + if (!m) return 2; + return Number(m[1]) * (m[2] ? 1.333 : 1); +} + export const Expressions = { pspicture: /\\begin\{pspicture\}\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, - psframe: /\\psframe\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, - psplot: /\\psplot(\[[^\]]*\])?\{([^\}]*)\}\{([^\}]*)\}\{([^\}]*)\}/, + psframe: /\\psframe\*?(\[[^\]]*\])?\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, + psplot: /\\psplot\*?(\[[^\]]*\])?\{([^\}]*)\}\{([^\}]*)\}\{([^\}]*)\}/, psarc: new RegExp( - '\\\\psarc' + + '\\\\psarc\\*?' + RE.options + RE.type + RE.coords + @@ -25,9 +36,9 @@ export const Expressions = { RE.squiggle ), pscircle: /\\pscircle.*\(\s*(.*),(.*)\s*\)\{(.*)\}/, - pspolygon: new RegExp('\\\\pspolygon' + RE.options + '(.*)'), + pspolygon: new RegExp('\\\\pspolygon\\*?' + RE.options + '(.*)'), psaxes: new RegExp( - '\\\\psaxes' + + '\\\\psaxes\\*?' + RE.options + RE.type + RE.coords + @@ -44,7 +55,7 @@ export const Expressions = { RE.squiggle ), psline: new RegExp( - '\\\\psline' + RE.options + RE.type + RE.coords + RE.coordsOpt + '\\\\psline\\*?' + RE.options + RE.type + RE.coords + RE.coordsOpt ), userline: new RegExp( '\\\\userline' + @@ -61,7 +72,19 @@ export const Expressions = { '\\\\uservariable' + RE.options + RE.squiggle + RE.coords + RE.squiggle ), rput: /\\rput\((.*),(.*)\)\{(.*)\}/, - psset: /\\psset\{(.*)\}/ + psset: /\\psset\{(.*)\}/, + psdots: new RegExp('\\\\psdots' + RE.options + '(.*)'), + psgrid: new RegExp( + '\\\\psgrid' + RE.options + RE.coordsOpt + RE.coordsOpt + RE.coordsOpt + ), + psellipse: /\\psellipse.*\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, + psbezier: /\\psbezier(\[[^\]]*\])?\((.*),(.*)\)\((.*),(.*)\)\((.*),(.*)\)\((.*),(.*)\)/, + pscurve: new RegExp('\\\\pscurve' + RE.options + RE.coords + '(.*)'), + psecurve: new RegExp('\\\\psecurve' + RE.options + RE.coords + '(.*)'), + psccurve: new RegExp('\\\\psccurve' + RE.options + RE.coords + '(.*)'), + pswedge: /\\pswedge(\[[^\]]*\])?\(\s*(.*),(.*)\s*\)\{(.*)\}\{(.*)\}\{(.*)\}/, + pscustom: /\\pscustom(\[[^\]]*\])?\{([\s\S]*)\}/, + multido: /\\multido\{([^}]*)\}\{([^}]*)\}\{([\s\S]*)\}/ }; export interface PSTricksContext { @@ -114,20 +137,35 @@ export const Functions = { return Object.assign(p, s); }, psframe(this: PSTricksContext, m: any) { - var obj = { - x1: X.call(this, m[1]), - y1: Y.call(this, m[2]), - x2: X.call(this, m[3]), - y2: Y.call(this, m[4]) + var obj: any = { + x1: X.call(this, m[2]), + y1: Y.call(this, m[3]), + x2: X.call(this, m[4]), + y2: Y.call(this, m[5]), + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'none', + fillcolor: 'black', + linewidth: 2, + filled: /\\psframe\*/.test(m[0]) }; + if (m[1]) Object.assign(obj, parseOptions(m[1])); return obj; }, pscircle(this: PSTricksContext, m: any) { - var obj = { + var obj: any = { cx: X.call(this, m[1]), cy: Y.call(this, m[2]), - r: this.xunit * m[3] + r: this.xunit * m[3], + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'none', + fillcolor: 'black', + linewidth: 2, + filled: /\\pscircle\*/.test(m[0]) }; + var opts = m[0].match(/\[([^\]]*)\]/); + if (opts) Object.assign(obj, parseOptions(opts[1])); return obj; }, psaxes(this: PSTricksContext, m: any) { @@ -184,31 +222,9 @@ export const Functions = { psplot(this: PSTricksContext, m: any) { var startX = evaluate.call(this, m[2]); var endX = evaluate.call(this, m[3]); - var data = []; + var data: number[] = []; var x; - // get env - var expression = ''; - Object.entries(this.variables || {}).forEach(([name, val]) => { - expression += 'var ' + name + ' = ' + val + ';'; - }); - const mathFunctions = 'var cos=Math.cos,sin=Math.sin,tan=Math.tan,atan=Math.atan,atan2=Math.atan2,exp=Math.exp,log=Math.log,sqrt=Math.sqrt,abs=Math.abs,floor=Math.floor,ceil=Math.ceil,round=Math.round,pow=Math.pow,PI=Math.PI,E=Math.E;'; - expression += mathFunctions + 'return ' + m[4] + ';'; - - for (x = startX; x <= endX; x += 0.005) { - data.push(X.call(this, x)); - try { - const evalFunc = new Function('x', expression); - const yValue = evalFunc(x); - if (yValue !== undefined && !isNaN(yValue)) { - data.push(Y.call(this, yValue)); - } else { - data.push(Y.call(this, 0)); - } - } catch (err) { - data.push(Y.call(this, 0)); // fallback value - } - } var obj: any = { linecolor: 'black', linestyle: 'solid', @@ -217,6 +233,37 @@ export const Functions = { linewidth: 2 }; if (m[1]) Object.assign(obj, parseOptions(m[1])); + + // Sampling: honor `plotpoints=N` (number of samples); default to a + // fixed 0.005 step like the original implementation. + var step = 0.005; + var plotpoints = obj.plotpoints ? Number(obj.plotpoints) : 0; + if (plotpoints > 1) { + step = (endX - startX) / (plotpoints - 1); + } + + // Compile the plot expression once; evaluate per sample against a + // reused scope (compile-once / evaluate-many). + let compiled; + try { + compiled = parseExpression(m[4]); + } catch (err) { + console.warn('psplot: could not parse expression:', (err as Error).message); + obj.data = data; + return obj; + } + const scope: any = Object.assign({}, this.variables || {}); + + for (x = startX; x <= endX + step / 2; x += step) { + data.push(X.call(this, x)); + scope.x = x; + const yValue = compiled.evaluate(scope); + if (yValue !== undefined && !isNaN(yValue)) { + data.push(Y.call(this, yValue)); + } else { + data.push(Y.call(this, 0)); + } + } obj.data = data; return obj; }, @@ -234,12 +281,13 @@ export const Functions = { data.push(Y.call(this, d[2])); } }); - var obj = { + var obj: any = { linecolor: 'black', linestyle: 'solid', fillstyle: 'none', fillcolor: 'black', linewidth: 2, + filled: /\\pspolygon\*/.test(m[0]), data: data }; if (m[1]) Object.assign(obj, parseOptions(m[1])); @@ -257,6 +305,7 @@ export const Functions = { linewidth: 2, arrows: arrows, dots: dots, + filled: /\\psarc\*/.test(m[0]), cx: X.call(this, 0), cy: Y.call(this, 0) }; @@ -303,7 +352,8 @@ export const Functions = { fillcolor: 'black', linewidth: 2, arrows: arrows, - dots: dots + dots: dots, + filled: /\\psline\*/.test(m[0]) }; if (m[5]) { obj.x1 = X.call(this, m[3]); @@ -321,7 +371,7 @@ export const Functions = { } // TODO: add regex if (typeof obj.linewidth === 'string') { - obj.linewidth = 2; + obj.linewidth = parseLinewidth(obj.linewidth); } return obj; }, @@ -338,25 +388,20 @@ export const Functions = { } var nx1 = Xinv.call(this, coords[0]); var ny1 = Yinv.call(this, coords[1]); - var expx1 = 'var x = ' + nx1 + ';'; - var expy1 = 'var y = ' + ny1 + ';'; - // return X.call(this, eval(expy1 + expx1 + xExp)); - var obj = { + var obj: any = { name: m[2], x: X.call(this, m[3]), y: Y.call(this, m[4]), func: m[5], - value: (() => { - try { - const mathFunctions = 'var cos=Math.cos,sin=Math.sin,tan=Math.tan,atan=Math.atan,atan2=Math.atan2,exp=Math.exp,log=Math.log,sqrt=Math.sqrt,abs=Math.abs,floor=Math.floor,ceil=Math.ceil,round=Math.round,pow=Math.pow,PI=Math.PI,E=Math.E;'; - const evalFunc = new Function('', mathFunctions + expx1 + expy1 + 'return ' + m[5]); - return evalFunc(); - } catch (err) { - console.warn('Error evaluating uservariable expression:', err); - return 0; - } - })() + value: 0 }; + try { + obj.value = parseExpression(m[5]).evaluate( + Object.assign({ x: nx1, y: ny1 }, this.variables || {}) + ); + } catch (err) { + console.warn('Error evaluating uservariable expression:', (err as Error).message); + } return obj; }, userline(this: PSTricksContext, m: any) { @@ -366,42 +411,42 @@ export const Functions = { var l = parseArrows(lineType); var arrows = l.arrows; var dots = l.dots; - var xExp = m[7]; - var yExp = m[8]; - const mathFunctions = 'var cos=Math.cos,sin=Math.sin,tan=Math.tan,atan=Math.atan,atan2=Math.atan2,exp=Math.exp,log=Math.log,sqrt=Math.sqrt,abs=Math.abs,floor=Math.floor,ceil=Math.ceil,round=Math.round,pow=Math.pow,PI=Math.PI,E=Math.E;'; - if (xExp) - xExp = mathFunctions + xExp.replace(/^\{/, '').replace(/\}$/, ''); - if (yExp) - yExp = mathFunctions + yExp.replace(/^\{/, '').replace(/\}$/, ''); - var xExp2 = m[9]; - var yExp2 = m[10]; - if (xExp2) - xExp2 = mathFunctions + xExp2.replace(/^\{/, '').replace(/\}$/, ''); - if (yExp2) - yExp2 = mathFunctions + yExp2.replace(/^\{/, '').replace(/\}$/, ''); - var expression = ''; - Object.entries(this.variables || {}).forEach(([name, val]) => { - expression += 'var ' + name + ' = ' + val + ';'; - }); - var obj = { + // Compile the interactive head/tail expressions once; each mousemove just + // re-evaluates them against a fresh {x, y} scope (compile-once). + const stripBraces = (s?: string) => (s ? s.replace(/^\{/, '').replace(/\}$/, '').trim() : null); + const compileOpt = (src: string | null) => { + if (!src) return null; + try { + return parseExpression(src); + } catch (err) { + console.warn('userline: could not parse expression:', (err as Error).message); + return null; + } + }; + const xExp = compileOpt(stripBraces(m[7])); + const yExp = compileOpt(stripBraces(m[8])); + const xExp2 = compileOpt(stripBraces(m[9])); + const yExp2 = compileOpt(stripBraces(m[10])); + const variables = this.variables || {}; + + const evalAt = (compiled: any, x: number, y: number) => + compiled.evaluate(Object.assign({ x: x, y: y }, variables)); + + var obj: any = { x1: X.call(this, m[3]), y1: Y.call(this, m[4]), x2: X.call(this, m[5]), y2: Y.call(this, m[6]), - xExp: xExp, - yExp: yExp, - xExp2: xExp2, - yExp2: yExp2, + xExp: m[7], + yExp: m[8], + xExp2: m[9], + yExp2: m[10], userx: (coords: number[]) => { var nx1 = Xinv.call(this, coords[0]); var ny1 = Yinv.call(this, coords[1]); - var expx1 = 'var x = ' + nx1 + ';'; - var expy1 = 'var y = ' + ny1 + ';'; try { - const cleanExp = xExp ? xExp.replace(/^var cos=Math\.cos[^;]*;/, '') : '0'; - const evalFunc = new Function('', mathFunctions + expression + expy1 + expx1 + 'return (' + cleanExp + ')'); - return X.call(this, evalFunc()); + return X.call(this, xExp ? evalAt(xExp, nx1, ny1) : 0); } catch (err) { console.warn('Error evaluating userx expression:', err); return X.call(this, 0); @@ -410,12 +455,8 @@ export const Functions = { usery: (coords: number[]) => { var nx2 = Xinv.call(this, coords[0]); var ny2 = Yinv.call(this, coords[1]); - var expx2 = 'var x = ' + nx2 + ';'; - var expy2 = 'var y = ' + ny2 + ';'; try { - const cleanExp = yExp ? yExp.replace(/^var cos=Math\.cos[^;]*;/, '') : '0'; - const evalFunc = new Function('', mathFunctions + expression + expy2 + expx2 + 'return (' + cleanExp + ')'); - return Y.call(this, evalFunc()); + return Y.call(this, yExp ? evalAt(yExp, nx2, ny2) : 0); } catch (err) { console.warn('Error evaluating usery expression:', err); return Y.call(this, 0); @@ -424,12 +465,8 @@ export const Functions = { userx2: (coords: number[]) => { var nx3 = Xinv.call(this, coords[0]); var ny3 = Yinv.call(this, coords[1]); - var expx3 = 'var x = ' + nx3 + ';'; - var expy3 = 'var y = ' + ny3 + ';'; try { - const cleanExp = xExp2 ? xExp2.replace(/^var cos=Math\.cos[^;]*;/, '') : '0'; - const evalFunc = new Function('', mathFunctions + expression + expy3 + expx3 + 'return (' + cleanExp + ')'); - return X.call(this, evalFunc()); + return X.call(this, xExp2 ? evalAt(xExp2, nx3, ny3) : 0); } catch (err) { console.warn('Error evaluating userx2 expression:', err); return X.call(this, 0); @@ -438,12 +475,8 @@ export const Functions = { usery2: (coords: number[]) => { var nx4 = Xinv.call(this, coords[0]); var ny4 = Yinv.call(this, coords[1]); - var expx4 = 'var x = ' + nx4 + ';'; - var expy4 = 'var y = ' + ny4 + ';'; try { - const cleanExp = yExp2 ? yExp2.replace(/^var cos=Math\.cos[^;]*;/, '') : '0'; - const evalFunc = new Function('', mathFunctions + expression + expy4 + expx4 + 'return (' + cleanExp + ')'); - return Y.call(this, evalFunc()); + return Y.call(this, yExp2 ? evalAt(yExp2, nx4, ny4) : 0); } catch (err) { console.warn('Error evaluating usery2 expression:', err); return Y.call(this, 0); @@ -462,7 +495,7 @@ export const Functions = { } // TODO: add regex if (typeof obj.linewidth === 'string') { - obj.linewidth = 2; + obj.linewidth = parseLinewidth(obj.linewidth); } return obj; }, @@ -487,9 +520,160 @@ export const Functions = { }); }); return obj; + }, + psdots(this: PSTricksContext, m: any) { + var obj: any = { + linecolor: 'black', + dotstyle: 'dot', + dotsize: 2, + data: parseCoordList.call(this, m[2]) + }; + if (m[1]) Object.assign(obj, parseOptions(m[1])); + return obj; + }, + psgrid(this: PSTricksContext, m: any) { + var obj: any = { + linecolor: 'black', + linestyle: 'solid', + linewidth: 0.5, + gridwidth: 0.5 + }; + if (m[1]) Object.assign(obj, parseOptions(m[1])); + // \psgrid[opts](x0,y0)(x1,y1) — defaults to the whole pspicture bounds. + // coordsOpt outer groups: m[2]/m[5]/m[8] = '(x,y)' strings, m[3],m[4] etc. + var has0 = m[3] !== undefined; + var has1 = m[6] !== undefined; + var x0 = has0 ? X.call(this, m[3]) : X.call(this, this.x0); + var y0 = has0 ? Y.call(this, m[4]) : Y.call(this, this.y0); + var x1 = has1 ? X.call(this, m[6]) : X.call(this, this.x1); + var y1 = has1 ? Y.call(this, m[7]) : Y.call(this, this.y1); + obj.x0 = Math.min(x0, x1); + obj.y0 = Math.min(y0, y1); + obj.x1 = Math.max(x0, x1); + obj.y1 = Math.max(y0, y1); + obj.xunit = this.xunit; + obj.yunit = this.yunit; + return obj; + }, + psellipse(this: PSTricksContext, m: any) { + var obj: any = { + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'none', + fillcolor: 'black', + linewidth: 2 + }; + var opts = m[0].match(/\[([^\]]*)\]/); + if (opts) Object.assign(obj, parseOptions(opts[1])); + obj.cx = X.call(this, m[1]); + obj.cy = Y.call(this, m[2]); + obj.rx = Math.abs(Number(m[3])) * this.xunit; + obj.ry = Math.abs(Number(m[4])) * this.yunit; + return obj; + }, + psbezier(this: PSTricksContext, m: any) { + var obj: any = { + linecolor: 'black', + linestyle: 'solid', + linewidth: 2 + }; + if (m[1]) Object.assign(obj, parseOptions(m[1])); + obj.x1 = X.call(this, m[2]); + obj.y1 = Y.call(this, m[3]); + obj.x2 = X.call(this, m[4]); + obj.y2 = Y.call(this, m[5]); + obj.x3 = X.call(this, m[6]); + obj.y3 = Y.call(this, m[7]); + obj.x4 = X.call(this, m[8]); + obj.y4 = Y.call(this, m[9]); + return obj; + }, + pscurve(this: PSTricksContext, m: any) { + var obj: any = { + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'none', + fillcolor: 'black', + linewidth: 2, + closed: /\\psecurve|\\psccurve/.test(m[0]) + }; + if (m[1]) Object.assign(obj, parseOptions(m[1])); + // first point is captured separately (m[2], m[3]); the rest follow + obj.data = [X.call(this, m[2]), Y.call(this, m[3])].concat( + parseCoordList.call(this, m[4] || '') + ); + return obj; + }, + psecurve(this: PSTricksContext, m: any) { + return Functions.pscurve.call(this, m); + }, + psccurve(this: PSTricksContext, m: any) { + return Functions.pscurve.call(this, m); + }, + pswedge(this: PSTricksContext, m: any) { + var obj: any = { + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'solid', + fillcolor: 'black', + linewidth: 2 + }; + if (m[1]) Object.assign(obj, parseOptions(m[1])); + obj.cx = X.call(this, m[2]); + obj.cy = Y.call(this, m[3]); + obj.r = Number(m[4]) * this.xunit; + obj.angleA = (Number(m[5]) * Math.PI) / 180; + obj.angleB = (Number(m[6]) * Math.PI) / 180; + obj.A = { + x: X.call(this, Number(m[4]) * Math.cos(obj.angleA)), + y: Y.call(this, Number(m[4]) * Math.sin(obj.angleA)) + }; + obj.B = { + x: X.call(this, Number(m[4]) * Math.cos(obj.angleB)), + y: Y.call(this, Number(m[4]) * Math.sin(obj.angleB)) + }; + return obj; + }, + pscustom(this: PSTricksContext, m: any) { + var obj: any = { + linecolor: 'black', + linestyle: 'solid', + fillstyle: 'none', + fillcolor: 'black', + linewidth: 2, + body: m[2] + }; + if (m[1]) Object.assign(obj, parseOptions(m[1])); + return obj; + }, + multido(this: PSTricksContext, m: any) { + var spec = m[1] || ''; + var varMatch = spec.match(/\\([a-zA-Z@]+)\s*=\s*([\d.+-]+)\s*\+\s*([\d.+-]+)/); + return { + variable: varMatch ? varMatch[1] : null, + start: varMatch ? Number(varMatch[2]) : 0, + step: varMatch ? Number(varMatch[3]) : 1, + count: Number(m[2]), + body: m[3] + }; } }; +/** + * Parse a coordinate list like `(0,0)(1,1)(2,2)` into a flat + * [x0,y0,x1,y1,...] pixel array. + */ +function parseCoordList(this: PSTricksContext, coords: string): number[] { + var data: number[] = []; + var re = new RegExp(RE.coords, 'g'); + var m: RegExpExecArray | null; + while ((m = re.exec(coords)) !== null) { + data.push(X.call(this, m[1])); + data.push(Y.call(this, m[2])); + } + return data; +} + export default { Expressions, Functions diff --git a/packages/pstricks/test/pstricks.test.ts b/packages/pstricks/test/pstricks.test.ts new file mode 100644 index 00000000..f6810680 --- /dev/null +++ b/packages/pstricks/test/pstricks.test.ts @@ -0,0 +1,243 @@ +import { Expressions, Functions } from '../src/lib/pstricks'; + +/** + * PSTricks data-extraction unit tests. Functions are called with a minimal + * pspicture-like context (the same shape the parser provides). + */ +function makeContext() { + // pspicture(-5,-5)(5,5): w=10, h=10, x1=5, y1=5, xunit=50, yunit=50 + // X(v) = (10 - (5 - v)) * 50 = (5 + v) * 50 + // Y(v) = (5 - v) * 50 + return { + xunit: 50, + yunit: 50, + x0: -5, + y0: -5, + x1: 5, + y1: 5, + w: 10, + h: 10, + variables: {}, + } as any; +} + +function match(exp: RegExp, raw: string): RegExpMatchArray { + const m = raw.match(exp); + expect(m).not.toBeNull(); + return m!; +} + +describe('pstricks Functions', () => { + it('psline maps coordinates and arrow types', () => { + const ctx = makeContext(); + const m = match(Expressions.psline, '\\psline{->}(0,-3.75)(0,3.75)'); + const data = Functions.psline.call(ctx, m); + expect(data.x1).toBe(250); // X(0) + expect(data.y1).toBe(437.5); // Y(-3.75) + expect(data.x2).toBe(250); + expect(data.y2).toBe(62.5); // Y(3.75) + expect(data.arrows).toEqual([0, 1]); + }); + + it('psline parses linewidth units', () => { + const ctx = makeContext(); + const m = match(Expressions.psline, '\\psline[linewidth=1.5 pt](0,0)(1,1)'); + const data = Functions.psline.call(ctx, m); + expect(data.linewidth).toBeCloseTo(2, 1); + }); + + it('pscircle computes center, radius and filled star', () => { + const ctx = makeContext(); + const plain = Functions.pscircle.call(ctx, match(Expressions.pscircle, '\\pscircle(0,0){3}')); + expect(plain.cx).toBe(250); + expect(plain.cy).toBe(250); + expect(plain.r).toBe(150); + expect(plain.filled).toBe(false); + + const starred = Functions.pscircle.call(ctx, match(Expressions.pscircle, '\\pscircle*(0,0){3}')); + expect(starred.filled).toBe(true); + }); + + it('psarc converts angles to radians and computes endpoints', () => { + const ctx = makeContext(); + const m = match(Expressions.psarc, '\\psarc(0,0){2}{0}{90}'); + const data = Functions.psarc.call(ctx, m); + expect(data.r).toBe(100); + expect(data.angleA).toBe(0); + expect(data.angleB).toBeCloseTo(Math.PI / 2); + expect(data.A.x).toBeCloseTo(250 + 100); // cos(0)=1 + expect(data.B.y).toBeCloseTo(250 - 100); // sin(90)=1 (Y flips) + }); + + it('pspolygon parses coordinate lists and filled star', () => { + const ctx = makeContext(); + const m = match(Expressions.pspolygon, '\\pspolygon*(0,0)(1,1)(2,0)'); + const data = Functions.pspolygon.call(ctx, m); + expect(data.data).toHaveLength(6); + expect(data.filled).toBe(true); + }); + + it('psdots collects all points', () => { + const ctx = makeContext(); + const m = match(Expressions.psdots, '\\psdots(1,1)(2,2)'); + const data = Functions.psdots.call(ctx, m); + expect(data.data).toHaveLength(4); + }); + + it('psgrid defaults to the pspicture bounds', () => { + const ctx = makeContext(); + const m = match(Expressions.psgrid, '\\psgrid'); + const data = Functions.psgrid.call(ctx, m); + // X(-5)=0, X(5)=500; Y(-5)=500, Y(5)=0 — normalized min/max + expect(data.x0).toBe(0); + expect(data.x1).toBe(500); + expect(data.y0).toBe(0); + expect(data.y1).toBe(500); + expect(data.xunit).toBe(50); + }); + + it('psellipse computes radii in pixels', () => { + const ctx = makeContext(); + const m = match(Expressions.psellipse, '\\psellipse[fillstyle=solid,fillcolor=lightblue](2,2)(1,0.5)'); + const data = Functions.psellipse.call(ctx, m); + expect(data.cx).toBe(350); // X(2) + expect(data.cy).toBe(150); // Y(2) + expect(data.rx).toBe(50); + expect(data.ry).toBe(25); + expect(data.fillcolor).toBe('lightblue'); + }); + + it('psbezier captures four control points', () => { + const ctx = makeContext(); + const m = match(Expressions.psbezier, '\\psbezier(0,0)(1,2)(2,2)(3,0)'); + const data = Functions.psbezier.call(ctx, m); + expect(data.x1).toBe(250); + expect(data.y1).toBe(250); + expect(data.x4).toBe(400); // X(3) + expect(data.y4).toBe(250); + }); + + it('pscurve and psccurve collect points with closure flag', () => { + const ctx = makeContext(); + const open = Functions.pscurve.call(ctx, match(Expressions.pscurve, '\\pscurve(0,0)(1,1)(2,0)')); + expect(open.closed).toBe(false); + expect(open.data).toHaveLength(6); + + const closed = Functions.pscurve.call(ctx, match(Expressions.psccurve, '\\psccurve(0,1)(1,2)(2,1)')); + expect(closed.closed).toBe(true); + }); + + it('pswedge computes pie-slice geometry', () => { + const ctx = makeContext(); + const m = match(Expressions.pswedge, '\\pswedge(2,2){1}{0}{90}'); + const data = Functions.pswedge.call(ctx, m); + expect(data.r).toBe(50); + expect(data.angleA).toBe(0); + expect(data.angleB).toBeCloseTo(Math.PI / 2); + expect(data.A).toBeDefined(); + expect(data.B).toBeDefined(); + }); + + it('pscustom captures options and body', () => { + const ctx = makeContext(); + const m = match(Expressions.pscustom, '\\pscustom[fillstyle=solid,fillcolor=gray!40]{\\psline(0,0)(4,1.2)}'); + const data = Functions.pscustom.call(ctx, m); + expect(data.fillstyle).toBe('solid'); + expect(data.fillcolor).toBe('gray!40'); + expect(data.body).toContain('\\psline(0,0)(4,1.2)'); + }); + + it('multido parses the counter spec', () => { + const ctx = makeContext(); + const m = match(Expressions.multido, '\\multido{\\i=10+-1}{5}{\\psline(\\i,0)(\\i,1)}'); + const data = Functions.multido.call(ctx, m); + expect(data.variable).toBe('i'); + expect(data.start).toBe(10); + expect(data.step).toBe(-1); + expect(data.count).toBe(5); + expect(data.body).toContain('\\psline(\\i,0)(\\i,1)'); + }); + + it('psplot honors plotpoints', () => { + const ctx = makeContext(); + const m = match(Expressions.psplot, '\\psplot[algebraic,plotpoints=11]{0}{1}{x*x}'); + const data = Functions.psplot.call(ctx, m); + // 11 samples → 22 numbers + expect(data.data).toHaveLength(22); + }); + + it('psplot evaluates ^ power (PSTricks, not JS XOR)', () => { + const ctx = makeContext(); + const m = match(Expressions.psplot, '\\psplot[algebraic,plotpoints=3]{0}{2}{x^2}'); + const data = Functions.psplot.call(ctx, m); + // samples at x=0,1,2 → y = 0,1,4 → pixel Y values + const ys = [data.data[1], data.data[3], data.data[5]]; + // Y(v) = (5 - v) * 50 → Y(0)=250, Y(1)=200, Y(4)=50 + expect(ys[0]).toBe(250); + expect(ys[1]).toBe(200); + expect(ys[2]).toBe(50); + }); + + it('psplot evaluates implicit multiplication', () => { + const ctx = makeContext(); + const m = match(Expressions.psplot, '\\psplot[algebraic,plotpoints=2]{1}{2}{2x}'); + const data = Functions.psplot.call(ctx, m); + const ys = [data.data[1], data.data[3]]; + // Y(2)=150, Y(4)=50 + expect(ys[0]).toBe(150); + expect(ys[1]).toBe(50); + }); + + it('psplot evaluates user variables in the expression', () => { + const ctx = makeContext(); + ctx.variables = { n: 4 }; + const m = match(Expressions.psplot, '\\psplot[algebraic,plotpoints=2]{0}{1}{n*x}'); + const data = Functions.psplot.call(ctx, m); + const ys = [data.data[1], data.data[3]]; + expect(ys[0]).toBe(250); // Y(0) + expect(ys[1]).toBe(50); // Y(4) + }); + + it('uservariable evaluates its initial expression', () => { + const ctx = makeContext(); + const m = match(Expressions.uservariable, '\\uservariable{alpha}(1,1){x^2}'); + const data = Functions.uservariable.call(ctx, m); + // x=1 in user coords → value = 1 + expect(data.value).toBeCloseTo(1); + }); + + it('userline evaluates head/tail expressions against x,y', () => { + const ctx = makeContext(); + const m = match( + Expressions.userline, + '\\userline{->}(0,0)(2,2){-x}{-y}' + ); + const data = Functions.userline.call(ctx, m); + // Xinv(0)= -5, so -x = 5 → X(5)=500; Yinv(0)=5 → -y=-5 → Y(-5)=500 + expect(data.userx([0, 0])).toBe(500); + expect(data.usery([0, 0])).toBe(500); + }); + + it('userline evaluates ternary conditional expressions', () => { + const ctx = makeContext(); + const m = match( + Expressions.userline, + '\\userline{->}(0,0)(2,2){(x>0) ? 3 * cos( atan(-y/x) ) : -3 * cos( atan(-y/x) ) }{0}' + ); + const data = Functions.userline.call(ctx, m); + // at coords (0,0): userx → x=0,y=0 → (x>0)=false → -3*cos(atan(0/0))... + // atan(0/0) is NaN in JS → cos(NaN)=NaN → X(NaN) → 0 guard + const result = data.userx([0, 0]); + expect(Number.isFinite(result)).toBe(true); + }); + + it('slider seeds variables and registers itself', () => { + const ctx = makeContext(); + const m = match(Expressions.slider, '\\slider{1}{8}{n}{$N$}{4}'); + const data = Functions.slider.call(ctx, m); + expect(data.variable).toBe('n'); + expect(data.value).toBe(4); + expect(ctx.variables.n).toBe(4); + expect(ctx.sliders).toHaveLength(1); + }); +}); diff --git a/packages/pstricks/tsconfig.json b/packages/pstricks/tsconfig.json index 1a9d5696..2b870caa 100644 --- a/packages/pstricks/tsconfig.json +++ b/packages/pstricks/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "dist", - "rootDir": "src/" + "rootDir": "src/", + "isolatedModules": true }, "include": ["src/**/*.ts"], "exclude": ["dist", "node_modules", "**/*.spec.*", "**/*.test.*"] diff --git a/packages/react/jest.config.ts b/packages/react/jest.config.ts index 3dcb8a43..c0b93fc3 100644 --- a/packages/react/jest.config.ts +++ b/packages/react/jest.config.ts @@ -3,7 +3,7 @@ import type { Config } from 'jest'; const config: Config = { preset: 'ts-jest', testEnvironment: 'jsdom', - roots: ['/src', '/test'], + roots: [''], testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], transform: { '^.+\\.(ts|tsx)$': 'ts-jest', diff --git a/packages/react/src/index.tsx b/packages/react/src/index.tsx index d3033c7e..8efb22c8 100644 --- a/packages/react/src/index.tsx +++ b/packages/react/src/index.tsx @@ -13,7 +13,7 @@ import slider from './components/slider'; import { getMathJax, loadMathJax } from 'mathjaxjs'; import { MathJaxProvider } from 'mathjaxjs-react'; -const ELEMENTS = { nicebox, enumerate, verbatim, math, macros, pspicture, slider }; +const ELEMENTS = { nicebox, enumerate, itemize: enumerate, description: enumerate, verbatim, math, macros, pspicture, slider }; export { nicebox, enumerate, verbatim, math, macros, pspicture, slider, MathJaxProvider }; diff --git a/packages/settings/jest.config.ts b/packages/settings/jest.config.ts index c8fde8c9..567bf402 100644 --- a/packages/settings/jest.config.ts +++ b/packages/settings/jest.config.ts @@ -3,11 +3,15 @@ import type { Config } from 'jest'; const config: Config = { preset: 'ts-jest', testEnvironment: 'node', - roots: ['/src', '/test'], + roots: [''], testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], transform: { '^.+\\.ts$': 'ts-jest', }, + // Run against TypeScript sources (no build needed). + moduleNameMapper: { + '^@latex2js/utils$': '/../../packages/utils/src/index.ts', + }, }; export default config; diff --git a/packages/settings/test/settings.test.ts b/packages/settings/test/settings.test.ts new file mode 100644 index 00000000..90890c27 --- /dev/null +++ b/packages/settings/test/settings.test.ts @@ -0,0 +1,41 @@ +import Settings from '../src/index'; + +describe('@latex2js/settings', () => { + it('linecolor/linestyle/fillstyle setters', () => { + const o: any = {}; + Settings.Functions.linecolor(o, 'red'); + Settings.Functions.linestyle(o, 'dashed'); + Settings.Functions.fillstyle(o, 'solid'); + Settings.Functions.fillcolor(o, 'gray!40'); + expect(o).toEqual({ + linecolor: 'red', + linestyle: 'dashed', + fillstyle: 'solid', + fillcolor: 'gray!40', + }); + }); + + it('unit sets xunit/yunit/runit together', () => { + const o: any = {}; + Settings.Functions.unit(o, '1cm'); + expect(o.unit).toBe(50); + expect(o.runit).toBe(50); + expect(o.xunit).toBe(50); + expect(o.yunit).toBe(50); + }); + + it('xunit/yunit set independently', () => { + const o: any = {}; + Settings.Functions.xunit(o, '1in'); + Settings.Functions.yunit(o, '0.5cm'); + expect(o.xunit).toBe(20); + expect(o.yunit).toBe(25); + }); + + it('expressions match the setting keys', () => { + expect(Settings.Expressions.linecolor.test('linecolor')).toBe(true); + expect(Settings.Expressions.xunit.test('xunit')).toBe(true); + expect(Settings.Expressions.unit.test('unit=2cm')).toBe(true); + expect(Settings.Expressions.linecolor.test('fillcolor')).toBe(false); + }); +}); diff --git a/packages/settings/tsconfig.json b/packages/settings/tsconfig.json index 1a9d5696..2b870caa 100644 --- a/packages/settings/tsconfig.json +++ b/packages/settings/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "dist", - "rootDir": "src/" + "rootDir": "src/", + "isolatedModules": true }, "include": ["src/**/*.ts"], "exclude": ["dist", "node_modules", "**/*.spec.*", "**/*.test.*"] diff --git a/packages/utils/jest.config.ts b/packages/utils/jest.config.ts new file mode 100644 index 00000000..d658b627 --- /dev/null +++ b/packages/utils/jest.config.ts @@ -0,0 +1,13 @@ +import type { Config } from 'jest'; + +const config: Config = { + preset: 'ts-jest', + testEnvironment: 'node', + roots: [''], + testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'], + transform: { + '^.+\\.ts$': 'ts-jest', + }, +}; + +export default config; diff --git a/packages/utils/src/expression.ts b/packages/utils/src/expression.ts new file mode 100644 index 00000000..525741b8 --- /dev/null +++ b/packages/utils/src/expression.ts @@ -0,0 +1,377 @@ +/** + * Algebraic expression parser + evaluator for PSTricks-style math. + * + * PSTricks `algebraic` expressions are NOT JavaScript: they use `^` for + * power, allow implicit multiplication (`2x`, `2(x+1)`, `2sin(x)`), and rely + * on bare math function names (`cos(x)`). This module parses an expression + * once into an AST and compiles it to a JavaScript closure that can be + * evaluated cheaply many times with a variable scope — exactly the + * compile-once / evaluate-many pattern the interactive plot and userline + * paths need. + * + * Supported syntax: + * numbers, identifiers (variables), arithmetic + - * / ^, + * unary minus/plus, implicit multiplication, parentheses, + * function calls (cos, sin, tan, atan, atan2, pow, sqrt, abs, exp, ln, + * log, floor, ceil, round, min, max, ...), comparisons (< > <= >= == !=), + * and ternary conditionals (cond ? a : b). + */ + +export class ExpressionError extends Error { + position: number; + line: number; + column: number; + + constructor(message: string, position: number) { + // position is a 0-based offset; compute 1-based line/column lazily + super(message); + this.name = 'ExpressionError'; + this.position = position; + this.line = 0; + this.column = 0; + } +} + +export interface CompiledExpression { + /** Evaluate with a variable scope. */ + evaluate(scope?: Record): number; + /** The generated JavaScript body (for debugging). */ + toJS(): string; + /** Identifiers referenced (excluding math functions/constants). */ + variables(): string[]; + source: string; +} + +// --------------------------------------------------------------------------- +// Tokenizer +// --------------------------------------------------------------------------- + +type TokenType = 'number' | 'ident' | 'op' | 'paren' | 'eof'; + +interface Token { + type: TokenType; + value: string; + pos: number; +} + +const OPS = ['<=', '>=', '==', '!=', '<', '>', '?', ':', '+', '-', '*', '/', '^', ',']; +const PARENS = new Set(['(', ')']); + +function tokenize(source: string): Token[] { + const tokens: Token[] = []; + let i = 0; + const n = source.length; + + const numberRe = /^\d*\.?\d+(?:[eE][+-]?\d+)?/; + const identRe = /^[a-zA-Z_][a-zA-Z0-9_]*/; + + while (i < n) { + const ch = source[i]; + if (/\s/.test(ch)) { + i++; + continue; + } + // Unicode pi + if (ch === 'π') { + tokens.push({ type: 'ident', value: 'π', pos: i }); + i++; + continue; + } + if (ch === '(' || ch === ')') { + tokens.push({ type: 'paren', value: ch, pos: i }); + i++; + continue; + } + const num = source.slice(i).match(numberRe); + if (num) { + tokens.push({ type: 'number', value: num[0], pos: i }); + i += num[0].length; + continue; + } + const ident = source.slice(i).match(identRe); + if (ident) { + tokens.push({ type: 'ident', value: ident[0], pos: i }); + i += ident[0].length; + continue; + } + const op = OPS.find((o) => source.startsWith(o, i)); + if (op) { + tokens.push({ type: op === '(' || op === ')' ? 'paren' : 'op', value: op, pos: i }); + i += op.length; + continue; + } + throw new ExpressionError(`unexpected character '${ch}'`, i); + } + tokens.push({ type: 'eof', value: '', pos: n }); + return tokens; +} + +// --------------------------------------------------------------------------- +// Parser (recursive descent with precedence climbing) +// --------------------------------------------------------------------------- + +interface Node { + type: string; + [key: string]: any; +} + +class Parser { + private tokens: Token[]; + private index = 0; + + constructor(private source: string) { + this.tokens = tokenize(source); + if (this.tokens.length <= 1) { + throw new ExpressionError('empty expression', 0); + } + } + + private peek(): Token { + return this.tokens[this.index]; + } + + private next(): Token { + return this.tokens[this.index++]; + } + + private expect(value: string): Token { + const t = this.peek(); + if (t.value !== value) { + throw new ExpressionError(`expected '${value}' but found '${t.value || 'end of input'}'`, t.pos); + } + return this.next(); + } + + parse(): Node { + const node = this.parseTernary(); + const t = this.peek(); + if (t.type !== 'eof') { + throw new ExpressionError(`unexpected '${t.value}'`, t.pos); + } + return node; + } + + private parseTernary(): Node { + const cond = this.parseComparison(); + if (this.peek().value === '?') { + this.next(); + const then = this.parseTernary(); + this.expect(':'); + const els = this.parseTernary(); + return { type: 'ternary', cond, then, els }; + } + return cond; + } + + private parseComparison(): Node { + let left = this.parseAdditive(); + for (;;) { + const op = this.peek().value; + if (op === '<' || op === '>' || op === '<=' || op === '>=' || op === '==' || op === '!=') { + this.next(); + const right = this.parseAdditive(); + left = { type: 'binary', op, left, right }; + } else { + return left; + } + } + } + + private parseAdditive(): Node { + let left = this.parseMultiplicative(); + for (;;) { + const op = this.peek().value; + if (op === '+' || op === '-') { + this.next(); + const right = this.parseMultiplicative(); + left = { type: 'binary', op, left, right }; + } else { + return left; + } + } + } + + private parseMultiplicative(): Node { + let left = this.parseUnary(); + for (;;) { + const op = this.peek().value; + if (op === '*' || op === '/') { + this.next(); + const right = this.parseUnary(); + left = { type: 'binary', op, left, right }; + } else if (this.isImplicitStart(this.peek())) { + // implicit multiplication: 2x, 2(x+1), (x+1)(x+2), 2sin(x) + const right = this.parseUnary(); + left = { type: 'binary', op: '*', left, right }; + } else { + return left; + } + } + } + + private parseUnary(): Node { + const op = this.peek().value; + if (op === '-' || op === '+') { + this.next(); + return { type: 'unary', op, operand: this.parseUnary() }; + } + return this.parsePower(); + } + + private parsePower(): Node { + const left = this.parsePrimary(); + if (this.peek().value === '^') { + this.next(); + const right = this.parseUnary(); // right-associative, binds tighter on the right + return { type: 'binary', op: '^', left, right }; + } + return left; + } + + private parsePrimary(): Node { + const t = this.peek(); + if (t.type === 'number') { + this.next(); + return { type: 'number', value: t.value }; + } + if (t.type === 'ident') { + this.next(); + // a known math function followed by '(' is a function call + if (this.peek().value === '(' && MATH_FUNCTIONS.hasOwnProperty(t.value)) { + this.next(); // consume '(' + const args: Node[] = []; + if (this.peek().value !== ')') { + args.push(this.parseTernary()); + while (this.peek().value === ',') { + this.next(); + args.push(this.parseTernary()); + } + } + this.expect(')'); + return { type: 'call', name: t.value, args }; + } + return { type: 'var', name: t.value }; + } + if (t.value === '(') { + this.next(); + const node = this.parseTernary(); + this.expect(')'); + return node; + } + throw new ExpressionError( + `unexpected '${t.value || 'end of input'}' in expression`, + t.pos + ); + } + + /** A token that can start an implicit multiplication operand. */ + private isImplicitStart(t: Token): boolean { + return t.type === 'number' || t.type === 'ident' || t.value === '('; + } +} + +// --------------------------------------------------------------------------- +// Compile AST → JS closure +// --------------------------------------------------------------------------- + +export const MATH_FUNCTIONS: Record = { + cos: 'Math.cos', + sin: 'Math.sin', + tan: 'Math.tan', + atan: 'Math.atan', + atan2: 'Math.atan2', + asin: 'Math.asin', + acos: 'Math.acos', + exp: 'Math.exp', + ln: 'Math.log', + log: 'Math.log', + log10: 'Math.log10', + sqrt: 'Math.sqrt', + cbrt: 'Math.cbrt', + abs: 'Math.abs', + sign: 'Math.sign', + floor: 'Math.floor', + ceil: 'Math.ceil', + round: 'Math.round', + pow: 'Math.pow', + min: 'Math.min', + max: 'Math.max', + sinh: 'Math.sinh', + cosh: 'Math.cosh', + tanh: 'Math.tanh', +}; + +export const MATH_CONSTANTS: Record = { + pi: 'Math.PI', + π: 'Math.PI', + PI: 'Math.PI', + E: 'Math.E', +}; + +function compileNode(node: Node, variableNames: Set): string { + switch (node.type) { + case 'number': + return node.value; + case 'var': { + if (MATH_CONSTANTS.hasOwnProperty(node.name)) { + return MATH_CONSTANTS[node.name]; + } + variableNames.add(node.name); + return 'v.' + node.name; + } + case 'call': { + const target = MATH_FUNCTIONS.hasOwnProperty(node.name) + ? MATH_FUNCTIONS[node.name] + : '(v.' + node.name + ')'; + return target + '(' + node.args.map((a: Node) => compileNode(a, variableNames)).join(',') + ')'; + } + case 'unary': + return '(' + node.op + compileNode(node.operand, variableNames) + ')'; + case 'binary': { + const op = node.op === '^' ? '**' : node.op; + return '(' + compileNode(node.left, variableNames) + op + compileNode(node.right, variableNames) + ')'; + } + case 'ternary': + return ( + '(' + + compileNode(node.cond, variableNames) + + '?' + + compileNode(node.then, variableNames) + + ':' + + compileNode(node.els, variableNames) + + ')' + ); + default: + throw new Error('unknown node type ' + node.type); + } +} + +/** + * Parse an algebraic expression and compile it to an evaluable closure. + * Throws ExpressionError with a character position on invalid syntax. + */ +export function parseExpression(source: string): CompiledExpression { + const trimmed = source.trim(); + if (!trimmed) { + throw new ExpressionError('empty expression', 0); + } + const parser = new Parser(trimmed); + const ast = parser.parse(); + const variableNames = new Set(); + const js = compileNode(ast, variableNames); + + let fn: (v: any) => number; + try { + // eslint-disable-next-line no-new-func + fn = new Function('v', 'return (' + js + ');') as (v: any) => number; + } catch (err) { + throw new ExpressionError('could not compile expression: ' + (err as Error).message, 0); + } + + return { + source: trimmed, + toJS: () => js, + variables: () => Array.from(variableNames), + evaluate: (scope?: Record) => fn(scope || {}), + }; +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index cd99f73e..2a2bdd1c 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,3 +1,6 @@ +import { parseExpression } from './expression'; +import type { CompiledExpression } from './expression'; + export const simplerepl = function (regex: RegExp, replace: string) { return function (_m: any, contents: string) { return contents.replace(regex, replace); @@ -110,24 +113,31 @@ export const evaluate = function (this: any, exp: string): number { this.variables = this.variables || {}; - const mathKeys = Object.keys(Math) as (keyof Math)[]; - const varKeys = Object.keys(this.variables); - const allKeys = [...mathKeys, ...varKeys]; - const allValues = [ - ...mathKeys.map(k => (Math[k] as any)), - ...varKeys.map(k => this.variables[k]) - ]; - try { - // @ts-ignore - const fn = new Function(...allKeys, `return (${exp});`); - return fn(...allValues); + return getCompiled(exp).evaluate(this.variables); } catch (e) { - console.warn('Evaluation error:', e); + console.warn('Evaluation error:', (e as Error).message); return NaN; } }; +// Small bounded cache so repeated identical expressions (e.g. plot bounds, +// slider-driven re-evaluation) skip re-parsing entirely. +const expressionCache = new Map(); +const EXPRESSION_CACHE_MAX = 500; + +function getCompiled(exp: string): CompiledExpression { + let compiled = expressionCache.get(exp); + if (!compiled) { + compiled = parseExpression(exp); + if (expressionCache.size >= EXPRESSION_CACHE_MAX) { + expressionCache.clear(); + } + expressionCache.set(exp, compiled); + } + return compiled; +} + export const X = function (this: any, v: number | string) { // Enhanced validation for coordinate transformation @@ -216,3 +226,10 @@ export const arrowType = parseArrows; export const dotType = parseArrows; export { SVGSelection, select } from './svg-utils'; +export { + parseExpression, + ExpressionError, + MATH_FUNCTIONS, + MATH_CONSTANTS, +} from './expression'; +export type { CompiledExpression } from './expression'; diff --git a/packages/utils/test/expression.test.ts b/packages/utils/test/expression.test.ts new file mode 100644 index 00000000..77c86d6c --- /dev/null +++ b/packages/utils/test/expression.test.ts @@ -0,0 +1,109 @@ +import { parseExpression, ExpressionError } from '../src/index'; + +function ev(source: string, scope?: Record): number { + return parseExpression(source).evaluate(scope); +} + +describe('parseExpression', () => { + it('evaluates arithmetic with precedence', () => { + expect(ev('2+3*4')).toBe(14); + expect(ev('(2+3)*4')).toBe(20); + expect(ev('10/4')).toBe(2.5); + expect(ev('7-2-1')).toBe(4); + }); + + it('supports power with ^ (right associative)', () => { + expect(ev('2^3')).toBe(8); + expect(ev('2^3^2')).toBe(512); // 2^(3^2) + expect(ev('x^2', { x: 3 })).toBe(9); + expect(ev('2^-2')).toBeCloseTo(0.25); + expect(ev('-2^2')).toBe(-4); // -(2^2), math convention + }); + + it('supports implicit multiplication', () => { + expect(ev('2x', { x: 3 })).toBe(6); + expect(ev('2(x+1)', { x: 3 })).toBe(8); + expect(ev('(x+1)(x+2)', { x: 3 })).toBe(20); + expect(ev('2sin(x)', { x: 0 })).toBe(0); + expect(ev('2x^2', { x: 3 })).toBe(18); + expect(ev('3cos(0)')).toBe(3); + }); + + it('supports unary minus and plus', () => { + expect(ev('-x', { x: 5 })).toBe(-5); + expect(ev('+3')).toBe(3); + expect(ev('-3*-2')).toBe(6); + expect(ev('-(x+1)', { x: 2 })).toBe(-3); + }); + + it('supports comparisons and ternary conditionals', () => { + expect(ev('x > 0 ? 3 : -3', { x: 2 })).toBe(3); + expect(ev('x > 0 ? 3 : -3', { x: -2 })).toBe(-3); + expect(ev('(x>0) ? 3 * cos( atan(-y/x) ) : -3 * cos( atan(-y/x) )', { x: 2, y: 2 })).toBeCloseTo( + 2.1213, + 3 + ); + expect(ev('x <= 1 ? 1 : 0', { x: 0.5 })).toBe(1); + }); + + it('supports math functions', () => { + expect(ev('cos(0)')).toBe(1); + expect(ev('sin(pi/2)')).toBeCloseTo(1); + expect(ev('pow(2,3)')).toBe(8); + expect(ev('sqrt(16)')).toBe(4); + expect(ev('abs(-5)')).toBe(5); + expect(ev('atan2(1,1)')).toBeCloseTo(Math.PI / 4); + expect(ev('min(3,1,2)')).toBe(1); + expect(ev('max(3,1,2)')).toBe(3); + expect(ev('ln(E)')).toBe(1); + expect(ev('floor(2.7)')).toBe(2); + expect(ev('round(2.5)')).toBe(3); + }); + + it('supports math constants', () => { + expect(ev('pi')).toBeCloseTo(Math.PI); + expect(ev('π')).toBeCloseTo(Math.PI); + expect(ev('2*E')).toBeCloseTo(2 * Math.E); + }); + + it('reads user variables from the scope', () => { + expect(ev('n*x + a', { n: 4, x: 2, a: 1 })).toBe(9); + expect(ev('alpha * sin(theta*x)/(x*phi)', { alpha: 2, theta: 3, x: 1, phi: 4 })).toBeCloseTo( + (2 * Math.sin(3)) / 4 + ); + }); + + it('lists referenced variables', () => { + const compiled = parseExpression('2x + n*y'); + expect(compiled.variables().sort()).toEqual(['n', 'x', 'y']); + }); + + it('re-evaluates cheaply with a changing scope', () => { + const compiled = parseExpression('a*sin(n*x)/(n*x)'); + const scope: any = { a: 2, n: 4, x: 0.5 }; + const first = compiled.evaluate(scope); + scope.x = 1; + const second = compiled.evaluate(scope); + expect(first).not.toBe(second); + expect(Number.isFinite(first) && Number.isFinite(second)).toBe(true); + }); + + it('throws ExpressionError with position on bad input', () => { + expect(() => parseExpression('2 +')).toThrow(ExpressionError); + expect(() => parseExpression('2 +* 3')).toThrow(ExpressionError); + expect(() => parseExpression('')).toThrow(ExpressionError); + expect(() => parseExpression('(x+1')).toThrow(ExpressionError); + try { + parseExpression('2 +'); + fail('should have thrown'); + } catch (e) { + expect((e as ExpressionError).position).toBeGreaterThanOrEqual(0); + } + }); + + it('compiles to a debuggable JS string', () => { + const js = parseExpression('x^2 + 2x').toJS(); + expect(js).toContain('**'); + expect(js).toContain('v.x'); + }); +}); diff --git a/packages/utils/test/utils.test.ts b/packages/utils/test/utils.test.ts new file mode 100644 index 00000000..12cb613d --- /dev/null +++ b/packages/utils/test/utils.test.ts @@ -0,0 +1,75 @@ +import { + convertUnits, + parseOptions, + parseArrows, + evaluate, + X, + Y, + Xinv, + Yinv, + simplerepl, + matchrepl, +} from '../src/index'; + +describe('utils', () => { + it('convertUnits maps cm/in to pixels', () => { + expect(convertUnits('1cm')).toBe(50); + expect(convertUnits('2cm')).toBe(100); + expect(convertUnits('1in')).toBe(20); + }); + + it('parseOptions converts [a=1, b=2] to an object', () => { + expect(parseOptions('[showorigin=false, labels=none, Dx=3.14]')).toEqual({ + showorigin: 'false', + labels: 'none', + Dx: '3.14', + }); + }); + + it('parseArrows detects arrowheads and dots', () => { + expect(parseArrows('{->}')).toEqual({ arrows: [0, 1], dots: [0, 0] }); + expect(parseArrows('{<-}')).toEqual({ arrows: [1, 0], dots: [0, 0] }); + expect(parseArrows('{<->}')).toEqual({ arrows: [1, 1], dots: [0, 0] }); + expect(parseArrows('{*-*}')).toEqual({ arrows: [0, 0], dots: [1, 1] }); + expect(parseArrows('{}')).toEqual({ arrows: [0, 0], dots: [0, 0] }); + }); + + it('X/Y transforms map user coordinates to pixels', () => { + const ctx: any = { x0: -5, y0: -5, x1: 5, y1: 5, w: 10, h: 10, xunit: 50, yunit: 50 }; + // X(v) = (w - (x1 - v)) * xunit ; Y(v) = (y1 - v) * yunit + expect(X.call(ctx, 0)).toBe(250); + expect(X.call(ctx, -5)).toBe(0); + expect(X.call(ctx, 5)).toBe(500); + expect(Y.call(ctx, 0)).toBe(250); + expect(Y.call(ctx, -5)).toBe(500); + expect(Y.call(ctx, 5)).toBe(0); + // inverse transforms round-trip + expect(Xinv.call(ctx, X.call(ctx, 2))).toBeCloseTo(2); + expect(Yinv.call(ctx, Y.call(ctx, 2))).toBeCloseTo(2); + }); + + it('X/Y guard against invalid input', () => { + const ctx: any = { x0: -5, y0: -5, x1: 5, y1: 5, w: 10, h: 10, xunit: 50, yunit: 50 }; + expect(X.call(ctx, 'not-a-number')).toBe(0); + expect(Y.call(ctx, NaN)).toBe(0); + }); + + it('evaluate evaluates math expressions with Math and variables', () => { + const ctx: any = { variables: { n: 4 } }; + expect(evaluate.call(ctx, '2+2')).toBe(4); + expect(evaluate.call(ctx, 'n*2')).toBe(8); + expect(evaluate.call(ctx, 'cos(0)')).toBe(1); + expect(evaluate.call(ctx, '7')).toBe(7); + }); + + it('simplerepl replaces a regex with a fixed string', () => { + const fn = simplerepl(/---/g, '—'); + expect(fn([], 'a---b---c')).toBe('a—b—c'); + }); + + it('matchrepl replaces matches via a callback', () => { + const fn = matchrepl(/\\emph\{([^}]*)\}/, (m: RegExpMatchArray) => '' + m[1] + ''); + const matches = '\\emph{hi} and \\emph{bye}'.match(/\\emph\{[^}]*\}/g) || []; + expect(fn(matches, '\\emph{hi} and \\emph{bye}')).toBe('hi and bye'); + }); +}); diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index 1a9d5696..2b870caa 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "dist", - "rootDir": "src/" + "rootDir": "src/", + "isolatedModules": true }, "include": ["src/**/*.ts"], "exclude": ["dist", "node_modules", "**/*.spec.*", "**/*.test.*"] diff --git a/packages/vue/src/latex.vue b/packages/vue/src/latex.vue index b7c14055..4cb06a3c 100644 --- a/packages/vue/src/latex.vue +++ b/packages/vue/src/latex.vue @@ -31,6 +31,8 @@ export default { pspicture, nicebox, enumerate, + itemize: enumerate, + description: enumerate, verbatim, slider, math, diff --git a/playground/e2e/examples.spec.ts b/playground/e2e/examples.spec.ts new file mode 100644 index 00000000..ae091795 --- /dev/null +++ b/playground/e2e/examples.spec.ts @@ -0,0 +1,70 @@ +import { test, expect } from '@playwright/test'; +import * as fs from 'fs'; +import * as path from 'path'; +import { fileURLToPath } from 'node:url'; + +/** + * Renders every example in the golden corpus (the latex2js.com examples plus + * the new feature examples) in a real browser and: + * 1. asserts the page renders without JS errors, + * 2. asserts each pspicture produced an SVG, + * 3. saves a PNG per example to playground/renders/ so the renderings can + * be inspected visually. + */ + +const here = fileURLToPath(new URL('.', import.meta.url)); +const corpusDir = path.join(here, '../../packages/latex2js/test/corpus'); +const outDir = path.join(here, '../renders'); + +const files = fs.readdirSync(corpusDir).filter((f) => f.endsWith('.tex')); + +test.beforeAll(() => { + fs.mkdirSync(outDir, { recursive: true }); +}); + +for (const file of files) { + test(`renders ${file}`, async ({ page }) => { + const tex = fs.readFileSync(path.join(corpusDir, file), 'utf8'); + const encoded = Buffer.from(tex, 'utf8').toString('base64url'); + + const errors: string[] = []; + page.on('console', (msg) => { + if (msg.type() === 'error') errors.push(msg.text()); + }); + page.on('pageerror', (err) => errors.push(String(err))); + + // Use the URL hash (not ?tex=): large examples would exceed the server's + // request-header size limit, and the hash is never sent to the server. + await page.goto(`/render.html#${encoded}`); + await page.waitForFunction(() => (window as any).document.body.dataset.ready === 'true', null, { + timeout: 30_000, + }); + + // let MathJax finish positioning before screenshotting + await page.waitForTimeout(400); + + const output = page.locator('#output'); + if (tex.includes('\\begin{pspicture}')) { + const svgCount = await output.locator('svg').count(); + expect(svgCount).toBeGreaterThan(0); + } else { + // pure math/text examples have no SVG — assert they rendered content + const hasContent = + (await output.locator('mjx-container').count()) > 0 || + ((await output.innerText()).trim().length > 0); + expect(hasContent).toBe(true); + } + + await output.screenshot({ path: path.join(outDir, file.replace(/\.tex$/, '.png')) }); + + // ignore resource-load noise (fonts/CDN) — real JS errors fail the test + const realErrors = errors.filter( + (e) => + !e.includes('Failed to load resource') && + !e.includes('net::') && + !e.includes('MathJax') && + !e.includes('font') + ); + expect(realErrors).toEqual([]); + }); +} diff --git a/playground/e2e/interactive.spec.ts b/playground/e2e/interactive.spec.ts new file mode 100644 index 00000000..fa95b267 --- /dev/null +++ b/playground/e2e/interactive.spec.ts @@ -0,0 +1,77 @@ +import { test, expect } from '@playwright/test'; + +/** + * Interactive browser tests against the live playground UI: hash-encoded LaTeX + * in, rendered output out, then real user interactions (slider drag, mouse + * movement on interactive graphics, MathJax typesetting). + */ + +const SLIDER_EXAMPLE = String.raw`\psset{unit=1cm} +\begin{pspicture}(-3.5,-1)(3.75,3.5) +\slider{1}{8}{n}{$N$}{4} +\psplot[algebraic,linewidth=1.5pt,plotpoints=1000]{-3.14}{3.14}{cos(n*x/2)+1.3} +\end{pspicture}`; + +const DRAGGABLE_EXAMPLE = String.raw`\begin{pspicture}(-2,-2)(2,2) +\psframe(-2,-2)(2,2) +\userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} +\userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} +\end{pspicture}`; + +const MATH_EXAMPLE = String.raw`The quadratic formula: $$x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$$ + +\begin{theorem} +If $u, v \in V$ then $|\langle u, v \rangle| \le \|u\| \|v\|$. +\end{theorem}`; + +const encode = (tex: string) => Buffer.from(tex, 'utf8').toString('base64'); + +test('renders the default playground example with an SVG', async ({ page }) => { + await page.goto('/'); + const svg = page.locator('.pspicture svg').first(); + await expect(svg).toBeVisible(); +}); + +test('slider changes re-render the plot', async ({ page }) => { + await page.goto(`/#${encode(SLIDER_EXAMPLE)}`); + const slider = page.locator('input[type="range"]').first(); + await expect(slider).toBeVisible(); + + const plot = page.locator('svg path.psplot').first(); + await expect(plot).toBeVisible(); + const before = await plot.getAttribute('d'); + + await slider.fill('8'); + await page.waitForTimeout(300); + + const after = await plot.getAttribute('d'); + expect(after).not.toBe(before); + // still exactly one plot path after the re-render + expect(await page.locator('svg path.psplot').count()).toBe(1); +}); + +test('moving the mouse drags interactive userline graphics', async ({ page }) => { + await page.goto(`/#${encode(DRAGGABLE_EXAMPLE)}`); + const svg = page.locator('.pspicture svg').first(); + await expect(svg).toBeVisible(); + + const userline = page.locator('svg path.userline').first(); + await expect(userline).toBeVisible(); + const before = await userline.getAttribute('d'); + + const box = (await svg.boundingBox())!; + await page.mouse.move(box.x + box.width * 0.75, box.y + box.height * 0.3); + await page.waitForTimeout(200); + + const after = await userline.getAttribute('d'); + expect(after).not.toBe(before); +}); + +test('MathJax typesets inline and display math', async ({ page }) => { + await page.goto(`/#${encode(MATH_EXAMPLE)}`); + // MathJax v3 output lives in elements + const mjx = page.locator('mjx-container').first(); + await expect(mjx).toBeVisible(); + // theorem header was transformed by the headers pass + await expect(page.getByText('Theorem', { exact: true })).toBeVisible(); +}); diff --git a/playground/index.html b/playground/index.html new file mode 100644 index 00000000..5e1b2b2a --- /dev/null +++ b/playground/index.html @@ -0,0 +1,27 @@ + + + + + + LaTeX2JS Playground + + +
    +

    LaTeX2JS playground

    + +
    + +
    +
    + +
    +
    +
    +
    +
    +
    + + + + diff --git a/playground/package.json b/playground/package.json new file mode 100644 index 00000000..00d89d43 --- /dev/null +++ b/playground/package.json @@ -0,0 +1,21 @@ +{ + "name": "@latex2js/playground", + "version": "0.0.0", + "private": true, + "type": "module", + "description": "Live editing playground for LaTeX2JS — dev loop and future website sandbox", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "e2e": "playwright test", + "e2e:gallery": "playwright test e2e/examples.spec.ts", + "e2e:interactive": "playwright test e2e/interactive.spec.ts", + "e2e:ui": "playwright test --ui" + }, + "devDependencies": { + "@playwright/test": "^1.62.1", + "typescript": "^5.8.0", + "vite": "^7.0.0" + } +} diff --git a/playground/playwright.config.ts b/playground/playwright.config.ts new file mode 100644 index 00000000..d9d77c4e --- /dev/null +++ b/playground/playwright.config.ts @@ -0,0 +1,40 @@ +import { defineConfig, devices } from '@playwright/test'; +import { fileURLToPath } from 'node:url'; + +// Browsers are installed into the workspace (playground/.browsers) instead of +// the user cache dir; make sure workers resolve them there. +const here = fileURLToPath(new URL('.', import.meta.url)); +process.env.PLAYWRIGHT_BROWSERS_PATH ||= `${here}.browsers`; + +/** + * Browser-level tests for LaTeX2JS. + * + * - examples.spec.ts renders every corpus example in a real browser, asserts + * clean rendering, and saves a PNG gallery to playground/renders/. + * - interactive.spec.ts drives the live playground UI (sliders, drag). + * + * Run with: pnpm e2e (starts its own vite dev server) + */ +export default defineConfig({ + testDir: './e2e', + timeout: 60_000, + fullyParallel: true, + reporter: [['list']], + use: { + baseURL: 'http://localhost:5173', + viewport: { width: 1400, height: 1000 }, + trace: 'on-first-retry', + }, + webServer: { + command: 'pnpm exec vite --port 5173 --strictPort', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI, + timeout: 60_000, + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}); diff --git a/playground/render.html b/playground/render.html new file mode 100644 index 00000000..0bcface5 --- /dev/null +++ b/playground/render.html @@ -0,0 +1,16 @@ + + + + + + LaTeX2JS Render + + + +
    + + + diff --git a/playground/src/main.ts b/playground/src/main.ts new file mode 100644 index 00000000..8493e6ef --- /dev/null +++ b/playground/src/main.ts @@ -0,0 +1,228 @@ +import './style.css'; +import '../../packages/css/latex2js.css'; + +import LaTeX2JS from 'latex2js'; +import render from 'latex2html5'; + +const editor = document.getElementById('editor') as HTMLTextAreaElement; +const output = document.getElementById('output') as HTMLDivElement; +const diagnostics = document.getElementById('diagnostics') as HTMLDivElement; +const exampleBar = document.getElementById('examples') as HTMLDivElement; + +const EXAMPLES: Array<[string, string]> = [ + [ + 'axes + vectors', + String.raw`\begin{pspicture}(-5,-5)(5,5) + +% y-axis +\rput(0.3,3.75){ $Im$ } +\psline{->}(0,-3.75)(0,3.75) + +% x-axis +\rput(3.75,0.3){ $Re$ } +\psline{->}(-3.75,0)(3.75,0) + +% the circle +\pscircle(0,0){ 3 } + +\rput(2.3,1){$e^{i\omega}-\alpha$} +\userline[linewidth=1.5 pt]{->}(1.500,0.000)(2.121,2.121) +\userline[linewidth=1.5 pt,linecolor=blue]{->}(0,0.000)(2.121,2.121){(x>0) ? 3 * cos( atan(-y/x) ) : -3 * cos( atan(-y/x) ) }{ (x>0) ? -3 * sin( atan(-y/x) ) : 3 * sin( atan(-y/x) )} + +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){x}{0}{x}{y} +\userline[linewidth=1.5 pt,linestyle=dashed](-1.500,0.000)(2.121,2.121){0}{y}{x}{y} + +\rput(-0.75,-4.25){$1+\alpha$} +\rput(2.25,-4.25){$1-\alpha$} +\psline{<->}(-3,-4)(1.5,-4) +\psline{<->}(1.5,-4)(3,-4) +\psline[linestyle=dashed](3,-4.5)(3,0) +\psline[linestyle=dashed](-3,-4.5)(-3,0) +\psline[linestyle=dashed](1.5,-4.5)(1.5,0) + +\end{pspicture}`, + ], + [ + 'slider + plot', + String.raw`\psset{unit=1cm} +\begin{pspicture}(-3.5,-1)(3.75,3.5) + +\slider{1}{8}{n}{$N$}{4} + +\psplot[algebraic,linewidth=1.5pt,plotpoints=1000]{-3.14}{3.14}{cos(n*x/2)+1.3} +\psaxes[showorigin=false,labels=none, Dx=1.62](0,0)(-3.25,0)(3.25,2.5) + +\psline[linestyle=dashed](-3.14,0.3)(3.14,0.3) +\psline[linestyle=dashed](-3.14,2.3)(3.14,2.3) +\rput(3.6,2.3){$\frac{1}{1-\alpha}$} +\rput(3.6,0.3){$\frac{1}{1+\alpha}$} + +\rput(3.14, -0.35){$\pi$} +\rput(1.62, -0.35){$\pi/2$} +\rput(-1.62, -0.35){$-\pi/2$} +\rput(-3.14, -0.35){$-\pi$} +\rput(0, -0.35){$0$} + +\end{pspicture}`, + ], + [ + 'draggable vector', + String.raw`\begin{pspicture}(-2,-2)(2,2) +\psframe(-2,-2)(2,2) +\userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} +\userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} +\userline[linewidth=2pt,linecolor=purple]{->}(0,0)(2,2){-x}{cos(y)} +\end{pspicture}`, + ], + [ + 'math + theorem', + String.raw`Let's get to the point. The core of PSTricks is graphics! + +$$\frac{\delta}{\delta u} \int_{birth}^{death} f(life) du = \mbox{your life}$$ + +\begin{theorem} +If you know \LaTeX, you can already author interactive diagrams. +\end{theorem} + +\begin{proof} +Drag the vectors above with your mouse or touch. +\end{proof} + +\begin{enumerate} +\item \emph{first} item +\item \textbf{second} item with an \href{https://latex2js.com}{inline link} +\end{enumerate}`, + ], + [ + 'new commands demo', + String.raw`\psset{unit=0.75cm} +\begin{pspicture}(0,0)(12,8) +\psgrid(0,0)(12,8) +\psdots(1,1)(2,2)(3,3) +\psellipse[fillstyle=solid,fillcolor=lightblue](4,6)(1.5,0.75) +\psbezier(6,1)(7,3)(8,3)(9,1) +\pscurve(0.5,6)(1.5,7)(2.5,6.5)(3.5,7.5) +\psccurve(5,4)(6,5)(7,4)(8,5) +\pswedge[fillstyle=solid,fillcolor=gray!40](10,5.5){1.25}{0}{90} +\pscircle*(1,7.5){0.4} +\psframe*[fillcolor=red](4,1)(6,2) +\pspolygon*(9,6.5)(10,7.5)(11,6.5) +\pscustom[fillstyle=solid,fillcolor=gray!40,linestyle=none]{ + \psline(0,0)(2,1.5) + \psline(2,1.5)(4,0) + \psline(4,0)(2,-1.5) + \psline(2,-1.5)(0,0) +} +\multido{\i=0+1}{6}{\psline[linecolor=blue](\i,4.5)(\i,5.5)} +\end{pspicture}`, + ], +]; + +function escapeHtml(s: string): string { + const div = document.createElement('div'); + div.textContent = s; + return div.innerHTML; +} + +// ---- diagnostics (parser AST breakdown) ------------------------------- + +function showDiagnostics(tex: string): void { + const lines: string[] = []; + try { + const latex = new LaTeX2JS(); + const envs = latex.parse(tex); + lines.push(`${envs.length} environment(s): ${envs.map((e) => e.type).join(', ')}`); + (latex.lastDiagnostics || []).forEach((d) => { + lines.push(`${d.severity.toUpperCase()}: ${d.message}${d.line ? ` @${d.line}:${d.column}` : ''}`); + }); + envs.forEach((e) => { + if (e.type === 'pspicture') { + const keys = Object.keys(e.plot).filter((k) => e.plot[k].length); + lines.push(` pspicture plot: ${keys.length ? keys.join(', ') : '(empty)'}`); + const ordered = e.env?.elements?.map((el: any) => el.name); + if (ordered?.length) lines.push(` pspicture order: ${ordered.join(', ')}`); + const sliders = e.env?.sliders; + if (sliders?.length) lines.push(` pspicture sliders: ${sliders.map((s: any) => `${s.variable}=${s.value}`).join(', ')}`); + } else if (e.type === 'math' && e.lines.length) { + lines.push(` math ${e.lines.length} line(s)`); + } + }); + } catch (err: any) { + lines.push(`parse error: ${err?.message ?? err}`); + } + diagnostics.innerHTML = lines.map((l) => `
    ${escapeHtml(l)}
    `).join(''); +} + +// ---- rendering --------------------------------------------------------- + +function renderAll(): void { + const tex = editor.value; + showDiagnostics(tex); + output.innerHTML = ''; + try { + render(tex, (div) => { + output.appendChild(div); + const mj = (window as any).MathJax; + if (mj && mj.typesetPromise) { + mj.typesetPromise([output]).catch((err: any) => console.error('MathJax typeset failed', err)); + } + }); + } catch (err: any) { + const pre = document.createElement('pre'); + pre.className = 'render-error'; + pre.textContent = `render error: ${err?.stack ?? err?.message ?? err}`; + output.appendChild(pre); + } +} + +// ---- debounce + shareable hash ---------------------------------------- + +let timer: number | undefined; +function scheduleRender(): void { + window.clearTimeout(timer); + timer = window.setTimeout(() => { + renderAll(); + syncHash(); + }, 250); +} + +function syncHash(): void { + const encoded = btoa(unescape(encodeURIComponent(editor.value))); + history.replaceState(null, '', `#${encoded}`); +} + +function readHash(): string | null { + const hash = location.hash.slice(1); + if (!hash) return null; + try { + return decodeURIComponent(escape(atob(hash))); + } catch { + return null; + } +} + +// ---- boot -------------------------------------------------------------- + +function loadExample(tex: string): void { + editor.value = tex; + renderAll(); + syncHash(); +} + +EXAMPLES.forEach(([label, tex]) => { + const btn = document.createElement('button'); + btn.className = 'example-btn'; + btn.textContent = label; + btn.addEventListener('click', () => loadExample(tex)); + exampleBar.appendChild(btn); +}); + +editor.addEventListener('input', scheduleRender); + +const shared = readHash(); +if (shared) { + editor.value = shared; +} else { + loadExample(EXAMPLES[0][1]); +} +renderAll(); diff --git a/playground/src/render.ts b/playground/src/render.ts new file mode 100644 index 00000000..6f8aa289 --- /dev/null +++ b/playground/src/render.ts @@ -0,0 +1,55 @@ +import '../../packages/css/latex2js.css'; + +import render from 'latex2html5'; + +/** + * Headless-friendly render page: renders LaTeX supplied via + * `?tex=` or the URL hash (`#`), typesets with MathJax, + * then marks the document ready so Playwright/screenshot tooling can wait on + * `document.body.dataset.ready === 'true'`. + */ + +function decodeParam(value: string | null): string | null { + if (!value) return null; + try { + return decodeURIComponent(escape(atob(value.replace(/-/g, '+').replace(/_/g, '/')))); + } catch { + return null; + } +} + +function getTex(): string { + const params = new URLSearchParams(location.search); + const fromParam = decodeParam(params.get('tex')); + if (fromParam) return fromParam; + const fromHash = decodeParam(location.hash.slice(1)); + if (fromHash) return fromHash; + return ''; +} + +const tex = getTex(); +const output = document.getElementById('output') as HTMLDivElement; + +function finish(): void { + const mj = (window as any).MathJax; + if (mj && mj.typesetPromise) { + mj.typesetPromise([output]) + .catch((err: any) => console.error('MathJax typeset failed', err)) + .finally(() => { + document.body.dataset.ready = 'true'; + }); + } else { + document.body.dataset.ready = 'true'; + } +} + +try { + render(tex, (div) => { + output.appendChild(div); + finish(); + }); +} catch (err) { + console.error('render failed', err); + document.body.dataset.ready = 'true'; + document.body.dataset.error = String((err as Error).message); +} diff --git a/playground/src/style.css b/playground/src/style.css new file mode 100644 index 00000000..45ad1acb --- /dev/null +++ b/playground/src/style.css @@ -0,0 +1,147 @@ +:root { + --bg: #101418; + --bg-panel: #161b21; + --bg-editor: #0d1117; + --fg: #dbe2ea; + --fg-dim: #8b98a5; + --accent: #4f8cff; + --error: #ff6b6b; + --border: #2a333d; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + height: 100%; + background: var(--bg); + color: var(--fg); + font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; +} + +#app, +body { + display: flex; + flex-direction: column; +} + +.topbar { + display: flex; + align-items: center; + gap: 16px; + padding: 8px 16px; + background: var(--bg-panel); + border-bottom: 1px solid var(--border); + flex: none; +} + +.topbar h1 { + font-size: 16px; + margin: 0; + white-space: nowrap; +} + +.badge { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--accent); + border: 1px solid var(--accent); + border-radius: 999px; + padding: 1px 8px; + margin-left: 6px; + vertical-align: middle; +} + +.examples { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.example-btn { + background: transparent; + color: var(--fg-dim); + border: 1px solid var(--border); + border-radius: 6px; + padding: 3px 10px; + font-size: 12px; + cursor: pointer; +} + +.example-btn:hover { + color: var(--fg); + border-color: var(--accent); +} + +.layout { + display: grid; + grid-template-columns: minmax(320px, 42%) 1fr; + flex: 1; + min-height: 0; +} + +.pane { + min-height: 0; + display: flex; + flex-direction: column; +} + +.editor-pane { + border-right: 1px solid var(--border); +} + +.editor { + flex: 1; + min-height: 0; + resize: none; + border: none; + outline: none; + background: var(--bg-editor); + color: #c9d4e0; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 13px; + line-height: 1.5; + padding: 14px; + tab-size: 2; +} + +.diagnostics { + flex: none; + max-height: 130px; + overflow: auto; + background: var(--bg-panel); + border-top: 1px solid var(--border); + padding: 8px 12px; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 11.5px; + color: var(--fg-dim); +} + +.diag-error { + color: var(--error); +} + +.preview-pane { + overflow: auto; + padding: 20px; +} + +.preview { + min-height: 100%; +} + +.render-error { + color: var(--error); + white-space: pre-wrap; + font-size: 12px; +} + +/* the rendered diagrams shouldn't shrink on narrow previews */ +.pspicture { + max-width: 100%; +} diff --git a/playground/tsconfig.json b/playground/tsconfig.json new file mode 100644 index 00000000..c12eeafe --- /dev/null +++ b/playground/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "types": ["vite/client"] + }, + "include": ["src", "vite.config.ts"] +} diff --git a/playground/vite.config.ts b/playground/vite.config.ts new file mode 100644 index 00000000..cdfb135f --- /dev/null +++ b/playground/vite.config.ts @@ -0,0 +1,73 @@ +import { defineConfig, type Plugin } from 'vite'; +import { fileURLToPath } from 'node:url'; + +// Point every workspace package at its TypeScript source so the dev loop +// runs against src/ (no build step needed) and HMR works on any change. +const fromRoot = (p: string) => fileURLToPath(new URL(`../${p}`, import.meta.url)); + +/** + * The Peggy-generated parser (packages/latex2js/src/grammar/parser.js) is + * CommonJS for the published package builds, but the playground serves the + * workspace sources raw (no pre-bundling), so the browser would hit + * `module is not defined`. Convert its export tail to ESM on the fly. + */ +function cjsParserToEsm(): Plugin { + return { + name: 'latex2js-cjs-parser-to-esm', + enforce: 'pre', + transform(code, id) { + if (!id.endsWith('grammar/parser.js')) return; + // The generated error class extends `SyntaxError`; once we export a + // module-scoped `SyntaxError` below it would shadow the global (TDZ), + // so pin the base class to the global explicitly. + code = code.replace( + 'class peg$SyntaxError extends SyntaxError', + 'class peg$SyntaxError extends globalThis.SyntaxError' + ); + const tail = code.indexOf('module.exports = {'); + if (tail === -1) return; + const body = code + .slice(tail + 'module.exports = {'.length) + .replace(/;\s*$/, ''); + const m = body.match( + /StartRules:\s*(\[[^\]]*\])\s*,\s*SyntaxError:\s*(\S+)\s*,\s*parse:\s*(\S+)\s*,/ + ); + if (!m) return; + return { + code: + code.slice(0, tail) + + `export const StartRules = ${m[1]};\n` + + `export const SyntaxError = ${m[2]};\n` + + `export const parse = ${m[3]};\n`, + map: null, + }; + }, + }; +} + +export default defineConfig({ + plugins: [cjsParserToEsm()], + resolve: { + alias: { + latex2js: fromRoot('packages/latex2js/src/index.ts'), + latex2html5: fromRoot('packages/html5/src/index.ts'), + '@latex2js/pstricks': fromRoot('packages/pstricks/src/index.ts'), + '@latex2js/settings': fromRoot('packages/settings/src/index.ts'), + '@latex2js/utils': fromRoot('packages/utils/src/index.ts'), + '@latex2js/macros': fromRoot('packages/macros/src/index.ts'), + mathjaxjs: fromRoot('packages/mathjaxjs/src/index.ts'), + }, + }, + build: { + rollupOptions: { + input: { + main: fileURLToPath(new URL('./index.html', import.meta.url)), + render: fileURLToPath(new URL('./render.html', import.meta.url)), + }, + }, + }, + server: { + port: 5173, + open: false, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43ba3259..007fecd0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,16 +1,17 @@ -lockfileVersion: "9.0" +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false importers: + .: devDependencies: - "@types/jest": + '@types/jest': specifier: ^29.5.0 version: 29.5.14 - "@types/node": + '@types/node': specifier: ^20.0.0 version: 20.19.4 copyfiles: @@ -18,10 +19,13 @@ importers: version: 2.4.1 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) + version: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) + jest-environment-jsdom: + specifier: ^29.7.0 + version: 29.7.0(supports-color@8.1.1) lerna: specifier: ^8.2.3 - version: 8.2.3(babel-plugin-macros@3.1.0)(encoding@0.1.13) + version: 8.2.3(babel-plugin-macros@3.1.0)(debug@4.4.1(supports-color@8.1.1))(encoding@0.1.13)(supports-color@8.1.1) prettier: specifier: ^3.0.0 version: 3.6.2 @@ -30,7 +34,7 @@ importers: version: 5.0.10 ts-jest: specifier: ^29.1.0 - version: 29.4.0(@babel/core@7.28.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)))(typescript@5.8.3) + version: 29.4.0(@babel/core@7.28.0(supports-color@8.1.1))(@jest/transform@29.7.0(supports-color@8.1.1))(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.0(supports-color@8.1.1))(supports-color@8.1.1))(esbuild@0.28.2)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)))(typescript@5.8.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.19.4)(typescript@5.8.3) @@ -42,13 +46,13 @@ importers: packages/html5: dependencies: - "@latex2js/macros": + '@latex2js/macros': specifier: workspace:^ version: link:../macros/dist - "@latex2js/pstricks": + '@latex2js/pstricks': specifier: workspace:^ version: link:../pstricks/dist - "@latex2js/utils": + '@latex2js/utils': specifier: workspace:^ version: link:../utils/dist latex2js: @@ -65,15 +69,19 @@ importers: packages/latex2js: dependencies: - "@latex2js/pstricks": + '@latex2js/pstricks': specifier: workspace:^ version: link:../pstricks/dist - "@latex2js/settings": + '@latex2js/settings': specifier: workspace:^ version: link:../settings/dist - "@latex2js/utils": + '@latex2js/utils': specifier: workspace:^ version: link:../utils/dist + devDependencies: + peggy: + specifier: ^5.1.0 + version: 5.1.0 publishDirectory: dist packages/macros: @@ -88,7 +96,7 @@ importers: specifier: workspace:^ version: link:../mathjaxjs/dist devDependencies: - "@types/react": + '@types/react': specifier: ^18.0.0 version: 18.3.23 react: @@ -98,23 +106,23 @@ importers: packages/pstricks: dependencies: - "@latex2js/settings": + '@latex2js/settings': specifier: workspace:^ version: link:../settings/dist - "@latex2js/utils": + '@latex2js/utils': specifier: workspace:^ version: link:../utils/dist publishDirectory: dist packages/react: dependencies: - "@latex2js/macros": + '@latex2js/macros': specifier: workspace:^ version: link:../macros/dist - "@latex2js/pstricks": + '@latex2js/pstricks': specifier: workspace:^ version: link:../pstricks/dist - "@latex2js/utils": + '@latex2js/utils': specifier: workspace:^ version: link:../utils/dist latex2js: @@ -133,17 +141,17 @@ importers: specifier: ^19.0.0 version: 19.1.0(react@19.1.0) devDependencies: - "@types/react": + '@types/react': specifier: ^18.2.0 version: 18.3.23 - "@types/react-dom": + '@types/react-dom': specifier: ^18.2.0 version: 18.3.7(@types/react@18.3.23) publishDirectory: dist packages/settings: dependencies: - "@latex2js/utils": + '@latex2js/utils': specifier: workspace:^ version: link:../utils/dist publishDirectory: dist @@ -153,13 +161,13 @@ importers: packages/vue: dependencies: - "@latex2js/macros": + '@latex2js/macros': specifier: workspace:^ version: link:../macros/dist - "@latex2js/pstricks": + '@latex2js/pstricks': specifier: workspace:^ version: link:../pstricks/dist - "@latex2js/utils": + '@latex2js/utils': specifier: workspace:^ version: link:../utils/dist latex2js: @@ -177,2139 +185,1588 @@ importers: version: 1.8.27(typescript@5.8.3) publishDirectory: dist + playground: + devDependencies: + '@playwright/test': + specifier: ^1.62.1 + version: 1.62.1 + typescript: + specifier: ^5.8.0 + version: 5.8.3 + vite: + specifier: ^7.0.0 + version: 7.3.6(@types/node@20.19.4)(yaml@2.8.0) + packages: - "@ampproject/remapping@2.3.0": - resolution: - { - integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==, - } - engines: { node: ">=6.0.0" } - - "@babel/code-frame@7.27.1": - resolution: - { - integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==, - } - engines: { node: ">=6.9.0" } - - "@babel/compat-data@7.28.0": - resolution: - { - integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==, - } - engines: { node: ">=6.9.0" } - - "@babel/core@7.28.0": - resolution: - { - integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==, - } - engines: { node: ">=6.9.0" } - - "@babel/generator@7.27.5": - resolution: - { - integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==, - } - engines: { node: ">=6.9.0" } - - "@babel/generator@7.28.0": - resolution: - { - integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-compilation-targets@7.27.2": - resolution: - { - integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-globals@7.28.0": - resolution: - { - integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-module-imports@7.27.1": - resolution: - { - integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-module-transforms@7.27.3": - resolution: - { - integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==, - } - engines: { node: ">=6.9.0" } + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.0': + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.0': + resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.27.5': + resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.0': + resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + engines: {node: '>=6.9.0'} peerDependencies: - "@babel/core": ^7.0.0 - - "@babel/helper-plugin-utils@7.27.1": - resolution: - { - integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-string-parser@7.27.1": - resolution: - { - integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-validator-identifier@7.27.1": - resolution: - { - integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==, - } - engines: { node: ">=6.9.0" } - - "@babel/helper-validator-option@7.27.1": - resolution: - { - integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, - } - engines: { node: ">=6.9.0" } - - "@babel/helpers@7.27.6": - resolution: - { - integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==, - } - engines: { node: ">=6.9.0" } - - "@babel/parser@7.27.7": - resolution: - { - integrity: sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==, - } - engines: { node: ">=6.0.0" } + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.27.6': + resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.7': + resolution: {integrity: sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==} + engines: {node: '>=6.0.0'} hasBin: true - "@babel/parser@7.28.0": - resolution: - { - integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==, - } - engines: { node: ">=6.0.0" } + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + engines: {node: '>=6.0.0'} hasBin: true - "@babel/plugin-syntax-async-generators@7.8.4": - resolution: - { - integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==, - } + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: - "@babel/core": ^7.0.0-0 + '@babel/core': ^7.0.0-0 - "@babel/plugin-syntax-bigint@7.8.3": - resolution: - { - integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==, - } + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: - "@babel/core": ^7.0.0-0 + '@babel/core': ^7.0.0-0 - "@babel/plugin-syntax-class-properties@7.12.13": - resolution: - { - integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==, - } + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: - "@babel/core": ^7.0.0-0 - - "@babel/plugin-syntax-class-static-block@7.14.5": - resolution: - { - integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==, - } - engines: { node: ">=6.9.0" } + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} peerDependencies: - "@babel/core": ^7.0.0-0 - - "@babel/plugin-syntax-import-attributes@7.27.1": - resolution: - { - integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==, - } - engines: { node: ">=6.9.0" } + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.27.1': + resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + engines: {node: '>=6.9.0'} peerDependencies: - "@babel/core": ^7.0.0-0 + '@babel/core': ^7.0.0-0 - "@babel/plugin-syntax-import-meta@7.10.4": - resolution: - { - integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==, - } + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: - "@babel/core": ^7.0.0-0 + '@babel/core': ^7.0.0-0 - "@babel/plugin-syntax-json-strings@7.8.3": - resolution: - { - integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==, - } + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} peerDependencies: - "@babel/core": ^7.0.0-0 - - "@babel/plugin-syntax-jsx@7.27.1": - resolution: - { - integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==, - } - engines: { node: ">=6.9.0" } + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} peerDependencies: - "@babel/core": ^7.0.0-0 + '@babel/core': ^7.0.0-0 - "@babel/plugin-syntax-logical-assignment-operators@7.10.4": - resolution: - { - integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==, - } + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} peerDependencies: - "@babel/core": ^7.0.0-0 + '@babel/core': ^7.0.0-0 - "@babel/plugin-syntax-nullish-coalescing-operator@7.8.3": - resolution: - { - integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==, - } + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} peerDependencies: - "@babel/core": ^7.0.0-0 + '@babel/core': ^7.0.0-0 - "@babel/plugin-syntax-numeric-separator@7.10.4": - resolution: - { - integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==, - } + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} peerDependencies: - "@babel/core": ^7.0.0-0 + '@babel/core': ^7.0.0-0 - "@babel/plugin-syntax-object-rest-spread@7.8.3": - resolution: - { - integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==, - } + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} peerDependencies: - "@babel/core": ^7.0.0-0 + '@babel/core': ^7.0.0-0 - "@babel/plugin-syntax-optional-catch-binding@7.8.3": - resolution: - { - integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==, - } + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} peerDependencies: - "@babel/core": ^7.0.0-0 + '@babel/core': ^7.0.0-0 - "@babel/plugin-syntax-optional-chaining@7.8.3": - resolution: - { - integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==, - } + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} peerDependencies: - "@babel/core": ^7.0.0-0 - - "@babel/plugin-syntax-private-property-in-object@7.14.5": - resolution: - { - integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==, - } - engines: { node: ">=6.9.0" } + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} peerDependencies: - "@babel/core": ^7.0.0-0 - - "@babel/plugin-syntax-top-level-await@7.14.5": - resolution: - { - integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==, - } - engines: { node: ">=6.9.0" } + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} peerDependencies: - "@babel/core": ^7.0.0-0 - - "@babel/plugin-syntax-typescript@7.27.1": - resolution: - { - integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==, - } - engines: { node: ">=6.9.0" } + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} peerDependencies: - "@babel/core": ^7.0.0-0 - - "@babel/runtime@7.27.6": - resolution: - { - integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==, - } - engines: { node: ">=6.9.0" } - - "@babel/template@7.27.2": - resolution: - { - integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==, - } - engines: { node: ">=6.9.0" } - - "@babel/traverse@7.27.7": - resolution: - { - integrity: sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==, - } - engines: { node: ">=6.9.0" } - - "@babel/traverse@7.28.0": - resolution: - { - integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==, - } - engines: { node: ">=6.9.0" } - - "@babel/types@7.27.7": - resolution: - { - integrity: sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==, - } - engines: { node: ">=6.9.0" } - - "@babel/types@7.28.0": - resolution: - { - integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==, - } - engines: { node: ">=6.9.0" } - - "@bcoe/v8-coverage@0.2.3": - resolution: - { - integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==, - } - - "@cspotcode/source-map-support@0.8.1": - resolution: - { - integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==, - } - engines: { node: ">=12" } - - "@emnapi/core@1.4.3": - resolution: - { - integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==, - } - - "@emnapi/runtime@1.4.3": - resolution: - { - integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==, - } - - "@emnapi/wasi-threads@1.0.2": - resolution: - { - integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==, - } - - "@hutson/parse-repository-url@3.0.2": - resolution: - { - integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==, - } - engines: { node: ">=6.9.0" } - - "@isaacs/cliui@8.0.2": - resolution: - { - integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==, - } - engines: { node: ">=12" } - - "@isaacs/string-locale-compare@1.1.0": - resolution: - { - integrity: sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==, - } - - "@istanbuljs/load-nyc-config@1.1.0": - resolution: - { - integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==, - } - engines: { node: ">=8" } - - "@istanbuljs/schema@0.1.3": - resolution: - { - integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==, - } - engines: { node: ">=8" } - - "@jest/console@29.7.0": - resolution: - { - integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jest/core@29.7.0": - resolution: - { - integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.27.6': + resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.0': + resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.7': + resolution: {integrity: sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.0': + resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@emnapi/core@1.4.3': + resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@emnapi/wasi-threads@1.0.2': + resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@hutson/parse-repository-url@3.0.2': + resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} + engines: {node: '>=6.9.0'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@isaacs/string-locale-compare@1.1.0': + resolution: {integrity: sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true - "@jest/environment@29.7.0": - resolution: - { - integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jest/expect-utils@29.7.0": - resolution: - { - integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jest/expect@29.7.0": - resolution: - { - integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jest/fake-timers@29.7.0": - resolution: - { - integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jest/globals@29.7.0": - resolution: - { - integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jest/reporters@29.7.0": - resolution: - { - integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true - "@jest/schemas@29.6.3": - resolution: - { - integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jest/source-map@29.6.3": - resolution: - { - integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jest/test-result@29.7.0": - resolution: - { - integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jest/test-sequencer@29.7.0": - resolution: - { - integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jest/transform@29.7.0": - resolution: - { - integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jest/types@29.6.3": - resolution: - { - integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } - - "@jridgewell/gen-mapping@0.3.12": - resolution: - { - integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==, - } - - "@jridgewell/resolve-uri@3.1.2": - resolution: - { - integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, - } - engines: { node: ">=6.0.0" } - - "@jridgewell/sourcemap-codec@1.5.4": - resolution: - { - integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==, - } - - "@jridgewell/trace-mapping@0.3.29": - resolution: - { - integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==, - } - - "@jridgewell/trace-mapping@0.3.9": - resolution: - { - integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==, - } - - "@lerna/create@8.2.3": - resolution: - { - integrity: sha512-f+68+iojcQ0tZRMfCgQyJdsdz+YPu3/d+0Zo1RJz92bgBxTCiEU+dHACVq1n3sEjm/YWPnFGdag8U5EYYmP3WA==, - } - engines: { node: ">=18.0.0" } - - "@napi-rs/wasm-runtime@0.2.4": - resolution: - { - integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==, - } - - "@npmcli/agent@2.2.2": - resolution: - { - integrity: sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@npmcli/arborist@7.5.4": - resolution: - { - integrity: sha512-nWtIc6QwwoUORCRNzKx4ypHqCk3drI+5aeYdMTQQiRCcn4lOOgfQh7WyZobGYTxXPSq1VwV53lkpN/BRlRk08g==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@lerna/create@8.2.3': + resolution: {integrity: sha512-f+68+iojcQ0tZRMfCgQyJdsdz+YPu3/d+0Zo1RJz92bgBxTCiEU+dHACVq1n3sEjm/YWPnFGdag8U5EYYmP3WA==} + engines: {node: '>=18.0.0'} + deprecated: This package is an implementation detail of Lerna and is no longer published separately. + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/wasm-runtime@0.2.4': + resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} + + '@npmcli/agent@2.2.2': + resolution: {integrity: sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@npmcli/arborist@7.5.4': + resolution: {integrity: sha512-nWtIc6QwwoUORCRNzKx4ypHqCk3drI+5aeYdMTQQiRCcn4lOOgfQh7WyZobGYTxXPSq1VwV53lkpN/BRlRk08g==} + engines: {node: ^16.14.0 || >=18.0.0} hasBin: true - "@npmcli/fs@3.1.1": - resolution: - { - integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } - - "@npmcli/git@5.0.8": - resolution: - { - integrity: sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@npmcli/installed-package-contents@2.1.0": - resolution: - { - integrity: sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + '@npmcli/fs@3.1.1': + resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@npmcli/git@5.0.8': + resolution: {integrity: sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@npmcli/installed-package-contents@2.1.0': + resolution: {integrity: sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true - "@npmcli/map-workspaces@3.0.6": - resolution: - { - integrity: sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } - - "@npmcli/metavuln-calculator@7.1.1": - resolution: - { - integrity: sha512-Nkxf96V0lAx3HCpVda7Vw4P23RILgdi/5K1fmj2tZkWIYLpXAN8k2UVVOsW16TsS5F8Ws2I7Cm+PU1/rsVF47g==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@npmcli/name-from-folder@2.0.0": - resolution: - { - integrity: sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } - - "@npmcli/node-gyp@3.0.0": - resolution: - { - integrity: sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } - - "@npmcli/package-json@5.2.0": - resolution: - { - integrity: sha512-qe/kiqqkW0AGtvBjL8TJKZk/eBBSpnJkUWvHdQ9jM2lKHXRYYJuyNpJPlJw3c8QjC2ow6NZYiLExhUaeJelbxQ==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@npmcli/promise-spawn@7.0.2": - resolution: - { - integrity: sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@npmcli/query@3.1.0": - resolution: - { - integrity: sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } - - "@npmcli/redact@2.0.1": - resolution: - { - integrity: sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@npmcli/run-script@8.1.0": - resolution: - { - integrity: sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@nx/devkit@20.8.2": - resolution: - { - integrity: sha512-rr9p2/tZDQivIpuBUpZaFBK6bZ+b5SAjZk75V4tbCUqGW3+5OPuVvBPm+X+7PYwUF6rwSpewxkjWNeGskfCe+Q==, - } + '@npmcli/map-workspaces@3.0.6': + resolution: {integrity: sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@npmcli/metavuln-calculator@7.1.1': + resolution: {integrity: sha512-Nkxf96V0lAx3HCpVda7Vw4P23RILgdi/5K1fmj2tZkWIYLpXAN8k2UVVOsW16TsS5F8Ws2I7Cm+PU1/rsVF47g==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@npmcli/name-from-folder@2.0.0': + resolution: {integrity: sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@npmcli/node-gyp@3.0.0': + resolution: {integrity: sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@npmcli/package-json@5.2.0': + resolution: {integrity: sha512-qe/kiqqkW0AGtvBjL8TJKZk/eBBSpnJkUWvHdQ9jM2lKHXRYYJuyNpJPlJw3c8QjC2ow6NZYiLExhUaeJelbxQ==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@npmcli/promise-spawn@7.0.2': + resolution: {integrity: sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@npmcli/query@3.1.0': + resolution: {integrity: sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@npmcli/redact@2.0.1': + resolution: {integrity: sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@npmcli/run-script@8.1.0': + resolution: {integrity: sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@nx/devkit@20.8.2': + resolution: {integrity: sha512-rr9p2/tZDQivIpuBUpZaFBK6bZ+b5SAjZk75V4tbCUqGW3+5OPuVvBPm+X+7PYwUF6rwSpewxkjWNeGskfCe+Q==} peerDependencies: - nx: ">= 19 <= 21" - - "@nx/nx-darwin-arm64@20.8.2": - resolution: - { - integrity: sha512-t+bmCn6sRPNGU6hnSyWNvbQYA/KgsxGZKYlaCLRwkNhI2akModcBUqtktJzCKd1XHDqs6EkEFBWjFr8/kBEkSg==, - } - engines: { node: ">= 10" } + nx: '>= 19 <= 21' + + '@nx/nx-darwin-arm64@20.8.2': + resolution: {integrity: sha512-t+bmCn6sRPNGU6hnSyWNvbQYA/KgsxGZKYlaCLRwkNhI2akModcBUqtktJzCKd1XHDqs6EkEFBWjFr8/kBEkSg==} + engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - "@nx/nx-darwin-x64@20.8.2": - resolution: - { - integrity: sha512-pt/wmDLM31Es8/EzazlyT5U+ou2l60rfMNFGCLqleHEQ0JUTc0KWnOciBLbHIQFiPsCQZJFEKyfV5V/ncePmmw==, - } - engines: { node: ">= 10" } + '@nx/nx-darwin-x64@20.8.2': + resolution: {integrity: sha512-pt/wmDLM31Es8/EzazlyT5U+ou2l60rfMNFGCLqleHEQ0JUTc0KWnOciBLbHIQFiPsCQZJFEKyfV5V/ncePmmw==} + engines: {node: '>= 10'} cpu: [x64] os: [darwin] - "@nx/nx-freebsd-x64@20.8.2": - resolution: - { - integrity: sha512-joZxFbgJfkHkB9uMIJr73Gpnm9pnpvr0XKGbWC409/d2x7q1qK77tKdyhGm+A3+kaZFwstNVPmCUtUwJYyU6LA==, - } - engines: { node: ">= 10" } + '@nx/nx-freebsd-x64@20.8.2': + resolution: {integrity: sha512-joZxFbgJfkHkB9uMIJr73Gpnm9pnpvr0XKGbWC409/d2x7q1qK77tKdyhGm+A3+kaZFwstNVPmCUtUwJYyU6LA==} + engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - "@nx/nx-linux-arm-gnueabihf@20.8.2": - resolution: - { - integrity: sha512-98O/qsxn4vIMPY/FyzvmVrl7C5yFhCUVk0/4PF+PA2SvtQ051L1eMRY6bq/lb69qfN6szJPZ41PG5mPx0NeLZw==, - } - engines: { node: ">= 10" } + '@nx/nx-linux-arm-gnueabihf@20.8.2': + resolution: {integrity: sha512-98O/qsxn4vIMPY/FyzvmVrl7C5yFhCUVk0/4PF+PA2SvtQ051L1eMRY6bq/lb69qfN6szJPZ41PG5mPx0NeLZw==} + engines: {node: '>= 10'} cpu: [arm] os: [linux] - "@nx/nx-linux-arm64-gnu@20.8.2": - resolution: - { - integrity: sha512-h6a+HxwfSpxsi4KpxGgPh9GDBmD2E+XqGCdfYpobabxqEBvlnIlJyuDhlRR06cTWpuNXHpRdrVogmV6m/YbtDg==, - } - engines: { node: ">= 10" } + '@nx/nx-linux-arm64-gnu@20.8.2': + resolution: {integrity: sha512-h6a+HxwfSpxsi4KpxGgPh9GDBmD2E+XqGCdfYpobabxqEBvlnIlJyuDhlRR06cTWpuNXHpRdrVogmV6m/YbtDg==} + engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] - "@nx/nx-linux-arm64-musl@20.8.2": - resolution: - { - integrity: sha512-4Ev+jM0VAxDHV/dFgMXjQTCXS4I8W4oMe7FSkXpG8RUn6JK659DC8ExIDPoGIh+Cyqq6r6mw1CSia+ciQWICWQ==, - } - engines: { node: ">= 10" } + '@nx/nx-linux-arm64-musl@20.8.2': + resolution: {integrity: sha512-4Ev+jM0VAxDHV/dFgMXjQTCXS4I8W4oMe7FSkXpG8RUn6JK659DC8ExIDPoGIh+Cyqq6r6mw1CSia+ciQWICWQ==} + engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] - "@nx/nx-linux-x64-gnu@20.8.2": - resolution: - { - integrity: sha512-nR0ev+wxu+nQYRd7bhqggOxK7UfkV6h+Ko1mumUFyrM5GvPpz/ELhjJFSnMcOkOMcvH0b6G5uTBJvN1XWCkbmg==, - } - engines: { node: ">= 10" } + '@nx/nx-linux-x64-gnu@20.8.2': + resolution: {integrity: sha512-nR0ev+wxu+nQYRd7bhqggOxK7UfkV6h+Ko1mumUFyrM5GvPpz/ELhjJFSnMcOkOMcvH0b6G5uTBJvN1XWCkbmg==} + engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] - "@nx/nx-linux-x64-musl@20.8.2": - resolution: - { - integrity: sha512-ost41l5yc2aq2Gc9bMMpaPi/jkXqbXEMEPHrxWKuKmaek3K2zbVDQzvBBNcQKxf/mlCsrqN4QO0mKYSRRqag5A==, - } - engines: { node: ">= 10" } + '@nx/nx-linux-x64-musl@20.8.2': + resolution: {integrity: sha512-ost41l5yc2aq2Gc9bMMpaPi/jkXqbXEMEPHrxWKuKmaek3K2zbVDQzvBBNcQKxf/mlCsrqN4QO0mKYSRRqag5A==} + engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] - "@nx/nx-win32-arm64-msvc@20.8.2": - resolution: - { - integrity: sha512-0SEOqT/daBG5WtM9vOGilrYaAuf1tiALdrFavY62+/arXYxXemUKmRI5qoKDTnvoLMBGkJs6kxhMO5b7aUXIvQ==, - } - engines: { node: ">= 10" } + '@nx/nx-win32-arm64-msvc@20.8.2': + resolution: {integrity: sha512-0SEOqT/daBG5WtM9vOGilrYaAuf1tiALdrFavY62+/arXYxXemUKmRI5qoKDTnvoLMBGkJs6kxhMO5b7aUXIvQ==} + engines: {node: '>= 10'} cpu: [arm64] os: [win32] - "@nx/nx-win32-x64-msvc@20.8.2": - resolution: - { - integrity: sha512-iIsY+tVqes/NOqTbJmggL9Juie/iaDYlWgXA9IUv88FE9thqWKhVj4/tCcPjsOwzD+1SVna3YISEEFsx5UV4ew==, - } - engines: { node: ">= 10" } + '@nx/nx-win32-x64-msvc@20.8.2': + resolution: {integrity: sha512-iIsY+tVqes/NOqTbJmggL9Juie/iaDYlWgXA9IUv88FE9thqWKhVj4/tCcPjsOwzD+1SVna3YISEEFsx5UV4ew==} + engines: {node: '>= 10'} cpu: [x64] os: [win32] - "@octokit/auth-token@4.0.0": - resolution: - { - integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==, - } - engines: { node: ">= 18" } - - "@octokit/core@5.2.1": - resolution: - { - integrity: sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==, - } - engines: { node: ">= 18" } - - "@octokit/endpoint@9.0.6": - resolution: - { - integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==, - } - engines: { node: ">= 18" } - - "@octokit/graphql@7.1.1": - resolution: - { - integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==, - } - engines: { node: ">= 18" } - - "@octokit/openapi-types@24.2.0": - resolution: - { - integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==, - } - - "@octokit/plugin-enterprise-rest@6.0.1": - resolution: - { - integrity: sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==, - } - - "@octokit/plugin-paginate-rest@11.4.4-cjs.2": - resolution: - { - integrity: sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==, - } - engines: { node: ">= 18" } + '@octokit/auth-token@4.0.0': + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} + + '@octokit/core@5.2.1': + resolution: {integrity: sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==} + engines: {node: '>= 18'} + + '@octokit/endpoint@9.0.6': + resolution: {integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==} + engines: {node: '>= 18'} + + '@octokit/graphql@7.1.1': + resolution: {integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==} + engines: {node: '>= 18'} + + '@octokit/openapi-types@24.2.0': + resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} + + '@octokit/plugin-enterprise-rest@6.0.1': + resolution: {integrity: sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==} + + '@octokit/plugin-paginate-rest@11.4.4-cjs.2': + resolution: {integrity: sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==} + engines: {node: '>= 18'} peerDependencies: - "@octokit/core": "5" - - "@octokit/plugin-request-log@4.0.1": - resolution: - { - integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==, - } - engines: { node: ">= 18" } + '@octokit/core': '5' + + '@octokit/plugin-request-log@4.0.1': + resolution: {integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==} + engines: {node: '>= 18'} peerDependencies: - "@octokit/core": "5" - - "@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1": - resolution: - { - integrity: sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==, - } - engines: { node: ">= 18" } + '@octokit/core': '5' + + '@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1': + resolution: {integrity: sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==} + engines: {node: '>= 18'} peerDependencies: - "@octokit/core": ^5 - - "@octokit/request-error@5.1.1": - resolution: - { - integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==, - } - engines: { node: ">= 18" } - - "@octokit/request@8.4.1": - resolution: - { - integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==, - } - engines: { node: ">= 18" } - - "@octokit/rest@20.1.2": - resolution: - { - integrity: sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==, - } - engines: { node: ">= 18" } - - "@octokit/types@13.10.0": - resolution: - { - integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==, - } - - "@pkgjs/parseargs@0.11.0": - resolution: - { - integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, - } - engines: { node: ">=14" } - - "@sigstore/bundle@2.3.2": - resolution: - { - integrity: sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@sigstore/core@1.1.0": - resolution: - { - integrity: sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@sigstore/protobuf-specs@0.3.3": - resolution: - { - integrity: sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==, - } - engines: { node: ^18.17.0 || >=20.5.0 } - - "@sigstore/sign@2.3.2": - resolution: - { - integrity: sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@sigstore/tuf@2.3.4": - resolution: - { - integrity: sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@sigstore/verify@1.2.1": - resolution: - { - integrity: sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@sinclair/typebox@0.27.8": - resolution: - { - integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==, - } - - "@sinonjs/commons@3.0.1": - resolution: - { - integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==, - } - - "@sinonjs/fake-timers@10.3.0": - resolution: - { - integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==, - } - - "@tsconfig/node10@1.0.11": - resolution: - { - integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==, - } - - "@tsconfig/node12@1.0.11": - resolution: - { - integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==, - } - - "@tsconfig/node14@1.0.3": - resolution: - { - integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==, - } - - "@tsconfig/node16@1.0.4": - resolution: - { - integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==, - } - - "@tufjs/canonical-json@2.0.0": - resolution: - { - integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@tufjs/models@2.0.1": - resolution: - { - integrity: sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==, - } - engines: { node: ^16.14.0 || >=18.0.0 } - - "@tybys/wasm-util@0.9.0": - resolution: - { - integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==, - } - - "@types/babel__core@7.20.5": - resolution: - { - integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==, - } - - "@types/babel__generator@7.27.0": - resolution: - { - integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==, - } - - "@types/babel__template@7.4.4": - resolution: - { - integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==, - } - - "@types/babel__traverse@7.20.7": - resolution: - { - integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==, - } - - "@types/graceful-fs@4.1.9": - resolution: - { - integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==, - } - - "@types/istanbul-lib-coverage@2.0.6": - resolution: - { - integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==, - } - - "@types/istanbul-lib-report@3.0.3": - resolution: - { - integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==, - } - - "@types/istanbul-reports@3.0.4": - resolution: - { - integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==, - } - - "@types/jest@29.5.14": - resolution: - { - integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==, - } - - "@types/minimatch@3.0.5": - resolution: - { - integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==, - } - - "@types/minimist@1.2.5": - resolution: - { - integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==, - } - - "@types/node@20.19.4": - resolution: - { - integrity: sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==, - } - - "@types/normalize-package-data@2.4.4": - resolution: - { - integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==, - } - - "@types/parse-json@4.0.2": - resolution: - { - integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==, - } - - "@types/prop-types@15.7.15": - resolution: - { - integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==, - } - - "@types/react-dom@18.3.7": - resolution: - { - integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==, - } + '@octokit/core': ^5 + + '@octokit/request-error@5.1.1': + resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==} + engines: {node: '>= 18'} + + '@octokit/request@8.4.1': + resolution: {integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==} + engines: {node: '>= 18'} + + '@octokit/rest@20.1.2': + resolution: {integrity: sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==} + engines: {node: '>= 18'} + + '@octokit/types@13.10.0': + resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} + + '@peggyjs/from-mem@3.1.3': + resolution: {integrity: sha512-LLlgtfXIaeYXoOYovOI0spLM8ZXaqkAlmcRRrLzHJzLMqkU6Sw0R4KMoCoHx1PjaP815pSCBlS+BN6aD8t1Jgg==} + engines: {node: '>=20.8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@playwright/test@1.62.1': + resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==} + engines: {node: '>=20'} + hasBin: true + + '@rollup/rollup-android-arm-eabi@4.62.4': + resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.4': + resolution: {integrity: sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.4': + resolution: {integrity: sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.4': + resolution: {integrity: sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.4': + resolution: {integrity: sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.4': + resolution: {integrity: sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.4': + resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.4': + resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.4': + resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.4': + resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.4': + resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.4': + resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.4': + resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.4': + resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.4': + resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.4': + resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.4': + resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.4': + resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.4': + resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.4': + resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.4': + resolution: {integrity: sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.4': + resolution: {integrity: sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.4': + resolution: {integrity: sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.4': + resolution: {integrity: sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.4': + resolution: {integrity: sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==} + cpu: [x64] + os: [win32] + + '@sigstore/bundle@2.3.2': + resolution: {integrity: sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@sigstore/core@1.1.0': + resolution: {integrity: sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@sigstore/protobuf-specs@0.3.3': + resolution: {integrity: sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@sigstore/sign@2.3.2': + resolution: {integrity: sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@sigstore/tuf@2.3.4': + resolution: {integrity: sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@sigstore/verify@1.2.1': + resolution: {integrity: sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@tootallnate/once@2.0.1': + resolution: {integrity: sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==} + engines: {node: '>= 10'} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@tufjs/canonical-json@2.0.0': + resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@tufjs/models@2.0.1': + resolution: {integrity: sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.7': + resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/jsdom@20.0.1': + resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} + + '@types/minimatch@3.0.5': + resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + + '@types/minimist@1.2.5': + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + + '@types/node@20.19.4': + resolution: {integrity: sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} peerDependencies: - "@types/react": ^18.0.0 - - "@types/react@18.3.23": - resolution: - { - integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==, - } - - "@types/stack-utils@2.0.3": - resolution: - { - integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==, - } - - "@types/yargs-parser@21.0.3": - resolution: - { - integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==, - } - - "@types/yargs@17.0.33": - resolution: - { - integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==, - } - - "@volar/language-core@1.11.1": - resolution: - { - integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==, - } - - "@volar/source-map@1.11.1": - resolution: - { - integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==, - } - - "@volar/typescript@1.11.1": - resolution: - { - integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==, - } - - "@vue/compiler-core@3.5.17": - resolution: - { - integrity: sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==, - } - - "@vue/compiler-dom@3.5.17": - resolution: - { - integrity: sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==, - } - - "@vue/compiler-sfc@3.5.17": - resolution: - { - integrity: sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==, - } - - "@vue/compiler-ssr@3.5.17": - resolution: - { - integrity: sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==, - } - - "@vue/language-core@1.8.27": - resolution: - { - integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==, - } + '@types/react': ^18.0.0 + + '@types/react@18.3.23': + resolution: {integrity: sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@volar/language-core@1.11.1': + resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} + + '@volar/source-map@1.11.1': + resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==} + + '@volar/typescript@1.11.1': + resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==} + + '@vue/compiler-core@3.5.17': + resolution: {integrity: sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==} + + '@vue/compiler-dom@3.5.17': + resolution: {integrity: sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==} + + '@vue/compiler-sfc@3.5.17': + resolution: {integrity: sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==} + + '@vue/compiler-ssr@3.5.17': + resolution: {integrity: sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==} + + '@vue/language-core@1.8.27': + resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==} peerDependencies: - typescript: "*" + typescript: '*' peerDependenciesMeta: typescript: optional: true - "@vue/reactivity@3.5.17": - resolution: - { - integrity: sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==, - } - - "@vue/runtime-core@3.5.17": - resolution: - { - integrity: sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==, - } - - "@vue/runtime-dom@3.5.17": - resolution: - { - integrity: sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==, - } - - "@vue/server-renderer@3.5.17": - resolution: - { - integrity: sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==, - } + '@vue/reactivity@3.5.17': + resolution: {integrity: sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==} + + '@vue/runtime-core@3.5.17': + resolution: {integrity: sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==} + + '@vue/runtime-dom@3.5.17': + resolution: {integrity: sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==} + + '@vue/server-renderer@3.5.17': + resolution: {integrity: sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==} peerDependencies: vue: 3.5.17 - "@vue/shared@3.5.17": - resolution: - { - integrity: sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==, - } - - "@yarnpkg/lockfile@1.1.0": - resolution: - { - integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==, - } - - "@yarnpkg/parsers@3.0.2": - resolution: - { - integrity: sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==, - } - engines: { node: ">=18.12.0" } - - "@zkochan/js-yaml@0.0.7": - resolution: - { - integrity: sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==, - } + '@vue/shared@3.5.17': + resolution: {integrity: sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==} + + '@yarnpkg/lockfile@1.1.0': + resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} + + '@yarnpkg/parsers@3.0.2': + resolution: {integrity: sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==} + engines: {node: '>=18.12.0'} + + '@zkochan/js-yaml@0.0.7': + resolution: {integrity: sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==} hasBin: true JSONStream@1.3.5: - resolution: - { - integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==, - } + resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true + abab@2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead + abbrev@2.0.0: - resolution: - { - integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + acorn-globals@7.0.1: + resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} acorn-node@1.8.2: - resolution: - { - integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==, - } + resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} acorn-walk@7.2.0: - resolution: - { - integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==, - } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} + engines: {node: '>=0.4.0'} acorn-walk@8.3.4: - resolution: - { - integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==, - } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} acorn@7.4.1: - resolution: - { - integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==, - } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} hasBin: true acorn@8.15.0: - resolution: - { - integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, - } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} hasBin: true add-stream@1.0.0: - resolution: - { - integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==, - } + resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} agent-base@7.1.3: - resolution: - { - integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==, - } - engines: { node: ">= 14" } + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} aggregate-error@3.1.0: - resolution: - { - integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} ansi-colors@4.1.3: - resolution: - { - integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} ansi-escapes@4.3.2: - resolution: - { - integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} ansi-regex@5.0.1: - resolution: - { - integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} ansi-regex@6.1.0: - resolution: - { - integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} ansi-styles@4.3.0: - resolution: - { - integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} ansi-styles@5.2.0: - resolution: - { - integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} ansi-styles@6.2.1: - resolution: - { - integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} anymatch@3.1.3: - resolution: - { - integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} aproba@2.0.0: - resolution: - { - integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==, - } + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} arg@4.1.3: - resolution: - { - integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==, - } + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} argparse@1.0.10: - resolution: - { - integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==, - } + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} argparse@2.0.1: - resolution: - { - integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, - } + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} array-differ@3.0.0: - resolution: - { - integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==} + engines: {node: '>=8'} array-ify@1.0.0: - resolution: - { - integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==, - } + resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} array-union@2.1.0: - resolution: - { - integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} arrify@1.0.1: - resolution: - { - integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} arrify@2.0.1: - resolution: - { - integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==} + engines: {node: '>=8'} asn1.js@4.10.1: - resolution: - { - integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==, - } + resolution: {integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==} assert@1.5.1: - resolution: - { - integrity: sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==, - } + resolution: {integrity: sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==} async@3.2.6: - resolution: - { - integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==, - } + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} asynckit@0.4.0: - resolution: - { - integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, - } + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} available-typed-arrays@1.0.7: - resolution: - { - integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} axios@1.10.0: - resolution: - { - integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==, - } + resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==} babel-jest@29.7.0: - resolution: - { - integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: - "@babel/core": ^7.8.0 + '@babel/core': ^7.8.0 babel-plugin-istanbul@6.1.1: - resolution: - { - integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} babel-plugin-jest-hoist@29.6.3: - resolution: - { - integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} babel-plugin-macros@3.1.0: - resolution: - { - integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==, - } - engines: { node: ">=10", npm: ">=6" } + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} babel-preset-current-node-syntax@1.1.0: - resolution: - { - integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==, - } + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} peerDependencies: - "@babel/core": ^7.0.0 + '@babel/core': ^7.0.0 babel-preset-jest@29.6.3: - resolution: - { - integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: - "@babel/core": ^7.0.0 + '@babel/core': ^7.0.0 balanced-match@1.0.2: - resolution: - { - integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, - } + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} base64-js@1.5.1: - resolution: - { - integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, - } + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} before-after-hook@2.2.3: - resolution: - { - integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==, - } + resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} bin-links@4.0.4: - resolution: - { - integrity: sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} bl@4.1.0: - resolution: - { - integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, - } + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} bn.js@4.12.2: - resolution: - { - integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==, - } + resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} bn.js@5.2.2: - resolution: - { - integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==, - } + resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} brace-expansion@1.1.12: - resolution: - { - integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==, - } + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} brace-expansion@2.0.2: - resolution: - { - integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, - } + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} braces@3.0.3: - resolution: - { - integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} brorand@1.1.0: - resolution: - { - integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==, - } + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} browser-pack@6.1.0: - resolution: - { - integrity: sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==, - } + resolution: {integrity: sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==} hasBin: true browser-resolve@2.0.0: - resolution: - { - integrity: sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==, - } + resolution: {integrity: sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==} browserify-aes@1.2.0: - resolution: - { - integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==, - } + resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} browserify-cipher@1.0.1: - resolution: - { - integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==, - } + resolution: {integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==} browserify-des@1.0.2: - resolution: - { - integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==, - } + resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==} browserify-rsa@4.1.1: - resolution: - { - integrity: sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==, - } - engines: { node: ">= 0.10" } + resolution: {integrity: sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==} + engines: {node: '>= 0.10'} browserify-sign@4.2.3: - resolution: - { - integrity: sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==, - } - engines: { node: ">= 0.12" } + resolution: {integrity: sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==} + engines: {node: '>= 0.12'} browserify-zlib@0.2.0: - resolution: - { - integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==, - } + resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==} browserify@17.0.1: - resolution: - { - integrity: sha512-pxhT00W3ylMhCHwG5yfqtZjNnFuX5h2IJdaBfSo4ChaaBsIp9VLrEMQ1bHV+Xr1uLPXuNDDM1GlJkjli0qkRsw==, - } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-pxhT00W3ylMhCHwG5yfqtZjNnFuX5h2IJdaBfSo4ChaaBsIp9VLrEMQ1bHV+Xr1uLPXuNDDM1GlJkjli0qkRsw==} + engines: {node: '>= 0.8'} hasBin: true browserslist@4.25.1: - resolution: - { - integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==, - } - engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true bs-logger@0.2.6: - resolution: - { - integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} bser@2.1.1: - resolution: - { - integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==, - } + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} buffer-from@1.1.2: - resolution: - { - integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, - } + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} buffer-xor@1.0.3: - resolution: - { - integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==, - } + resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} buffer@5.2.1: - resolution: - { - integrity: sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==, - } + resolution: {integrity: sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==} buffer@5.7.1: - resolution: - { - integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, - } + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} builtin-status-codes@3.0.0: - resolution: - { - integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==, - } + resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} byte-size@8.1.1: - resolution: - { - integrity: sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==, - } - engines: { node: ">=12.17" } + resolution: {integrity: sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==} + engines: {node: '>=12.17'} cacache@18.0.4: - resolution: - { - integrity: sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==} + engines: {node: ^16.14.0 || >=18.0.0} cached-path-relative@1.1.0: - resolution: - { - integrity: sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==, - } + resolution: {integrity: sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==} call-bind-apply-helpers@1.0.2: - resolution: - { - integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} call-bind@1.0.8: - resolution: - { - integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} call-bound@1.0.4: - resolution: - { - integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} callsites@3.1.0: - resolution: - { - integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} camelcase-keys@6.2.2: - resolution: - { - integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} camelcase@5.3.1: - resolution: - { - integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} camelcase@6.3.0: - resolution: - { - integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} caniuse-lite@1.0.30001726: - resolution: - { - integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==, - } + resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==} chalk@4.1.0: - resolution: - { - integrity: sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==} + engines: {node: '>=10'} chalk@4.1.2: - resolution: - { - integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} char-regex@1.0.2: - resolution: - { - integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} chardet@0.7.0: - resolution: - { - integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==, - } + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} chownr@2.0.0: - resolution: - { - integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} ci-info@3.9.0: - resolution: - { - integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} ci-info@4.2.0: - resolution: - { - integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==} + engines: {node: '>=8'} cipher-base@1.0.6: - resolution: - { - integrity: sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==, - } - engines: { node: ">= 0.10" } + resolution: {integrity: sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==} + engines: {node: '>= 0.10'} cjs-module-lexer@1.4.3: - resolution: - { - integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==, - } + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} clean-stack@2.2.0: - resolution: - { - integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} cli-cursor@3.1.0: - resolution: - { - integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} cli-spinners@2.6.1: - resolution: - { - integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==} + engines: {node: '>=6'} cli-spinners@2.9.2: - resolution: - { - integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} cli-width@3.0.0: - resolution: - { - integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==, - } - engines: { node: ">= 10" } + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} cliui@7.0.4: - resolution: - { - integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==, - } + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} cliui@8.0.1: - resolution: - { - integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} clone-deep@4.0.1: - resolution: - { - integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} clone@1.0.4: - resolution: - { - integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==, - } - engines: { node: ">=0.8" } + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} cmd-shim@6.0.3: - resolution: - { - integrity: sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} co@4.6.0: - resolution: - { - integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==, - } - engines: { iojs: ">= 1.0.0", node: ">= 0.12.0" } + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} collect-v8-coverage@1.0.2: - resolution: - { - integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==, - } + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} color-convert@2.0.1: - resolution: - { - integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, - } - engines: { node: ">=7.0.0" } + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} color-name@1.1.4: - resolution: - { - integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, - } + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} color-support@1.1.3: - resolution: - { - integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==, - } + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true columnify@1.6.0: - resolution: - { - integrity: sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==, - } - engines: { node: ">=8.0.0" } + resolution: {integrity: sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==} + engines: {node: '>=8.0.0'} combine-source-map@0.8.0: - resolution: - { - integrity: sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==, - } + resolution: {integrity: sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==} combined-stream@1.0.8: - resolution: - { - integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, - } - engines: { node: ">= 0.8" } + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} common-ancestor-path@1.0.1: - resolution: - { - integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==, - } + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} compare-func@2.0.0: - resolution: - { - integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==, - } + resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} computeds@0.0.1: - resolution: - { - integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==, - } + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} concat-map@0.0.1: - resolution: - { - integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, - } + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} concat-stream@1.6.2: - resolution: - { - integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==, - } - engines: { "0": node >= 0.8 } + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} concat-stream@2.0.0: - resolution: - { - integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==, - } - engines: { "0": node >= 6.0 } + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} console-browserify@1.2.0: - resolution: - { - integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==, - } + resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} console-control-strings@1.1.0: - resolution: - { - integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==, - } + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} constants-browserify@1.0.0: - resolution: - { - integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==, - } + resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==} conventional-changelog-angular@7.0.0: - resolution: - { - integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==, - } - engines: { node: ">=16" } + resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} + engines: {node: '>=16'} conventional-changelog-core@5.0.1: - resolution: - { - integrity: sha512-Rvi5pH+LvgsqGwZPZ3Cq/tz4ty7mjijhr3qR4m9IBXNbxGGYgTVVO+duXzz9aArmHxFtwZ+LRkrNIMDQzgoY4A==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-Rvi5pH+LvgsqGwZPZ3Cq/tz4ty7mjijhr3qR4m9IBXNbxGGYgTVVO+duXzz9aArmHxFtwZ+LRkrNIMDQzgoY4A==} + engines: {node: '>=14'} + deprecated: Deprecated and no longer maintained. Please use conventional-changelog instead. conventional-changelog-preset-loader@3.0.0: - resolution: - { - integrity: sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA==} + engines: {node: '>=14'} conventional-changelog-writer@6.0.1: - resolution: - { - integrity: sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==} + engines: {node: '>=14'} hasBin: true conventional-commits-filter@3.0.0: - resolution: - { - integrity: sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==} + engines: {node: '>=14'} conventional-commits-parser@4.0.0: - resolution: - { - integrity: sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg==} + engines: {node: '>=14'} hasBin: true conventional-recommended-bump@7.0.1: - resolution: - { - integrity: sha512-Ft79FF4SlOFvX4PkwFDRnaNiIVX7YbmqGU0RwccUaiGvgp3S0a8ipR2/Qxk31vclDNM+GSdJOVs2KrsUCjblVA==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-Ft79FF4SlOFvX4PkwFDRnaNiIVX7YbmqGU0RwccUaiGvgp3S0a8ipR2/Qxk31vclDNM+GSdJOVs2KrsUCjblVA==} + engines: {node: '>=14'} hasBin: true convert-source-map@1.1.3: - resolution: - { - integrity: sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==, - } + resolution: {integrity: sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==} convert-source-map@2.0.0: - resolution: - { - integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, - } + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} copyfiles@2.4.1: - resolution: - { - integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==, - } + resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==} hasBin: true core-util-is@1.0.3: - resolution: - { - integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==, - } + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} cosmiconfig@7.1.0: - resolution: - { - integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} cosmiconfig@9.0.0: - resolution: - { - integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} peerDependencies: - typescript: ">=4.9.5" + typescript: '>=4.9.5' peerDependenciesMeta: typescript: optional: true create-ecdh@4.0.4: - resolution: - { - integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==, - } + resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} create-hash@1.1.3: - resolution: - { - integrity: sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==, - } + resolution: {integrity: sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==} create-hash@1.2.0: - resolution: - { - integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==, - } + resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} create-hmac@1.1.7: - resolution: - { - integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==, - } + resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} create-jest@29.7.0: - resolution: - { - integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true create-require@1.1.1: - resolution: - { - integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==, - } + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} cross-spawn@7.0.6: - resolution: - { - integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} crypto-browserify@3.12.1: - resolution: - { - integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==, - } - engines: { node: ">= 0.10" } + resolution: {integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==} + engines: {node: '>= 0.10'} cssesc@3.0.0: - resolution: - { - integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} hasBin: true + cssom@0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + + cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + + cssstyle@2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} + csstype@3.1.3: - resolution: - { - integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==, - } + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} dargs@7.0.0: - resolution: - { - integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} + engines: {node: '>=8'} dash-ast@1.0.0: - resolution: - { - integrity: sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==, - } + resolution: {integrity: sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==} + + data-urls@3.0.2: + resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} + engines: {node: '>=12'} dateformat@3.0.3: - resolution: - { - integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==, - } + resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} de-indent@1.0.2: - resolution: - { - integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==, - } + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} debug@4.4.1: - resolution: - { - integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==, - } - engines: { node: ">=6.0" } + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} peerDependencies: - supports-color: "*" + supports-color: '*' peerDependenciesMeta: supports-color: optional: true decamelize-keys@1.1.1: - resolution: - { - integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} decamelize@1.2.0: - resolution: - { - integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} dedent@1.5.3: - resolution: - { - integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==, - } + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: @@ -2317,10 +1774,7 @@ packages: optional: true dedent@1.6.0: - resolution: - { - integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==, - } + resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: @@ -2328,405 +1782,261 @@ packages: optional: true deepmerge@4.3.1: - resolution: - { - integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} defaults@1.0.4: - resolution: - { - integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==, - } + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} define-data-property@1.1.4: - resolution: - { - integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} define-lazy-prop@2.0.0: - resolution: - { - integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} define-properties@1.2.1: - resolution: - { - integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} defined@1.0.1: - resolution: - { - integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==, - } + resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} delayed-stream@1.0.0: - resolution: - { - integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, - } - engines: { node: ">=0.4.0" } + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} deprecation@2.3.1: - resolution: - { - integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==, - } + resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} deps-sort@2.0.1: - resolution: - { - integrity: sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==, - } + resolution: {integrity: sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==} hasBin: true des.js@1.1.0: - resolution: - { - integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==, - } + resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} detect-indent@5.0.0: - resolution: - { - integrity: sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g==} + engines: {node: '>=4'} detect-newline@3.1.0: - resolution: - { - integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} detective@5.2.1: - resolution: - { - integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==, - } - engines: { node: ">=0.8.0" } + resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} + engines: {node: '>=0.8.0'} hasBin: true diff-sequences@29.6.3: - resolution: - { - integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} diff@4.0.2: - resolution: - { - integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==, - } - engines: { node: ">=0.3.1" } + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} diffie-hellman@5.0.3: - resolution: - { - integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==, - } + resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==} domain-browser@1.2.0: - resolution: - { - integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==, - } - engines: { node: ">=0.4", npm: ">=1.2" } + resolution: {integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==} + engines: {node: '>=0.4', npm: '>=1.2'} + + domexception@4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + deprecated: Use your platform's native DOMException instead dot-prop@5.3.0: - resolution: - { - integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} dotenv-expand@11.0.7: - resolution: - { - integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} + engines: {node: '>=12'} dotenv@16.4.7: - resolution: - { - integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} dotenv@16.6.1: - resolution: - { - integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} dunder-proto@1.0.1: - resolution: - { - integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} duplexer2@0.1.4: - resolution: - { - integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==, - } + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} eastasianwidth@0.2.0: - resolution: - { - integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, - } + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} ejs@3.1.10: - resolution: - { - integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} hasBin: true electron-to-chromium@1.5.178: - resolution: - { - integrity: sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==, - } + resolution: {integrity: sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==} elliptic@6.6.1: - resolution: - { - integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==, - } + resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} emittery@0.13.1: - resolution: - { - integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} emoji-regex@8.0.0: - resolution: - { - integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, - } + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: - resolution: - { - integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, - } + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} encoding@0.1.13: - resolution: - { - integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==, - } + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} end-of-stream@1.4.5: - resolution: - { - integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==, - } + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} enquirer@2.3.6: - resolution: - { - integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==, - } - engines: { node: ">=8.6" } + resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} + engines: {node: '>=8.6'} entities@4.5.0: - resolution: - { - integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==, - } - engines: { node: ">=0.12" } + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} env-paths@2.2.1: - resolution: - { - integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} envinfo@7.13.0: - resolution: - { - integrity: sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==} + engines: {node: '>=4'} hasBin: true err-code@2.0.3: - resolution: - { - integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==, - } + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} error-ex@1.3.2: - resolution: - { - integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==, - } + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} es-define-property@1.0.1: - resolution: - { - integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} es-errors@1.3.0: - resolution: - { - integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} es-object-atoms@1.1.1: - resolution: - { - integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: - resolution: - { - integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true escalade@3.2.0: - resolution: - { - integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} escape-string-regexp@1.0.5: - resolution: - { - integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==, - } - engines: { node: ">=0.8.0" } + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} escape-string-regexp@2.0.0: - resolution: - { - integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true esprima@4.0.1: - resolution: - { - integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} hasBin: true + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@2.0.2: - resolution: - { - integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==, - } + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} eventemitter3@4.0.7: - resolution: - { - integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==, - } + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} events@3.3.0: - resolution: - { - integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==, - } - engines: { node: ">=0.8.x" } + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} evp_bytestokey@1.0.3: - resolution: - { - integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==, - } + resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} execa@5.0.0: - resolution: - { - integrity: sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==} + engines: {node: '>=10'} execa@5.1.1: - resolution: - { - integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} exit@0.1.2: - resolution: - { - integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} expect@29.7.0: - resolution: - { - integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} exponential-backoff@3.1.2: - resolution: - { - integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==, - } + resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} external-editor@3.1.0: - resolution: - { - integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} fast-json-stable-stringify@2.1.0: - resolution: - { - integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, - } + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-safe-stringify@2.1.1: - resolution: - { - integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==, - } + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} fb-watchman@2.0.2: - resolution: - { - integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==, - } + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} fdir@6.4.6: - resolution: - { - integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==, - } + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2734,874 +2044,513 @@ packages: optional: true figures@3.2.0: - resolution: - { - integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} filelist@1.0.4: - resolution: - { - integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==, - } + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} fill-range@7.1.1: - resolution: - { - integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} find-up@2.1.0: - resolution: - { - integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} find-up@4.1.0: - resolution: - { - integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} flat@5.0.2: - resolution: - { - integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==, - } + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true follow-redirects@1.15.9: - resolution: - { - integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==, - } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} peerDependencies: - debug: "*" + debug: '*' peerDependenciesMeta: debug: optional: true for-each@0.3.5: - resolution: - { - integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} foreground-child@3.3.1: - resolution: - { - integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} form-data@4.0.3: - resolution: - { - integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==} + engines: {node: '>= 6'} front-matter@4.0.2: - resolution: - { - integrity: sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==, - } + resolution: {integrity: sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==} fs-constants@1.0.0: - resolution: - { - integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==, - } + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} fs-extra@11.3.0: - resolution: - { - integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==, - } - engines: { node: ">=14.14" } + resolution: {integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==} + engines: {node: '>=14.14'} fs-minipass@2.1.0: - resolution: - { - integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} fs-minipass@3.0.3: - resolution: - { - integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} fs.realpath@1.0.0: - resolution: - { - integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, - } + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] fsevents@2.3.3: - resolution: - { - integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, - } - engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] function-bind@1.1.2: - resolution: - { - integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, - } + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} gensync@1.0.0-beta.2: - resolution: - { - integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} get-assigned-identifiers@1.2.0: - resolution: - { - integrity: sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==, - } + resolution: {integrity: sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==} get-caller-file@2.0.5: - resolution: - { - integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, - } - engines: { node: 6.* || 8.* || >= 10.* } + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} get-intrinsic@1.3.0: - resolution: - { - integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} get-package-type@0.1.0: - resolution: - { - integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==, - } - engines: { node: ">=8.0.0" } + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} get-pkg-repo@4.2.1: - resolution: - { - integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==, - } - engines: { node: ">=6.9.0" } + resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} + engines: {node: '>=6.9.0'} hasBin: true get-port@5.1.1: - resolution: - { - integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} get-proto@1.0.1: - resolution: - { - integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} get-stream@6.0.0: - resolution: - { - integrity: sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==} + engines: {node: '>=10'} get-stream@6.0.1: - resolution: - { - integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} git-raw-commits@3.0.0: - resolution: - { - integrity: sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==} + engines: {node: '>=14'} + deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead. hasBin: true git-remote-origin-url@2.0.0: - resolution: - { - integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} + engines: {node: '>=4'} git-semver-tags@5.0.1: - resolution: - { - integrity: sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==} + engines: {node: '>=14'} + deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead. hasBin: true git-up@7.0.0: - resolution: - { - integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==, - } + resolution: {integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==} git-url-parse@14.0.0: - resolution: - { - integrity: sha512-NnLweV+2A4nCvn4U/m2AoYu0pPKlsmhK9cknG7IMwsjFY1S2jxM+mAhsDxyxfCIGfGaD+dozsyX4b6vkYc83yQ==, - } + resolution: {integrity: sha512-NnLweV+2A4nCvn4U/m2AoYu0pPKlsmhK9cknG7IMwsjFY1S2jxM+mAhsDxyxfCIGfGaD+dozsyX4b6vkYc83yQ==} gitconfiglocal@1.0.0: - resolution: - { - integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==, - } + resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} glob-parent@6.0.2: - resolution: - { - integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, - } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} glob@10.4.5: - resolution: - { - integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==, - } + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: - resolution: - { - integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, - } - deprecated: Glob versions prior to v9 are no longer supported + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@9.3.5: - resolution: - { - integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==, - } - engines: { node: ">=16 || 14 >=14.17" } - - globals@11.12.0: - resolution: - { - integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} + engines: {node: '>=16 || 14 >=14.17'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me gopd@1.2.0: - resolution: - { - integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} graceful-fs@4.2.11: - resolution: - { - integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, - } + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} handlebars@4.7.8: - resolution: - { - integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==, - } - engines: { node: ">=0.4.7" } + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} hasBin: true hard-rejection@2.1.0: - resolution: - { - integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} has-flag@4.0.0: - resolution: - { - integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} has-property-descriptors@1.0.2: - resolution: - { - integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==, - } + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} has-symbols@1.1.0: - resolution: - { - integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} has-tostringtag@1.0.2: - resolution: - { - integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} has-unicode@2.0.1: - resolution: - { - integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==, - } + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} hash-base@2.0.2: - resolution: - { - integrity: sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==, - } + resolution: {integrity: sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==} hash-base@3.0.5: - resolution: - { - integrity: sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==, - } - engines: { node: ">= 0.10" } + resolution: {integrity: sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==} + engines: {node: '>= 0.10'} hash.js@1.1.7: - resolution: - { - integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==, - } + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} hasown@2.0.2: - resolution: - { - integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} he@1.2.0: - resolution: - { - integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==, - } + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true hmac-drbg@1.0.1: - resolution: - { - integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==, - } + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} hosted-git-info@2.8.9: - resolution: - { - integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==, - } + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} hosted-git-info@4.1.0: - resolution: - { - integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} hosted-git-info@7.0.2: - resolution: - { - integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + html-encoding-sniffer@3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} html-escaper@2.0.2: - resolution: - { - integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, - } + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} htmlescape@1.1.1: - resolution: - { - integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==, - } - engines: { node: ">=0.10" } + resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==} + engines: {node: '>=0.10'} http-cache-semantics@4.2.0: - resolution: - { - integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==, - } + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} http-proxy-agent@7.0.2: - resolution: - { - integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==, - } - engines: { node: ">= 14" } + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} https-browserify@1.0.0: - resolution: - { - integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==, - } + resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} https-proxy-agent@7.0.6: - resolution: - { - integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==, - } - engines: { node: ">= 14" } + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} human-signals@2.1.0: - resolution: - { - integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==, - } - engines: { node: ">=10.17.0" } + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} iconv-lite@0.4.24: - resolution: - { - integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} iconv-lite@0.6.3: - resolution: - { - integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} ieee754@1.2.1: - resolution: - { - integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, - } + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} ignore-walk@6.0.5: - resolution: - { - integrity: sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} ignore@5.3.2: - resolution: - { - integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, - } - engines: { node: ">= 4" } + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} import-fresh@3.3.1: - resolution: - { - integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} import-local@3.1.0: - resolution: - { - integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} hasBin: true import-local@3.2.0: - resolution: - { - integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} hasBin: true imurmurhash@0.1.4: - resolution: - { - integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, - } - engines: { node: ">=0.8.19" } + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} indent-string@4.0.0: - resolution: - { - integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} inflight@1.0.6: - resolution: - { - integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==, - } + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.3: - resolution: - { - integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==, - } + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} inherits@2.0.4: - resolution: - { - integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, - } + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} ini@1.3.8: - resolution: - { - integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==, - } + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} ini@4.1.3: - resolution: - { - integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} init-package-json@6.0.3: - resolution: - { - integrity: sha512-Zfeb5ol+H+eqJWHTaGca9BovufyGeIfr4zaaBorPmJBMrJ+KBnN+kQx2ZtXdsotUTgldHmHQV44xvUWOUA7E2w==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-Zfeb5ol+H+eqJWHTaGca9BovufyGeIfr4zaaBorPmJBMrJ+KBnN+kQx2ZtXdsotUTgldHmHQV44xvUWOUA7E2w==} + engines: {node: ^16.14.0 || >=18.0.0} inline-source-map@0.6.3: - resolution: - { - integrity: sha512-1aVsPEsJWMJq/pdMU61CDlm1URcW702MTB4w9/zUjMus6H/Py8o7g68Pr9D4I6QluWGt/KdmswuRhaA05xVR1w==, - } + resolution: {integrity: sha512-1aVsPEsJWMJq/pdMU61CDlm1URcW702MTB4w9/zUjMus6H/Py8o7g68Pr9D4I6QluWGt/KdmswuRhaA05xVR1w==} inquirer@8.2.6: - resolution: - { - integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==, - } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} + engines: {node: '>=12.0.0'} insert-module-globals@7.2.1: - resolution: - { - integrity: sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==, - } + resolution: {integrity: sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==} hasBin: true ip-address@9.0.5: - resolution: - { - integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==, - } - engines: { node: ">= 12" } + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} is-arguments@1.2.0: - resolution: - { - integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} is-arrayish@0.2.1: - resolution: - { - integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, - } + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} is-buffer@1.1.6: - resolution: - { - integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==, - } + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} is-callable@1.2.7: - resolution: - { - integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} is-ci@3.0.1: - resolution: - { - integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==, - } + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true is-core-module@2.16.1: - resolution: - { - integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} is-docker@2.2.1: - resolution: - { - integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} hasBin: true is-extglob@2.1.1: - resolution: - { - integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} is-fullwidth-code-point@3.0.0: - resolution: - { - integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} is-generator-fn@2.1.0: - resolution: - { - integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} is-generator-function@1.1.0: - resolution: - { - integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} is-glob@4.0.3: - resolution: - { - integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} is-interactive@1.0.0: - resolution: - { - integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} is-lambda@1.0.1: - resolution: - { - integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==, - } + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} is-number@7.0.0: - resolution: - { - integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, - } - engines: { node: ">=0.12.0" } + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} is-obj@2.0.0: - resolution: - { - integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} is-plain-obj@1.1.0: - resolution: - { - integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} is-plain-object@2.0.4: - resolution: - { - integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} is-regex@1.2.1: - resolution: - { - integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} is-ssh@1.4.1: - resolution: - { - integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==, - } + resolution: {integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==} is-stream@2.0.0: - resolution: - { - integrity: sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==} + engines: {node: '>=8'} is-stream@2.0.1: - resolution: - { - integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} is-text-path@1.0.1: - resolution: - { - integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} + engines: {node: '>=0.10.0'} is-typed-array@1.1.15: - resolution: - { - integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} is-unicode-supported@0.1.0: - resolution: - { - integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} is-wsl@2.2.0: - resolution: - { - integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} isarray@0.0.1: - resolution: - { - integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==, - } + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} isarray@1.0.0: - resolution: - { - integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==, - } + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} isarray@2.0.5: - resolution: - { - integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==, - } + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} isexe@2.0.0: - resolution: - { - integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, - } + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} isexe@3.1.1: - resolution: - { - integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==, - } - engines: { node: ">=16" } + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} isobject@3.0.1: - resolution: - { - integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} istanbul-lib-coverage@3.2.2: - resolution: - { - integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} istanbul-lib-instrument@5.2.1: - resolution: - { - integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} istanbul-lib-instrument@6.0.3: - resolution: - { - integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} istanbul-lib-report@3.0.1: - resolution: - { - integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} istanbul-lib-source-maps@4.0.1: - resolution: - { - integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} istanbul-reports@3.1.7: - resolution: - { - integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} jackspeak@3.4.3: - resolution: - { - integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, - } + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} jake@10.9.2: - resolution: - { - integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} + engines: {node: '>=10'} hasBin: true jest-changed-files@29.7.0: - resolution: - { - integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-circus@29.7.0: - resolution: - { - integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-cli@29.7.0: - resolution: - { - integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -3610,178 +2559,118 @@ packages: optional: true jest-config@29.7.0: - resolution: - { - integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: - "@types/node": "*" - ts-node: ">=9.0.0" + '@types/node': '*' + ts-node: '>=9.0.0' peerDependenciesMeta: - "@types/node": + '@types/node': optional: true ts-node: optional: true jest-diff@29.7.0: - resolution: - { - integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-docblock@29.7.0: - resolution: - { - integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-each@29.7.0: - resolution: - { - integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-jsdom@29.7.0: + resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true jest-environment-node@29.7.0: - resolution: - { - integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-get-type@29.6.3: - resolution: - { - integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-haste-map@29.7.0: - resolution: - { - integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-leak-detector@29.7.0: - resolution: - { - integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-matcher-utils@29.7.0: - resolution: - { - integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-message-util@29.7.0: - resolution: - { - integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-mock@29.7.0: - resolution: - { - integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-pnp-resolver@1.2.3: - resolution: - { - integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} peerDependencies: - jest-resolve: "*" + jest-resolve: '*' peerDependenciesMeta: jest-resolve: optional: true jest-regex-util@29.6.3: - resolution: - { - integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-resolve-dependencies@29.7.0: - resolution: - { - integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-resolve@29.7.0: - resolution: - { - integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-runner@29.7.0: - resolution: - { - integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-runtime@29.7.0: - resolution: - { - integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-snapshot@29.7.0: - resolution: - { - integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-util@29.7.0: - resolution: - { - integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-validate@29.7.0: - resolution: - { - integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-watcher@29.7.0: - resolution: - { - integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest-worker@29.7.0: - resolution: - { - integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} jest@29.7.0: - resolution: - { - integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -3790,606 +2679,345 @@ packages: optional: true js-tokens@4.0.0: - resolution: - { - integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, - } + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} js-yaml@3.14.1: - resolution: - { - integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==, - } + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} hasBin: true js-yaml@4.1.0: - resolution: - { - integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, - } + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true jsbn@1.1.0: - resolution: - { - integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==, - } + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + + jsdom@20.0.3: + resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} + engines: {node: '>=14'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true jsesc@3.1.0: - resolution: - { - integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} hasBin: true json-parse-better-errors@1.0.2: - resolution: - { - integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==, - } + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} json-parse-even-better-errors@2.3.1: - resolution: - { - integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, - } + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} json-parse-even-better-errors@3.0.2: - resolution: - { - integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} json-stringify-nice@1.1.4: - resolution: - { - integrity: sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==, - } + resolution: {integrity: sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==} json-stringify-safe@5.0.1: - resolution: - { - integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==, - } + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} json5@2.2.3: - resolution: - { - integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} hasBin: true jsonc-parser@3.2.0: - resolution: - { - integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==, - } + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} jsonfile@6.1.0: - resolution: - { - integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==, - } + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} jsonparse@1.3.1: - resolution: - { - integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==, - } - engines: { "0": node >= 0.2.0 } + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} just-diff-apply@5.5.0: - resolution: - { - integrity: sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==, - } + resolution: {integrity: sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==} just-diff@6.0.2: - resolution: - { - integrity: sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==, - } + resolution: {integrity: sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==} kind-of@6.0.3: - resolution: - { - integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} kleur@3.0.3: - resolution: - { - integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} labeled-stream-splicer@2.0.2: - resolution: - { - integrity: sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==, - } + resolution: {integrity: sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==} lerna@8.2.3: - resolution: - { - integrity: sha512-rmuDU+92eWUnnyaPg3Ise339pTxF+r2hu8ky/soCfbGpUoW4kCwsDza3P/LtQJWrKwZWHcosEitfYvxGUWZ16A==, - } - engines: { node: ">=18.0.0" } + resolution: {integrity: sha512-rmuDU+92eWUnnyaPg3Ise339pTxF+r2hu8ky/soCfbGpUoW4kCwsDza3P/LtQJWrKwZWHcosEitfYvxGUWZ16A==} + engines: {node: '>=18.0.0'} hasBin: true leven@3.1.0: - resolution: - { - integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} libnpmaccess@8.0.6: - resolution: - { - integrity: sha512-uM8DHDEfYG6G5gVivVl+yQd4pH3uRclHC59lzIbSvy7b5FEwR+mU49Zq1jEyRtRFv7+M99mUW9S0wL/4laT4lw==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-uM8DHDEfYG6G5gVivVl+yQd4pH3uRclHC59lzIbSvy7b5FEwR+mU49Zq1jEyRtRFv7+M99mUW9S0wL/4laT4lw==} + engines: {node: ^16.14.0 || >=18.0.0} libnpmpublish@9.0.9: - resolution: - { - integrity: sha512-26zzwoBNAvX9AWOPiqqF6FG4HrSCPsHFkQm7nT+xU1ggAujL/eae81RnCv4CJ2In9q9fh10B88sYSzKCUh/Ghg==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-26zzwoBNAvX9AWOPiqqF6FG4HrSCPsHFkQm7nT+xU1ggAujL/eae81RnCv4CJ2In9q9fh10B88sYSzKCUh/Ghg==} + engines: {node: ^16.14.0 || >=18.0.0} lines-and-columns@1.2.4: - resolution: - { - integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, - } + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} lines-and-columns@2.0.3: - resolution: - { - integrity: sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + resolution: {integrity: sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} load-json-file@4.0.0: - resolution: - { - integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} load-json-file@6.2.0: - resolution: - { - integrity: sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==} + engines: {node: '>=8'} locate-path@2.0.0: - resolution: - { - integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} locate-path@5.0.0: - resolution: - { - integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} lodash.ismatch@4.4.0: - resolution: - { - integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==, - } + resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} lodash.memoize@3.0.4: - resolution: - { - integrity: sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==, - } + resolution: {integrity: sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==} lodash.memoize@4.1.2: - resolution: - { - integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==, - } + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} lodash@4.17.21: - resolution: - { - integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, - } + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} log-symbols@4.1.0: - resolution: - { - integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} loose-envify@1.4.0: - resolution: - { - integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, - } + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true lru-cache@10.4.3: - resolution: - { - integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, - } + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} lru-cache@5.1.1: - resolution: - { - integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, - } + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} lru-cache@6.0.0: - resolution: - { - integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} magic-string@0.30.17: - resolution: - { - integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==, - } + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} make-dir@2.1.0: - resolution: - { - integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} make-dir@4.0.0: - resolution: - { - integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} make-error@1.3.6: - resolution: - { - integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==, - } + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} make-fetch-happen@13.0.1: - resolution: - { - integrity: sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==} + engines: {node: ^16.14.0 || >=18.0.0} makeerror@1.0.12: - resolution: - { - integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==, - } + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} map-obj@1.0.1: - resolution: - { - integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} map-obj@4.3.0: - resolution: - { - integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} math-intrinsics@1.1.0: - resolution: - { - integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} md5.js@1.3.5: - resolution: - { - integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==, - } + resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} meow@8.1.2: - resolution: - { - integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} + engines: {node: '>=10'} merge-stream@2.0.0: - resolution: - { - integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, - } + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} micromatch@4.0.8: - resolution: - { - integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, - } - engines: { node: ">=8.6" } + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} miller-rabin@4.0.1: - resolution: - { - integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==, - } + resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==} hasBin: true mime-db@1.52.0: - resolution: - { - integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} mime-types@2.1.35: - resolution: - { - integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} mimic-fn@2.1.0: - resolution: - { - integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} min-indent@1.0.1: - resolution: - { - integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} minimalistic-assert@1.0.1: - resolution: - { - integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==, - } + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} minimalistic-crypto-utils@1.0.1: - resolution: - { - integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==, - } + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} minimatch@3.0.5: - resolution: - { - integrity: sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==, - } + resolution: {integrity: sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==} minimatch@3.1.2: - resolution: - { - integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, - } + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} minimatch@5.1.6: - resolution: - { - integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} minimatch@8.0.4: - resolution: - { - integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==, - } - engines: { node: ">=16 || 14 >=14.17" } + resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} + engines: {node: '>=16 || 14 >=14.17'} minimatch@9.0.3: - resolution: - { - integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==, - } - engines: { node: ">=16 || 14 >=14.17" } + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} minimatch@9.0.5: - resolution: - { - integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==, - } - engines: { node: ">=16 || 14 >=14.17" } + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} minimist-options@4.1.0: - resolution: - { - integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} minimist@1.2.8: - resolution: - { - integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, - } + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} minipass-collect@2.0.1: - resolution: - { - integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==, - } - engines: { node: ">=16 || 14 >=14.17" } + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} minipass-fetch@3.0.5: - resolution: - { - integrity: sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} minipass-flush@1.0.5: - resolution: - { - integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} minipass-pipeline@1.2.4: - resolution: - { - integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} minipass-sized@1.0.3: - resolution: - { - integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} minipass@3.3.6: - resolution: - { - integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} minipass@4.2.8: - resolution: - { - integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} + engines: {node: '>=8'} minipass@5.0.0: - resolution: - { - integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} minipass@7.1.2: - resolution: - { - integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==, - } - engines: { node: ">=16 || 14 >=14.17" } + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} minizlib@2.1.2: - resolution: - { - integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} mkdirp-classic@0.5.3: - resolution: - { - integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==, - } + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} mkdirp@1.0.4: - resolution: - { - integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} hasBin: true modify-values@1.0.1: - resolution: - { - integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} + engines: {node: '>=0.10.0'} module-deps@6.2.3: - resolution: - { - integrity: sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==} + engines: {node: '>= 0.8.0'} hasBin: true ms@2.1.3: - resolution: - { - integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, - } + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} muggle-string@0.3.1: - resolution: - { - integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==, - } + resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} multimatch@5.0.0: - resolution: - { - integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==} + engines: {node: '>=10'} mute-stream@0.0.8: - resolution: - { - integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==, - } + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} mute-stream@1.0.0: - resolution: - { - integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} nanoid@3.3.11: - resolution: - { - integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, - } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true natural-compare@1.4.0: - resolution: - { - integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, - } + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} negotiator@0.6.4: - resolution: - { - integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==, - } - engines: { node: ">= 0.6" } + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} neo-async@2.6.2: - resolution: - { - integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==, - } + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} node-fetch@2.6.7: - resolution: - { - integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==, - } - engines: { node: 4.x || >=6.0.0 } + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} peerDependencies: encoding: ^0.1.0 peerDependenciesMeta: @@ -4397,1476 +3025,906 @@ packages: optional: true node-gyp@10.3.1: - resolution: - { - integrity: sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==} + engines: {node: ^16.14.0 || >=18.0.0} hasBin: true node-int64@0.4.0: - resolution: - { - integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==, - } + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} node-machine-id@1.1.12: - resolution: - { - integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==, - } + resolution: {integrity: sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==} node-releases@2.0.19: - resolution: - { - integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==, - } + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} noms@0.0.0: - resolution: - { - integrity: sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==, - } + resolution: {integrity: sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==} nopt@7.2.1: - resolution: - { - integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true normalize-package-data@2.5.0: - resolution: - { - integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==, - } + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} normalize-package-data@3.0.3: - resolution: - { - integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} normalize-package-data@6.0.2: - resolution: - { - integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} + engines: {node: ^16.14.0 || >=18.0.0} normalize-path@3.0.0: - resolution: - { - integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} npm-bundled@3.0.1: - resolution: - { - integrity: sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} npm-install-checks@6.3.0: - resolution: - { - integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} npm-normalize-package-bin@3.0.1: - resolution: - { - integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} npm-package-arg@11.0.2: - resolution: - { - integrity: sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==} + engines: {node: ^16.14.0 || >=18.0.0} npm-packlist@8.0.2: - resolution: - { - integrity: sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} npm-pick-manifest@9.1.0: - resolution: - { - integrity: sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==} + engines: {node: ^16.14.0 || >=18.0.0} npm-registry-fetch@17.1.0: - resolution: - { - integrity: sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==} + engines: {node: ^16.14.0 || >=18.0.0} npm-run-path@4.0.1: - resolution: - { - integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nwsapi@2.2.24: + resolution: {integrity: sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==} nx@20.8.2: - resolution: - { - integrity: sha512-mDKpbH3vEpUFDx0rrLh+tTqLq1PYU8KiD/R7OVZGd1FxQxghx2HOl32MiqNsfPcw6AvKlXhslbwIESV+N55FLQ==, - } + resolution: {integrity: sha512-mDKpbH3vEpUFDx0rrLh+tTqLq1PYU8KiD/R7OVZGd1FxQxghx2HOl32MiqNsfPcw6AvKlXhslbwIESV+N55FLQ==} hasBin: true peerDependencies: - "@swc-node/register": ^1.8.0 - "@swc/core": ^1.3.85 + '@swc-node/register': ^1.8.0 + '@swc/core': ^1.3.85 peerDependenciesMeta: - "@swc-node/register": + '@swc-node/register': optional: true - "@swc/core": + '@swc/core': optional: true object-inspect@1.13.4: - resolution: - { - integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} object-keys@1.1.1: - resolution: - { - integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} object.assign@4.1.7: - resolution: - { - integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} once@1.4.0: - resolution: - { - integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, - } + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} onetime@5.1.2: - resolution: - { - integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} open@8.4.2: - resolution: - { - integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} ora@5.3.0: - resolution: - { - integrity: sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g==} + engines: {node: '>=10'} ora@5.4.1: - resolution: - { - integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} os-browserify@0.3.0: - resolution: - { - integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==, - } + resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} os-tmpdir@1.0.2: - resolution: - { - integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} p-finally@1.0.0: - resolution: - { - integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} p-limit@1.3.0: - resolution: - { - integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} p-limit@2.3.0: - resolution: - { - integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} p-limit@3.1.0: - resolution: - { - integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} p-locate@2.0.0: - resolution: - { - integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} p-locate@4.1.0: - resolution: - { - integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} p-map-series@2.1.0: - resolution: - { - integrity: sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-RpYIIK1zXSNEOdwxcfe7FdvGcs7+y5n8rifMhMNWvaxRNMPINJHF5GDeuVxWqnfrcHPSCnp7Oo5yNXHId9Av2Q==} + engines: {node: '>=8'} p-map@4.0.0: - resolution: - { - integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} p-pipe@3.1.0: - resolution: - { - integrity: sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==} + engines: {node: '>=8'} p-queue@6.6.2: - resolution: - { - integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} p-reduce@2.1.0: - resolution: - { - integrity: sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==} + engines: {node: '>=8'} p-timeout@3.2.0: - resolution: - { - integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} p-try@1.0.0: - resolution: - { - integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} p-try@2.2.0: - resolution: - { - integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} p-waterfall@2.1.1: - resolution: - { - integrity: sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-RRTnDb2TBG/epPRI2yYXsimO0v3BXC8Yd3ogr1545IaqKK17VGhbWVeGGN+XfCm/08OK8635nH31c8bATkHuSw==} + engines: {node: '>=8'} package-json-from-dist@1.0.1: - resolution: - { - integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==, - } + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} pacote@18.0.6: - resolution: - { - integrity: sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==} + engines: {node: ^16.14.0 || >=18.0.0} hasBin: true pako@1.0.11: - resolution: - { - integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==, - } + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} parent-module@1.0.1: - resolution: - { - integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} parents@1.0.1: - resolution: - { - integrity: sha512-mXKF3xkoUt5td2DoxpLmtOmZvko9VfFpwRwkKDHSNvgmpLAeBo18YDhcPbBzJq+QLCHMbGOfzia2cX4U+0v9Mg==, - } + resolution: {integrity: sha512-mXKF3xkoUt5td2DoxpLmtOmZvko9VfFpwRwkKDHSNvgmpLAeBo18YDhcPbBzJq+QLCHMbGOfzia2cX4U+0v9Mg==} parse-asn1@5.1.7: - resolution: - { - integrity: sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==, - } - engines: { node: ">= 0.10" } + resolution: {integrity: sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==} + engines: {node: '>= 0.10'} parse-conflict-json@3.0.1: - resolution: - { - integrity: sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} parse-json@4.0.0: - resolution: - { - integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} parse-json@5.2.0: - resolution: - { - integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} parse-path@7.1.0: - resolution: - { - integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==, - } + resolution: {integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==} parse-url@8.1.0: - resolution: - { - integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==, - } + resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} path-browserify@1.0.1: - resolution: - { - integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==, - } + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} path-exists@3.0.0: - resolution: - { - integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} path-exists@4.0.0: - resolution: - { - integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} path-is-absolute@1.0.1: - resolution: - { - integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} path-key@3.1.1: - resolution: - { - integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} path-parse@1.0.7: - resolution: - { - integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, - } + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} path-platform@0.11.15: - resolution: - { - integrity: sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==, - } - engines: { node: ">= 0.8.0" } + resolution: {integrity: sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==} + engines: {node: '>= 0.8.0'} path-scurry@1.11.1: - resolution: - { - integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==, - } - engines: { node: ">=16 || 14 >=14.18" } + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} path-type@3.0.0: - resolution: - { - integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} path-type@4.0.0: - resolution: - { - integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} pbkdf2@3.1.3: - resolution: - { - integrity: sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==, - } - engines: { node: ">=0.12" } + resolution: {integrity: sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==} + engines: {node: '>=0.12'} + + peggy@5.1.0: + resolution: {integrity: sha512-IEo5aYRZ2kXH4Qby06cjtL114PZnwLoTiA41vUmg2vPZgANn+c87m5BUurhuDr5/cu758ZlpgsAfBVx+hhO5+w==} + engines: {node: '>=20'} + hasBin: true picocolors@1.1.1: - resolution: - { - integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, - } + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: - resolution: - { - integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, - } - engines: { node: ">=8.6" } + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} picomatch@4.0.2: - resolution: - { - integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} pify@2.3.0: - resolution: - { - integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} pify@3.0.0: - resolution: - { - integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} pify@4.0.1: - resolution: - { - integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} pify@5.0.0: - resolution: - { - integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} + engines: {node: '>=10'} pirates@4.0.7: - resolution: - { - integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} pkg-dir@4.2.0: - resolution: - { - integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + playwright-core@1.62.1: + resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} + engines: {node: '>=20'} + hasBin: true + + playwright@1.62.1: + resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==} + engines: {node: '>=20'} + hasBin: true possible-typed-array-names@1.1.0: - resolution: - { - integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} postcss-selector-parser@6.1.2: - resolution: - { - integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} postcss@8.5.6: - resolution: - { - integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, - } - engines: { node: ^10 || ^12 || >=14 } + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} prettier@3.6.2: - resolution: - { - integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} hasBin: true pretty-format@29.7.0: - resolution: - { - integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==, - } - engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} proc-log@4.2.0: - resolution: - { - integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} process-nextick-args@2.0.1: - resolution: - { - integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==, - } + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} process@0.11.10: - resolution: - { - integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==, - } - engines: { node: ">= 0.6.0" } + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} proggy@2.0.0: - resolution: - { - integrity: sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} promise-all-reject-late@1.0.1: - resolution: - { - integrity: sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==, - } + resolution: {integrity: sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==} promise-call-limit@3.0.2: - resolution: - { - integrity: sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==, - } + resolution: {integrity: sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==} promise-inflight@1.0.1: - resolution: - { - integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==, - } + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} peerDependencies: - bluebird: "*" + bluebird: '*' peerDependenciesMeta: bluebird: optional: true promise-retry@2.0.1: - resolution: - { - integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} prompts@2.4.2: - resolution: - { - integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} promzard@1.0.2: - resolution: - { - integrity: sha512-2FPputGL+mP3jJ3UZg/Dl9YOkovB7DX0oOr+ck5QbZ5MtORtds8k/BZdn+02peDLI8/YWbmzx34k5fA+fHvCVQ==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-2FPputGL+mP3jJ3UZg/Dl9YOkovB7DX0oOr+ck5QbZ5MtORtds8k/BZdn+02peDLI8/YWbmzx34k5fA+fHvCVQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} protocols@2.0.2: - resolution: - { - integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==, - } + resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} proxy-from-env@1.1.0: - resolution: - { - integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==, - } + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} public-encrypt@4.0.3: - resolution: - { - integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==, - } + resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} punycode@1.4.1: - resolution: - { - integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==, - } + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} pure-rand@6.1.0: - resolution: - { - integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==, - } + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} qs@6.14.0: - resolution: - { - integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==, - } - engines: { node: ">=0.6" } + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} querystring-es3@0.2.1: - resolution: - { - integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==, - } - engines: { node: ">=0.4.x" } + resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} + engines: {node: '>=0.4.x'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} quick-lru@4.0.1: - resolution: - { - integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} randombytes@2.1.0: - resolution: - { - integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==, - } + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} randomfill@1.0.4: - resolution: - { - integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==, - } + resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==} react-dom@19.1.0: - resolution: - { - integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==, - } + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: react: ^19.1.0 react-is@18.3.1: - resolution: - { - integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==, - } + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} react@18.3.1: - resolution: - { - integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} react@19.1.0: - resolution: - { - integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} read-cmd-shim@4.0.0: - resolution: - { - integrity: sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} read-only-stream@2.0.0: - resolution: - { - integrity: sha512-3ALe0bjBVZtkdWKIcThYpQCLbBMd/+Tbh2CDSrAIDO3UsZ4Xs+tnyjv2MjCOMMgBG+AsUOeuP1cgtY1INISc8w==, - } + resolution: {integrity: sha512-3ALe0bjBVZtkdWKIcThYpQCLbBMd/+Tbh2CDSrAIDO3UsZ4Xs+tnyjv2MjCOMMgBG+AsUOeuP1cgtY1INISc8w==} read-package-json-fast@3.0.2: - resolution: - { - integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} read-pkg-up@3.0.0: - resolution: - { - integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} + engines: {node: '>=4'} read-pkg-up@7.0.1: - resolution: - { - integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} read-pkg@3.0.0: - resolution: - { - integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} read-pkg@5.2.0: - resolution: - { - integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} read@3.0.1: - resolution: - { - integrity: sha512-SLBrDU/Srs/9EoWhU5GdbAoxG1GzpQHo/6qiGItaoLJ1thmYpcNIM1qISEUvyHBzfGlWIyd6p2DNi1oV1VmAuw==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-SLBrDU/Srs/9EoWhU5GdbAoxG1GzpQHo/6qiGItaoLJ1thmYpcNIM1qISEUvyHBzfGlWIyd6p2DNi1oV1VmAuw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} readable-stream@1.0.34: - resolution: - { - integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==, - } + resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} readable-stream@2.3.8: - resolution: - { - integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==, - } + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} readable-stream@3.6.2: - resolution: - { - integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} redent@3.0.0: - resolution: - { - integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} require-directory@2.1.1: - resolution: - { - integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} resolve-cwd@3.0.0: - resolution: - { - integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} resolve-from@4.0.0: - resolution: - { - integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} resolve-from@5.0.0: - resolution: - { - integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} resolve.exports@2.0.3: - resolution: - { - integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} resolve@1.22.10: - resolution: - { - integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} hasBin: true restore-cursor@3.1.0: - resolution: - { - integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} retry@0.12.0: - resolution: - { - integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==, - } - engines: { node: ">= 4" } + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} rimraf@4.4.1: - resolution: - { - integrity: sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==} + engines: {node: '>=14'} hasBin: true rimraf@5.0.10: - resolution: - { - integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==, - } + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true ripemd160@2.0.1: - resolution: - { - integrity: sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==, - } + resolution: {integrity: sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==} ripemd160@2.0.2: - resolution: - { - integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==, - } + resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} + + rollup@4.62.4: + resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true run-async@2.4.1: - resolution: - { - integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==, - } - engines: { node: ">=0.12.0" } + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} rxjs@7.8.2: - resolution: - { - integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==, - } + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} safe-buffer@5.1.2: - resolution: - { - integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==, - } + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} safe-buffer@5.2.1: - resolution: - { - integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, - } + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} safe-regex-test@1.1.0: - resolution: - { - integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} safer-buffer@2.1.2: - resolution: - { - integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, - } + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} scheduler@0.26.0: - resolution: - { - integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==, - } + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} semver@5.7.2: - resolution: - { - integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==, - } + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true semver@6.3.1: - resolution: - { - integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, - } + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true semver@7.7.2: - resolution: - { - integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} hasBin: true set-blocking@2.0.0: - resolution: - { - integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==, - } + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} set-function-length@1.2.2: - resolution: - { - integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} sha.js@2.4.12: - resolution: - { - integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==, - } - engines: { node: ">= 0.10" } + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} hasBin: true shallow-clone@3.0.1: - resolution: - { - integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} shasum-object@1.0.0: - resolution: - { - integrity: sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==, - } + resolution: {integrity: sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg==} shebang-command@2.0.0: - resolution: - { - integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} shebang-regex@3.0.0: - resolution: - { - integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} shell-quote@1.8.3: - resolution: - { - integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} side-channel-list@1.0.0: - resolution: - { - integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} side-channel-map@1.0.1: - resolution: - { - integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} side-channel-weakmap@1.0.2: - resolution: - { - integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} side-channel@1.1.0: - resolution: - { - integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} signal-exit@3.0.7: - resolution: - { - integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, - } + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} signal-exit@4.1.0: - resolution: - { - integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, - } - engines: { node: ">=14" } + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} sigstore@2.3.1: - resolution: - { - integrity: sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==} + engines: {node: ^16.14.0 || >=18.0.0} simple-concat@1.0.1: - resolution: - { - integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==, - } + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} sisteransi@1.0.5: - resolution: - { - integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==, - } + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} slash@3.0.0: - resolution: - { - integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} smart-buffer@4.2.0: - resolution: - { - integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==, - } - engines: { node: ">= 6.0.0", npm: ">= 3.0.0" } + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} socks-proxy-agent@8.0.5: - resolution: - { - integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==, - } - engines: { node: ">= 14" } + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} socks@2.8.5: - resolution: - { - integrity: sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==, - } - engines: { node: ">= 10.0.0", npm: ">= 3.0.0" } + resolution: {integrity: sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} sort-keys@2.0.0: - resolution: - { - integrity: sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-/dPCrG1s3ePpWm6yBbxZq5Be1dXGLyLn9Z791chDC3NFrpkVbWGzkBwPN1knaciexFXgRJ7hzdnwZ4stHSDmjg==} + engines: {node: '>=4'} + + source-map-generator@2.0.6: + resolution: {integrity: sha512-IlassDs1Ve8nV6uyQZXF9kdkJpVKnMte2JZQXu13M0A5zwc+vu6+LNHfmxsHBMDtoZE21RHiKI0/xvpecZRCNg==} + engines: {node: '>=20'} source-map-js@1.2.1: - resolution: - { - integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} source-map-support@0.5.13: - resolution: - { - integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==, - } + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} source-map@0.5.7: - resolution: - { - integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} source-map@0.6.1: - resolution: - { - integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} spdx-correct@3.2.0: - resolution: - { - integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==, - } + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} spdx-exceptions@2.5.0: - resolution: - { - integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==, - } + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} spdx-expression-parse@3.0.1: - resolution: - { - integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==, - } + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} spdx-license-ids@3.0.21: - resolution: - { - integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==, - } + resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} split2@3.2.2: - resolution: - { - integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==, - } + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} split@1.0.1: - resolution: - { - integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==, - } + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} sprintf-js@1.0.3: - resolution: - { - integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, - } + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} sprintf-js@1.1.3: - resolution: - { - integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==, - } + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} ssri@10.0.6: - resolution: - { - integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} stack-utils@2.0.6: - resolution: - { - integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} stream-browserify@3.0.0: - resolution: - { - integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==, - } + resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} stream-combiner2@1.1.1: - resolution: - { - integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==, - } + resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} stream-http@3.2.0: - resolution: - { - integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==, - } + resolution: {integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==} stream-splicer@2.0.1: - resolution: - { - integrity: sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==, - } + resolution: {integrity: sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==} string-length@4.0.2: - resolution: - { - integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} string-width@4.2.3: - resolution: - { - integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} string-width@5.1.2: - resolution: - { - integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} string_decoder@0.10.31: - resolution: - { - integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==, - } + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} string_decoder@1.1.1: - resolution: - { - integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==, - } + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} string_decoder@1.3.0: - resolution: - { - integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, - } + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} strip-ansi@6.0.1: - resolution: - { - integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} strip-ansi@7.1.0: - resolution: - { - integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} strip-bom@3.0.0: - resolution: - { - integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} strip-bom@4.0.0: - resolution: - { - integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} strip-final-newline@2.0.0: - resolution: - { - integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} strip-indent@3.0.0: - resolution: - { - integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} strip-json-comments@3.1.1: - resolution: - { - integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} subarg@1.0.0: - resolution: - { - integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==, - } + resolution: {integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==} supports-color@7.2.0: - resolution: - { - integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} supports-color@8.1.1: - resolution: - { - integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} supports-preserve-symlinks-flag@1.0.0: - resolution: - { - integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} syntax-error@1.4.0: - resolution: - { - integrity: sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==, - } + resolution: {integrity: sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==} tar-stream@2.2.0: - resolution: - { - integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} tar@6.2.1: - resolution: - { - integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me temp-dir@1.0.0: - resolution: - { - integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==} + engines: {node: '>=4'} test-exclude@6.0.0: - resolution: - { - integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} text-extensions@1.9.0: - resolution: - { - integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==, - } - engines: { node: ">=0.10" } + resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} + engines: {node: '>=0.10'} through2@2.0.5: - resolution: - { - integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==, - } + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} through@2.3.8: - resolution: - { - integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==, - } + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} timers-browserify@1.4.2: - resolution: - { - integrity: sha512-PIxwAupJZiYU4JmVZYwXp9FKsHMXb5h0ZEFyuXTAn8WLHOlcij+FEcbrvDsom1o5dr1YggEtFbECvGCW2sT53Q==, - } - engines: { node: ">=0.6.0" } + resolution: {integrity: sha512-PIxwAupJZiYU4JmVZYwXp9FKsHMXb5h0ZEFyuXTAn8WLHOlcij+FEcbrvDsom1o5dr1YggEtFbECvGCW2sT53Q==} + engines: {node: '>=0.6.0'} tinyglobby@0.2.12: - resolution: - { - integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==, - } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} + engines: {node: '>=12.0.0'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} tmp@0.0.33: - resolution: - { - integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==, - } - engines: { node: ">=0.6.0" } + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} tmp@0.2.3: - resolution: - { - integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==, - } - engines: { node: ">=14.14" } + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} tmpl@1.0.5: - resolution: - { - integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==, - } + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} to-buffer@1.2.1: - resolution: - { - integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==} + engines: {node: '>= 0.4'} to-regex-range@5.0.1: - resolution: - { - integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, - } - engines: { node: ">=8.0" } + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} tr46@0.0.3: - resolution: - { - integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, - } + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tr46@3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} treeverse@3.0.0: - resolution: - { - integrity: sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} trim-newlines@3.0.1: - resolution: - { - integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} ts-jest@29.4.0: - resolution: - { - integrity: sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==, - } - engines: { node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0 } + resolution: {integrity: sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - "@babel/core": ">=7.0.0-beta.0 <8" - "@jest/transform": ^29.0.0 || ^30.0.0 - "@jest/types": ^29.0.0 || ^30.0.0 + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 babel-jest: ^29.0.0 || ^30.0.0 - esbuild: "*" + esbuild: '*' jest: ^29.0.0 || ^30.0.0 jest-util: ^29.0.0 || ^30.0.0 - typescript: ">=4.3 <6" + typescript: '>=4.3 <6' peerDependenciesMeta: - "@babel/core": + '@babel/core': optional: true - "@jest/transform": + '@jest/transform': optional: true - "@jest/types": + '@jest/types': optional: true babel-jest: optional: true @@ -5876,742 +3934,687 @@ packages: optional: true ts-node@10.9.2: - resolution: - { - integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==, - } + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} hasBin: true peerDependencies: - "@swc/core": ">=1.2.50" - "@swc/wasm": ">=1.2.50" - "@types/node": "*" - typescript: ">=2.7" + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' peerDependenciesMeta: - "@swc/core": + '@swc/core': optional: true - "@swc/wasm": + '@swc/wasm': optional: true tsconfig-paths@4.2.0: - resolution: - { - integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} tslib@2.8.1: - resolution: - { - integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, - } + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} tty-browserify@0.0.1: - resolution: - { - integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==, - } + resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} tuf-js@2.2.1: - resolution: - { - integrity: sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==, - } - engines: { node: ^16.14.0 || >=18.0.0 } + resolution: {integrity: sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==} + engines: {node: ^16.14.0 || >=18.0.0} type-detect@4.0.8: - resolution: - { - integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} type-fest@0.18.1: - resolution: - { - integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} type-fest@0.21.3: - resolution: - { - integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} type-fest@0.4.1: - resolution: - { - integrity: sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==} + engines: {node: '>=6'} type-fest@0.6.0: - resolution: - { - integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} type-fest@0.8.1: - resolution: - { - integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} type-fest@4.41.0: - resolution: - { - integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==, - } - engines: { node: ">=16" } + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} typed-array-buffer@1.0.3: - resolution: - { - integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} typedarray@0.0.6: - resolution: - { - integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==, - } + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} typescript@5.8.3: - resolution: - { - integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==, - } - engines: { node: ">=14.17" } + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} hasBin: true uglify-js@3.19.3: - resolution: - { - integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==, - } - engines: { node: ">=0.8.0" } + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} hasBin: true umd@3.0.3: - resolution: - { - integrity: sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==, - } + resolution: {integrity: sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==} hasBin: true undeclared-identifiers@1.1.3: - resolution: - { - integrity: sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==, - } + resolution: {integrity: sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==} hasBin: true undici-types@6.21.0: - resolution: - { - integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, - } + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} unique-filename@3.0.0: - resolution: - { - integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} unique-slug@4.0.0: - resolution: - { - integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} universal-user-agent@6.0.1: - resolution: - { - integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==, - } + resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} universalify@2.0.1: - resolution: - { - integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==, - } - engines: { node: ">= 10.0.0" } + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} untildify@4.0.0: - resolution: - { - integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} upath@2.0.1: - resolution: - { - integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==} + engines: {node: '>=4'} update-browserslist-db@1.1.3: - resolution: - { - integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==, - } + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true peerDependencies: - browserslist: ">= 4.21.0" + browserslist: '>= 4.21.0' + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} url@0.11.4: - resolution: - { - integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} + engines: {node: '>= 0.4'} util-deprecate@1.0.2: - resolution: - { - integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, - } + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} util@0.10.4: - resolution: - { - integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==, - } + resolution: {integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==} util@0.12.5: - resolution: - { - integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==, - } + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} uuid@10.0.0: - resolution: - { - integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==, - } + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: - resolution: - { - integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==, - } + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} v8-to-istanbul@9.3.0: - resolution: - { - integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==, - } - engines: { node: ">=10.12.0" } + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} validate-npm-package-license@3.0.4: - resolution: - { - integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==, - } + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} validate-npm-package-name@5.0.1: - resolution: - { - integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + vite@7.3.6: + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true vm-browserify@1.1.2: - resolution: - { - integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==, - } + resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} vue-template-compiler@2.7.16: - resolution: - { - integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==, - } + resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} vue-tsc@1.8.27: - resolution: - { - integrity: sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==, - } + resolution: {integrity: sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==} hasBin: true peerDependencies: - typescript: "*" + typescript: '*' vue@3.5.17: - resolution: - { - integrity: sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==, - } + resolution: {integrity: sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==} peerDependencies: - typescript: "*" + typescript: '*' peerDependenciesMeta: typescript: optional: true + w3c-xmlserializer@4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} + walk-up-path@3.0.1: - resolution: - { - integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==, - } + resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} walker@1.0.8: - resolution: - { - integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==, - } + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} wcwidth@1.0.1: - resolution: - { - integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==, - } + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} webidl-conversions@3.0.1: - resolution: - { - integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, - } + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + + whatwg-url@11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} whatwg-url@5.0.0: - resolution: - { - integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, - } + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} which-typed-array@1.1.19: - resolution: - { - integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==, - } - engines: { node: ">= 0.4" } + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} which@2.0.2: - resolution: - { - integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, - } - engines: { node: ">= 8" } + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} hasBin: true which@4.0.0: - resolution: - { - integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==, - } - engines: { node: ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} hasBin: true wide-align@1.1.5: - resolution: - { - integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==, - } + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} wordwrap@1.0.0: - resolution: - { - integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==, - } + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} wrap-ansi@6.2.0: - resolution: - { - integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} wrap-ansi@7.0.0: - resolution: - { - integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} wrap-ansi@8.1.0: - resolution: - { - integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} wrappy@1.0.2: - resolution: - { - integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, - } + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} write-file-atomic@2.4.3: - resolution: - { - integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==, - } + resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} write-file-atomic@4.0.2: - resolution: - { - integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==, - } - engines: { node: ^12.13.0 || ^14.15.0 || >=16.0.0 } + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} write-file-atomic@5.0.1: - resolution: - { - integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==, - } - engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + write-json-file@3.2.0: + resolution: {integrity: sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==} + engines: {node: '>=6'} + + write-pkg@4.0.0: + resolution: {integrity: sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA==} + engines: {node: '>=8'} + + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true - write-json-file@3.2.0: - resolution: - { - integrity: sha512-3xZqT7Byc2uORAatYiP3DHUUAVEkNOswEWNs9H5KXiicRTvzYzYqKjYc4G7p+8pltvAw641lVByKVtMpf+4sYQ==, - } - engines: { node: ">=6" } + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} - write-pkg@4.0.0: - resolution: - { - integrity: sha512-v2UQ+50TNf2rNHJ8NyWttfm/EJUBWMJcx6ZTYZr6Qp52uuegWw/lBkCtCbnYZEmPRNL61m+u67dAmGxo+HTULA==, - } - engines: { node: ">=8" } + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} xtend@4.0.2: - resolution: - { - integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==, - } - engines: { node: ">=0.4" } + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} y18n@5.0.8: - resolution: - { - integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} yallist@3.1.1: - resolution: - { - integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, - } + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} yallist@4.0.0: - resolution: - { - integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, - } + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} yaml@1.10.2: - resolution: - { - integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==, - } - engines: { node: ">= 6" } + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} yaml@2.8.0: - resolution: - { - integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==, - } - engines: { node: ">= 14.6" } + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} hasBin: true yargs-parser@20.2.4: - resolution: - { - integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==} + engines: {node: '>=10'} yargs-parser@21.1.1: - resolution: - { - integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} yargs@16.2.0: - resolution: - { - integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} yargs@17.7.2: - resolution: - { - integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} yn@3.1.1: - resolution: - { - integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} yocto-queue@0.1.0: - resolution: - { - integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} snapshots: - "@ampproject/remapping@2.3.0": + + '@ampproject/remapping@2.3.0': dependencies: - "@jridgewell/gen-mapping": 0.3.12 - "@jridgewell/trace-mapping": 0.3.29 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 - "@babel/code-frame@7.27.1": + '@babel/code-frame@7.27.1': dependencies: - "@babel/helper-validator-identifier": 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 js-tokens: 4.0.0 picocolors: 1.1.1 - "@babel/compat-data@7.28.0": {} + '@babel/compat-data@7.28.0': {} - "@babel/core@7.28.0": + '@babel/core@7.28.0(supports-color@8.1.1)': dependencies: - "@ampproject/remapping": 2.3.0 - "@babel/code-frame": 7.27.1 - "@babel/generator": 7.28.0 - "@babel/helper-compilation-targets": 7.27.2 - "@babel/helper-module-transforms": 7.27.3(@babel/core@7.28.0) - "@babel/helpers": 7.27.6 - "@babel/parser": 7.28.0 - "@babel/template": 7.27.2 - "@babel/traverse": 7.28.0 - "@babel/types": 7.28.0 + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helpers': 7.27.6 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.0(supports-color@8.1.1) + '@babel/types': 7.28.0 convert-source-map: 2.0.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - "@babel/generator@7.27.5": + '@babel/generator@7.27.5': dependencies: - "@babel/parser": 7.28.0 - "@babel/types": 7.28.0 - "@jridgewell/gen-mapping": 0.3.12 - "@jridgewell/trace-mapping": 0.3.29 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 - "@babel/generator@7.28.0": + '@babel/generator@7.28.0': dependencies: - "@babel/parser": 7.28.0 - "@babel/types": 7.28.0 - "@jridgewell/gen-mapping": 0.3.12 - "@jridgewell/trace-mapping": 0.3.29 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 jsesc: 3.1.0 - "@babel/helper-compilation-targets@7.27.2": + '@babel/helper-compilation-targets@7.27.2': dependencies: - "@babel/compat-data": 7.28.0 - "@babel/helper-validator-option": 7.27.1 + '@babel/compat-data': 7.28.0 + '@babel/helper-validator-option': 7.27.1 browserslist: 4.25.1 lru-cache: 5.1.1 semver: 6.3.1 - "@babel/helper-globals@7.28.0": {} + '@babel/helper-globals@7.28.0': {} - "@babel/helper-module-imports@7.27.1": + '@babel/helper-module-imports@7.27.1(supports-color@8.1.1)': dependencies: - "@babel/traverse": 7.27.7 - "@babel/types": 7.28.0 + '@babel/traverse': 7.28.0(supports-color@8.1.1) + '@babel/types': 7.28.0 transitivePeerDependencies: - supports-color - "@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)": + '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-module-imports": 7.27.1 - "@babel/helper-validator-identifier": 7.27.1 - "@babel/traverse": 7.28.0 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-module-imports': 7.27.1(supports-color@8.1.1) + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - "@babel/helper-plugin-utils@7.27.1": {} + '@babel/helper-plugin-utils@7.27.1': {} - "@babel/helper-string-parser@7.27.1": {} + '@babel/helper-string-parser@7.27.1': {} - "@babel/helper-validator-identifier@7.27.1": {} + '@babel/helper-validator-identifier@7.27.1': {} - "@babel/helper-validator-option@7.27.1": {} + '@babel/helper-validator-option@7.27.1': {} - "@babel/helpers@7.27.6": + '@babel/helpers@7.27.6': dependencies: - "@babel/template": 7.27.2 - "@babel/types": 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.0 - "@babel/parser@7.27.7": + '@babel/parser@7.27.7': dependencies: - "@babel/types": 7.27.7 + '@babel/types': 7.27.7 - "@babel/parser@7.28.0": + '@babel/parser@7.28.0': dependencies: - "@babel/types": 7.28.0 + '@babel/types': 7.28.0 - "@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.0)": + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.0)": + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.0)": + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.0)": + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.0)": + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.0)": + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.0)": + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)": + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.0)": + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.0)": + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.0)": + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.0)": + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.0)": + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.0)": + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.0)": + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.0)": + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)": + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0(supports-color@8.1.1))': dependencies: - "@babel/core": 7.28.0 - "@babel/helper-plugin-utils": 7.27.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.27.1 - "@babel/runtime@7.27.6": + '@babel/runtime@7.27.6': optional: true - "@babel/template@7.27.2": + '@babel/template@7.27.2': dependencies: - "@babel/code-frame": 7.27.1 - "@babel/parser": 7.28.0 - "@babel/types": 7.28.0 - - "@babel/traverse@7.27.7": - dependencies: - "@babel/code-frame": 7.27.1 - "@babel/generator": 7.27.5 - "@babel/parser": 7.28.0 - "@babel/template": 7.27.2 - "@babel/types": 7.28.0 - debug: 4.4.1 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 - "@babel/traverse@7.28.0": + '@babel/traverse@7.28.0(supports-color@8.1.1)': dependencies: - "@babel/code-frame": 7.27.1 - "@babel/generator": 7.28.0 - "@babel/helper-globals": 7.28.0 - "@babel/parser": 7.28.0 - "@babel/template": 7.27.2 - "@babel/types": 7.28.0 - debug: 4.4.1 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.0 + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - "@babel/types@7.27.7": + '@babel/types@7.27.7': dependencies: - "@babel/helper-string-parser": 7.27.1 - "@babel/helper-validator-identifier": 7.27.1 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 - "@babel/types@7.28.0": + '@babel/types@7.28.0': dependencies: - "@babel/helper-string-parser": 7.27.1 - "@babel/helper-validator-identifier": 7.27.1 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 - "@bcoe/v8-coverage@0.2.3": {} + '@bcoe/v8-coverage@0.2.3': {} - "@cspotcode/source-map-support@0.8.1": + '@cspotcode/source-map-support@0.8.1': dependencies: - "@jridgewell/trace-mapping": 0.3.9 + '@jridgewell/trace-mapping': 0.3.9 - "@emnapi/core@1.4.3": + '@emnapi/core@1.4.3': dependencies: - "@emnapi/wasi-threads": 1.0.2 + '@emnapi/wasi-threads': 1.0.2 tslib: 2.8.1 - "@emnapi/runtime@1.4.3": + '@emnapi/runtime@1.4.3': dependencies: tslib: 2.8.1 - "@emnapi/wasi-threads@1.0.2": + '@emnapi/wasi-threads@1.0.2': dependencies: tslib: 2.8.1 - "@hutson/parse-repository-url@3.0.2": {} + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@hutson/parse-repository-url@3.0.2': {} - "@isaacs/cliui@8.0.2": + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 @@ -6620,9 +4623,9 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - "@isaacs/string-locale-compare@1.1.0": {} + '@isaacs/string-locale-compare@1.1.0': {} - "@istanbuljs/load-nyc-config@1.1.0": + '@istanbuljs/load-nyc-config@1.1.0': dependencies: camelcase: 5.3.1 find-up: 4.1.0 @@ -6630,40 +4633,40 @@ snapshots: js-yaml: 3.14.1 resolve-from: 5.0.0 - "@istanbuljs/schema@0.1.3": {} + '@istanbuljs/schema@0.1.3': {} - "@jest/console@29.7.0": + '@jest/console@29.7.0': dependencies: - "@jest/types": 29.6.3 - "@types/node": 20.19.4 + '@jest/types': 29.6.3 + '@types/node': 20.19.4 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 - "@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3))": + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3))': dependencies: - "@jest/console": 29.7.0 - "@jest/reporters": 29.7.0 - "@jest/test-result": 29.7.0 - "@jest/transform": 29.7.0 - "@jest/types": 29.6.3 - "@types/node": 20.19.4 + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0(supports-color@8.1.1) + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0(supports-color@8.1.1) + '@jest/types': 29.6.3 + '@types/node': 20.19.4 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) + jest-config: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 + jest-resolve-dependencies: 29.7.0(supports-color@8.1.1) + jest-runner: 29.7.0(supports-color@8.1.1) + jest-runtime: 29.7.0(supports-color@8.1.1) + jest-snapshot: 29.7.0(supports-color@8.1.1) jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 @@ -6676,60 +4679,60 @@ snapshots: - supports-color - ts-node - "@jest/environment@29.7.0": + '@jest/environment@29.7.0': dependencies: - "@jest/fake-timers": 29.7.0 - "@jest/types": 29.6.3 - "@types/node": 20.19.4 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.4 jest-mock: 29.7.0 - "@jest/expect-utils@29.7.0": + '@jest/expect-utils@29.7.0': dependencies: jest-get-type: 29.6.3 - "@jest/expect@29.7.0": + '@jest/expect@29.7.0(supports-color@8.1.1)': dependencies: expect: 29.7.0 - jest-snapshot: 29.7.0 + jest-snapshot: 29.7.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - "@jest/fake-timers@29.7.0": + '@jest/fake-timers@29.7.0': dependencies: - "@jest/types": 29.6.3 - "@sinonjs/fake-timers": 10.3.0 - "@types/node": 20.19.4 + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.19.4 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 - "@jest/globals@29.7.0": + '@jest/globals@29.7.0(supports-color@8.1.1)': dependencies: - "@jest/environment": 29.7.0 - "@jest/expect": 29.7.0 - "@jest/types": 29.6.3 + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0(supports-color@8.1.1) + '@jest/types': 29.6.3 jest-mock: 29.7.0 transitivePeerDependencies: - supports-color - "@jest/reporters@29.7.0": + '@jest/reporters@29.7.0(supports-color@8.1.1)': dependencies: - "@bcoe/v8-coverage": 0.2.3 - "@jest/console": 29.7.0 - "@jest/test-result": 29.7.0 - "@jest/transform": 29.7.0 - "@jest/types": 29.6.3 - "@jridgewell/trace-mapping": 0.3.29 - "@types/node": 20.19.4 + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0(supports-color@8.1.1) + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.29 + '@types/node': 20.19.4 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 glob: 7.2.3 graceful-fs: 4.2.11 istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.3 + istanbul-lib-instrument: 6.0.3(supports-color@8.1.1) istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 + istanbul-lib-source-maps: 4.0.1(supports-color@8.1.1) istanbul-reports: 3.1.7 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -6741,36 +4744,36 @@ snapshots: transitivePeerDependencies: - supports-color - "@jest/schemas@29.6.3": + '@jest/schemas@29.6.3': dependencies: - "@sinclair/typebox": 0.27.8 + '@sinclair/typebox': 0.27.8 - "@jest/source-map@29.6.3": + '@jest/source-map@29.6.3': dependencies: - "@jridgewell/trace-mapping": 0.3.29 + '@jridgewell/trace-mapping': 0.3.29 callsites: 3.1.0 graceful-fs: 4.2.11 - "@jest/test-result@29.7.0": + '@jest/test-result@29.7.0': dependencies: - "@jest/console": 29.7.0 - "@jest/types": 29.6.3 - "@types/istanbul-lib-coverage": 2.0.6 + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.2 - "@jest/test-sequencer@29.7.0": + '@jest/test-sequencer@29.7.0': dependencies: - "@jest/test-result": 29.7.0 + '@jest/test-result': 29.7.0 graceful-fs: 4.2.11 jest-haste-map: 29.7.0 slash: 3.0.0 - "@jest/transform@29.7.0": + '@jest/transform@29.7.0(supports-color@8.1.1)': dependencies: - "@babel/core": 7.28.0 - "@jest/types": 29.6.3 - "@jridgewell/trace-mapping": 0.3.29 - babel-plugin-istanbul: 6.1.1 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.29 + babel-plugin-istanbul: 6.1.1(supports-color@8.1.1) chalk: 4.1.2 convert-source-map: 2.0.0 fast-json-stable-stringify: 2.1.0 @@ -6785,42 +4788,42 @@ snapshots: transitivePeerDependencies: - supports-color - "@jest/types@29.6.3": + '@jest/types@29.6.3': dependencies: - "@jest/schemas": 29.6.3 - "@types/istanbul-lib-coverage": 2.0.6 - "@types/istanbul-reports": 3.0.4 - "@types/node": 20.19.4 - "@types/yargs": 17.0.33 + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.19.4 + '@types/yargs': 17.0.33 chalk: 4.1.2 - "@jridgewell/gen-mapping@0.3.12": + '@jridgewell/gen-mapping@0.3.12': dependencies: - "@jridgewell/sourcemap-codec": 1.5.4 - "@jridgewell/trace-mapping": 0.3.29 + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.29 - "@jridgewell/resolve-uri@3.1.2": {} + '@jridgewell/resolve-uri@3.1.2': {} - "@jridgewell/sourcemap-codec@1.5.4": {} + '@jridgewell/sourcemap-codec@1.5.4': {} - "@jridgewell/trace-mapping@0.3.29": + '@jridgewell/trace-mapping@0.3.29': dependencies: - "@jridgewell/resolve-uri": 3.1.2 - "@jridgewell/sourcemap-codec": 1.5.4 + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 - "@jridgewell/trace-mapping@0.3.9": + '@jridgewell/trace-mapping@0.3.9': dependencies: - "@jridgewell/resolve-uri": 3.1.2 - "@jridgewell/sourcemap-codec": 1.5.4 + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 - "@lerna/create@8.2.3(babel-plugin-macros@3.1.0)(encoding@0.1.13)(typescript@5.8.3)": + '@lerna/create@8.2.3(babel-plugin-macros@3.1.0)(debug@4.4.1(supports-color@8.1.1))(encoding@0.1.13)(supports-color@8.1.1)(typescript@5.8.3)': dependencies: - "@npmcli/arborist": 7.5.4 - "@npmcli/package-json": 5.2.0 - "@npmcli/run-script": 8.1.0 - "@nx/devkit": 20.8.2(nx@20.8.2) - "@octokit/plugin-enterprise-rest": 6.0.1 - "@octokit/rest": 20.1.2 + '@npmcli/arborist': 7.5.4(supports-color@8.1.1) + '@npmcli/package-json': 5.2.0 + '@npmcli/run-script': 8.1.0(supports-color@8.1.1) + '@nx/devkit': 20.8.2(nx@20.8.2(debug@4.4.1(supports-color@8.1.1))) + '@octokit/plugin-enterprise-rest': 6.0.1 + '@octokit/rest': 20.1.2 aproba: 2.0.0 byte-size: 8.1.1 chalk: 4.1.0 @@ -6846,7 +4849,7 @@ snapshots: is-ci: 3.0.1 is-stream: 2.0.0 js-yaml: 4.1.0 - libnpmpublish: 9.0.9 + libnpmpublish: 9.0.9(supports-color@8.1.1) load-json-file: 6.2.0 lodash: 4.17.21 make-dir: 4.0.0 @@ -6855,13 +4858,13 @@ snapshots: node-fetch: 2.6.7(encoding@0.1.13) npm-package-arg: 11.0.2 npm-packlist: 8.0.2 - npm-registry-fetch: 17.1.0 - nx: 20.8.2 + npm-registry-fetch: 17.1.0(supports-color@8.1.1) + nx: 20.8.2(debug@4.4.1(supports-color@8.1.1)) p-map: 4.0.0 p-map-series: 2.1.0 p-queue: 6.6.2 p-reduce: 2.1.0 - pacote: 18.0.6 + pacote: 18.0.6(supports-color@8.1.1) pify: 5.0.0 read-cmd-shim: 4.0.0 resolve-from: 5.0.0 @@ -6886,8 +4889,8 @@ snapshots: yargs: 17.7.2 yargs-parser: 21.1.1 transitivePeerDependencies: - - "@swc-node/register" - - "@swc/core" + - '@swc-node/register' + - '@swc/core' - babel-plugin-macros - bluebird - debug @@ -6895,35 +4898,38 @@ snapshots: - supports-color - typescript - "@napi-rs/wasm-runtime@0.2.4": + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + + '@napi-rs/wasm-runtime@0.2.4': dependencies: - "@emnapi/core": 1.4.3 - "@emnapi/runtime": 1.4.3 - "@tybys/wasm-util": 0.9.0 + '@emnapi/core': 1.4.3 + '@emnapi/runtime': 1.4.3 + '@tybys/wasm-util': 0.9.0 - "@npmcli/agent@2.2.2": + '@npmcli/agent@2.2.2(supports-color@8.1.1)': dependencies: agent-base: 7.1.3 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 + http-proxy-agent: 7.0.2(supports-color@8.1.1) + https-proxy-agent: 7.0.6(supports-color@8.1.1) lru-cache: 10.4.3 - socks-proxy-agent: 8.0.5 + socks-proxy-agent: 8.0.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color - "@npmcli/arborist@7.5.4": - dependencies: - "@isaacs/string-locale-compare": 1.1.0 - "@npmcli/fs": 3.1.1 - "@npmcli/installed-package-contents": 2.1.0 - "@npmcli/map-workspaces": 3.0.6 - "@npmcli/metavuln-calculator": 7.1.1 - "@npmcli/name-from-folder": 2.0.0 - "@npmcli/node-gyp": 3.0.0 - "@npmcli/package-json": 5.2.0 - "@npmcli/query": 3.1.0 - "@npmcli/redact": 2.0.1 - "@npmcli/run-script": 8.1.0 + '@npmcli/arborist@7.5.4(supports-color@8.1.1)': + dependencies: + '@isaacs/string-locale-compare': 1.1.0 + '@npmcli/fs': 3.1.1 + '@npmcli/installed-package-contents': 2.1.0 + '@npmcli/map-workspaces': 3.0.6 + '@npmcli/metavuln-calculator': 7.1.1(supports-color@8.1.1) + '@npmcli/name-from-folder': 2.0.0 + '@npmcli/node-gyp': 3.0.0 + '@npmcli/package-json': 5.2.0 + '@npmcli/query': 3.1.0 + '@npmcli/redact': 2.0.1 + '@npmcli/run-script': 8.1.0(supports-color@8.1.1) bin-links: 4.0.4 cacache: 18.0.4 common-ancestor-path: 1.0.1 @@ -6936,8 +4942,8 @@ snapshots: npm-install-checks: 6.3.0 npm-package-arg: 11.0.2 npm-pick-manifest: 9.1.0 - npm-registry-fetch: 17.1.0 - pacote: 18.0.6 + npm-registry-fetch: 17.1.0(supports-color@8.1.1) + pacote: 18.0.6(supports-color@8.1.1) parse-conflict-json: 3.0.1 proc-log: 4.2.0 proggy: 2.0.0 @@ -6952,13 +4958,13 @@ snapshots: - bluebird - supports-color - "@npmcli/fs@3.1.1": + '@npmcli/fs@3.1.1': dependencies: semver: 7.7.2 - "@npmcli/git@5.0.8": + '@npmcli/git@5.0.8': dependencies: - "@npmcli/promise-spawn": 7.0.2 + '@npmcli/promise-spawn': 7.0.2 ini: 4.1.3 lru-cache: 10.4.3 npm-pick-manifest: 9.1.0 @@ -6970,36 +4976,36 @@ snapshots: transitivePeerDependencies: - bluebird - "@npmcli/installed-package-contents@2.1.0": + '@npmcli/installed-package-contents@2.1.0': dependencies: npm-bundled: 3.0.1 npm-normalize-package-bin: 3.0.1 - "@npmcli/map-workspaces@3.0.6": + '@npmcli/map-workspaces@3.0.6': dependencies: - "@npmcli/name-from-folder": 2.0.0 + '@npmcli/name-from-folder': 2.0.0 glob: 10.4.5 minimatch: 9.0.5 read-package-json-fast: 3.0.2 - "@npmcli/metavuln-calculator@7.1.1": + '@npmcli/metavuln-calculator@7.1.1(supports-color@8.1.1)': dependencies: cacache: 18.0.4 json-parse-even-better-errors: 3.0.2 - pacote: 18.0.6 + pacote: 18.0.6(supports-color@8.1.1) proc-log: 4.2.0 semver: 7.7.2 transitivePeerDependencies: - bluebird - supports-color - "@npmcli/name-from-folder@2.0.0": {} + '@npmcli/name-from-folder@2.0.0': {} - "@npmcli/node-gyp@3.0.0": {} + '@npmcli/node-gyp@3.0.0': {} - "@npmcli/package-json@5.2.0": + '@npmcli/package-json@5.2.0': dependencies: - "@npmcli/git": 5.0.8 + '@npmcli/git': 5.0.8 glob: 10.4.5 hosted-git-info: 7.0.2 json-parse-even-better-errors: 3.0.2 @@ -7009,320 +5015,415 @@ snapshots: transitivePeerDependencies: - bluebird - "@npmcli/promise-spawn@7.0.2": + '@npmcli/promise-spawn@7.0.2': dependencies: which: 4.0.0 - "@npmcli/query@3.1.0": + '@npmcli/query@3.1.0': dependencies: postcss-selector-parser: 6.1.2 - "@npmcli/redact@2.0.1": {} + '@npmcli/redact@2.0.1': {} - "@npmcli/run-script@8.1.0": + '@npmcli/run-script@8.1.0(supports-color@8.1.1)': dependencies: - "@npmcli/node-gyp": 3.0.0 - "@npmcli/package-json": 5.2.0 - "@npmcli/promise-spawn": 7.0.2 - node-gyp: 10.3.1 + '@npmcli/node-gyp': 3.0.0 + '@npmcli/package-json': 5.2.0 + '@npmcli/promise-spawn': 7.0.2 + node-gyp: 10.3.1(supports-color@8.1.1) proc-log: 4.2.0 which: 4.0.0 transitivePeerDependencies: - bluebird - supports-color - "@nx/devkit@20.8.2(nx@20.8.2)": + '@nx/devkit@20.8.2(nx@20.8.2(debug@4.4.1(supports-color@8.1.1)))': dependencies: ejs: 3.1.10 enquirer: 2.3.6 ignore: 5.3.2 minimatch: 9.0.3 - nx: 20.8.2 + nx: 20.8.2(debug@4.4.1(supports-color@8.1.1)) semver: 7.7.2 tmp: 0.2.3 tslib: 2.8.1 yargs-parser: 21.1.1 - "@nx/nx-darwin-arm64@20.8.2": + '@nx/nx-darwin-arm64@20.8.2': optional: true - "@nx/nx-darwin-x64@20.8.2": + '@nx/nx-darwin-x64@20.8.2': optional: true - "@nx/nx-freebsd-x64@20.8.2": + '@nx/nx-freebsd-x64@20.8.2': optional: true - "@nx/nx-linux-arm-gnueabihf@20.8.2": + '@nx/nx-linux-arm-gnueabihf@20.8.2': optional: true - "@nx/nx-linux-arm64-gnu@20.8.2": + '@nx/nx-linux-arm64-gnu@20.8.2': optional: true - "@nx/nx-linux-arm64-musl@20.8.2": + '@nx/nx-linux-arm64-musl@20.8.2': optional: true - "@nx/nx-linux-x64-gnu@20.8.2": + '@nx/nx-linux-x64-gnu@20.8.2': optional: true - "@nx/nx-linux-x64-musl@20.8.2": + '@nx/nx-linux-x64-musl@20.8.2': optional: true - "@nx/nx-win32-arm64-msvc@20.8.2": + '@nx/nx-win32-arm64-msvc@20.8.2': optional: true - "@nx/nx-win32-x64-msvc@20.8.2": + '@nx/nx-win32-x64-msvc@20.8.2': optional: true - "@octokit/auth-token@4.0.0": {} + '@octokit/auth-token@4.0.0': {} - "@octokit/core@5.2.1": + '@octokit/core@5.2.1': dependencies: - "@octokit/auth-token": 4.0.0 - "@octokit/graphql": 7.1.1 - "@octokit/request": 8.4.1 - "@octokit/request-error": 5.1.1 - "@octokit/types": 13.10.0 + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.1.1 + '@octokit/request': 8.4.1 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 before-after-hook: 2.2.3 universal-user-agent: 6.0.1 - "@octokit/endpoint@9.0.6": + '@octokit/endpoint@9.0.6': dependencies: - "@octokit/types": 13.10.0 + '@octokit/types': 13.10.0 universal-user-agent: 6.0.1 - "@octokit/graphql@7.1.1": + '@octokit/graphql@7.1.1': dependencies: - "@octokit/request": 8.4.1 - "@octokit/types": 13.10.0 + '@octokit/request': 8.4.1 + '@octokit/types': 13.10.0 universal-user-agent: 6.0.1 - "@octokit/openapi-types@24.2.0": {} + '@octokit/openapi-types@24.2.0': {} - "@octokit/plugin-enterprise-rest@6.0.1": {} + '@octokit/plugin-enterprise-rest@6.0.1': {} - "@octokit/plugin-paginate-rest@11.4.4-cjs.2(@octokit/core@5.2.1)": + '@octokit/plugin-paginate-rest@11.4.4-cjs.2(@octokit/core@5.2.1)': dependencies: - "@octokit/core": 5.2.1 - "@octokit/types": 13.10.0 + '@octokit/core': 5.2.1 + '@octokit/types': 13.10.0 - "@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.1)": + '@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.1)': dependencies: - "@octokit/core": 5.2.1 + '@octokit/core': 5.2.1 - "@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1(@octokit/core@5.2.1)": + '@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1(@octokit/core@5.2.1)': dependencies: - "@octokit/core": 5.2.1 - "@octokit/types": 13.10.0 + '@octokit/core': 5.2.1 + '@octokit/types': 13.10.0 - "@octokit/request-error@5.1.1": + '@octokit/request-error@5.1.1': dependencies: - "@octokit/types": 13.10.0 + '@octokit/types': 13.10.0 deprecation: 2.3.1 once: 1.4.0 - "@octokit/request@8.4.1": + '@octokit/request@8.4.1': dependencies: - "@octokit/endpoint": 9.0.6 - "@octokit/request-error": 5.1.1 - "@octokit/types": 13.10.0 + '@octokit/endpoint': 9.0.6 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 universal-user-agent: 6.0.1 - "@octokit/rest@20.1.2": + '@octokit/rest@20.1.2': + dependencies: + '@octokit/core': 5.2.1 + '@octokit/plugin-paginate-rest': 11.4.4-cjs.2(@octokit/core@5.2.1) + '@octokit/plugin-request-log': 4.0.1(@octokit/core@5.2.1) + '@octokit/plugin-rest-endpoint-methods': 13.3.2-cjs.1(@octokit/core@5.2.1) + + '@octokit/types@13.10.0': dependencies: - "@octokit/core": 5.2.1 - "@octokit/plugin-paginate-rest": 11.4.4-cjs.2(@octokit/core@5.2.1) - "@octokit/plugin-request-log": 4.0.1(@octokit/core@5.2.1) - "@octokit/plugin-rest-endpoint-methods": 13.3.2-cjs.1(@octokit/core@5.2.1) + '@octokit/openapi-types': 24.2.0 - "@octokit/types@13.10.0": + '@peggyjs/from-mem@3.1.3': dependencies: - "@octokit/openapi-types": 24.2.0 + semver: 7.7.4 - "@pkgjs/parseargs@0.11.0": + '@pkgjs/parseargs@0.11.0': optional: true - "@sigstore/bundle@2.3.2": + '@playwright/test@1.62.1': dependencies: - "@sigstore/protobuf-specs": 0.3.3 + playwright: 1.62.1 - "@sigstore/core@1.1.0": {} + '@rollup/rollup-android-arm-eabi@4.62.4': + optional: true + + '@rollup/rollup-android-arm64@4.62.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.4': + optional: true + + '@rollup/rollup-darwin-x64@4.62.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.4': + optional: true + + '@sigstore/bundle@2.3.2': + dependencies: + '@sigstore/protobuf-specs': 0.3.3 + + '@sigstore/core@1.1.0': {} - "@sigstore/protobuf-specs@0.3.3": {} + '@sigstore/protobuf-specs@0.3.3': {} - "@sigstore/sign@2.3.2": + '@sigstore/sign@2.3.2(supports-color@8.1.1)': dependencies: - "@sigstore/bundle": 2.3.2 - "@sigstore/core": 1.1.0 - "@sigstore/protobuf-specs": 0.3.3 - make-fetch-happen: 13.0.1 + '@sigstore/bundle': 2.3.2 + '@sigstore/core': 1.1.0 + '@sigstore/protobuf-specs': 0.3.3 + make-fetch-happen: 13.0.1(supports-color@8.1.1) proc-log: 4.2.0 promise-retry: 2.0.1 transitivePeerDependencies: - supports-color - "@sigstore/tuf@2.3.4": + '@sigstore/tuf@2.3.4(supports-color@8.1.1)': dependencies: - "@sigstore/protobuf-specs": 0.3.3 - tuf-js: 2.2.1 + '@sigstore/protobuf-specs': 0.3.3 + tuf-js: 2.2.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - "@sigstore/verify@1.2.1": + '@sigstore/verify@1.2.1': dependencies: - "@sigstore/bundle": 2.3.2 - "@sigstore/core": 1.1.0 - "@sigstore/protobuf-specs": 0.3.3 + '@sigstore/bundle': 2.3.2 + '@sigstore/core': 1.1.0 + '@sigstore/protobuf-specs': 0.3.3 - "@sinclair/typebox@0.27.8": {} + '@sinclair/typebox@0.27.8': {} - "@sinonjs/commons@3.0.1": + '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 - "@sinonjs/fake-timers@10.3.0": + '@sinonjs/fake-timers@10.3.0': dependencies: - "@sinonjs/commons": 3.0.1 + '@sinonjs/commons': 3.0.1 - "@tsconfig/node10@1.0.11": {} + '@tootallnate/once@2.0.1': {} - "@tsconfig/node12@1.0.11": {} + '@tsconfig/node10@1.0.11': {} - "@tsconfig/node14@1.0.3": {} + '@tsconfig/node12@1.0.11': {} - "@tsconfig/node16@1.0.4": {} + '@tsconfig/node14@1.0.3': {} - "@tufjs/canonical-json@2.0.0": {} + '@tsconfig/node16@1.0.4': {} - "@tufjs/models@2.0.1": + '@tufjs/canonical-json@2.0.0': {} + + '@tufjs/models@2.0.1': dependencies: - "@tufjs/canonical-json": 2.0.0 + '@tufjs/canonical-json': 2.0.0 minimatch: 9.0.5 - "@tybys/wasm-util@0.9.0": + '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 - "@types/babel__core@7.20.5": + '@types/babel__core@7.20.5': dependencies: - "@babel/parser": 7.28.0 - "@babel/types": 7.28.0 - "@types/babel__generator": 7.27.0 - "@types/babel__template": 7.4.4 - "@types/babel__traverse": 7.20.7 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.7 - "@types/babel__generator@7.27.0": + '@types/babel__generator@7.27.0': dependencies: - "@babel/types": 7.28.0 + '@babel/types': 7.28.0 - "@types/babel__template@7.4.4": + '@types/babel__template@7.4.4': dependencies: - "@babel/parser": 7.28.0 - "@babel/types": 7.28.0 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 - "@types/babel__traverse@7.20.7": + '@types/babel__traverse@7.20.7': dependencies: - "@babel/types": 7.28.0 + '@babel/types': 7.28.0 + + '@types/estree@1.0.9': {} - "@types/graceful-fs@4.1.9": + '@types/graceful-fs@4.1.9': dependencies: - "@types/node": 20.19.4 + '@types/node': 20.19.4 - "@types/istanbul-lib-coverage@2.0.6": {} + '@types/istanbul-lib-coverage@2.0.6': {} - "@types/istanbul-lib-report@3.0.3": + '@types/istanbul-lib-report@3.0.3': dependencies: - "@types/istanbul-lib-coverage": 2.0.6 + '@types/istanbul-lib-coverage': 2.0.6 - "@types/istanbul-reports@3.0.4": + '@types/istanbul-reports@3.0.4': dependencies: - "@types/istanbul-lib-report": 3.0.3 + '@types/istanbul-lib-report': 3.0.3 - "@types/jest@29.5.14": + '@types/jest@29.5.14': dependencies: expect: 29.7.0 pretty-format: 29.7.0 - "@types/minimatch@3.0.5": {} + '@types/jsdom@20.0.1': + dependencies: + '@types/node': 20.19.4 + '@types/tough-cookie': 4.0.5 + parse5: 7.3.0 + + '@types/minimatch@3.0.5': {} - "@types/minimist@1.2.5": {} + '@types/minimist@1.2.5': {} - "@types/node@20.19.4": + '@types/node@20.19.4': dependencies: undici-types: 6.21.0 - "@types/normalize-package-data@2.4.4": {} + '@types/normalize-package-data@2.4.4': {} - "@types/parse-json@4.0.2": + '@types/parse-json@4.0.2': optional: true - "@types/prop-types@15.7.15": {} + '@types/prop-types@15.7.15': {} - "@types/react-dom@18.3.7(@types/react@18.3.23)": + '@types/react-dom@18.3.7(@types/react@18.3.23)': dependencies: - "@types/react": 18.3.23 + '@types/react': 18.3.23 - "@types/react@18.3.23": + '@types/react@18.3.23': dependencies: - "@types/prop-types": 15.7.15 + '@types/prop-types': 15.7.15 csstype: 3.1.3 - "@types/stack-utils@2.0.3": {} + '@types/stack-utils@2.0.3': {} - "@types/yargs-parser@21.0.3": {} + '@types/tough-cookie@4.0.5': {} - "@types/yargs@17.0.33": + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': dependencies: - "@types/yargs-parser": 21.0.3 + '@types/yargs-parser': 21.0.3 - "@volar/language-core@1.11.1": + '@volar/language-core@1.11.1': dependencies: - "@volar/source-map": 1.11.1 + '@volar/source-map': 1.11.1 - "@volar/source-map@1.11.1": + '@volar/source-map@1.11.1': dependencies: muggle-string: 0.3.1 - "@volar/typescript@1.11.1": + '@volar/typescript@1.11.1': dependencies: - "@volar/language-core": 1.11.1 + '@volar/language-core': 1.11.1 path-browserify: 1.0.1 - "@vue/compiler-core@3.5.17": + '@vue/compiler-core@3.5.17': dependencies: - "@babel/parser": 7.27.7 - "@vue/shared": 3.5.17 + '@babel/parser': 7.27.7 + '@vue/shared': 3.5.17 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.2.1 - "@vue/compiler-dom@3.5.17": + '@vue/compiler-dom@3.5.17': dependencies: - "@vue/compiler-core": 3.5.17 - "@vue/shared": 3.5.17 + '@vue/compiler-core': 3.5.17 + '@vue/shared': 3.5.17 - "@vue/compiler-sfc@3.5.17": + '@vue/compiler-sfc@3.5.17': dependencies: - "@babel/parser": 7.27.7 - "@vue/compiler-core": 3.5.17 - "@vue/compiler-dom": 3.5.17 - "@vue/compiler-ssr": 3.5.17 - "@vue/shared": 3.5.17 + '@babel/parser': 7.27.7 + '@vue/compiler-core': 3.5.17 + '@vue/compiler-dom': 3.5.17 + '@vue/compiler-ssr': 3.5.17 + '@vue/shared': 3.5.17 estree-walker: 2.0.2 magic-string: 0.30.17 postcss: 8.5.6 source-map-js: 1.2.1 - "@vue/compiler-ssr@3.5.17": + '@vue/compiler-ssr@3.5.17': dependencies: - "@vue/compiler-dom": 3.5.17 - "@vue/shared": 3.5.17 + '@vue/compiler-dom': 3.5.17 + '@vue/shared': 3.5.17 - "@vue/language-core@1.8.27(typescript@5.8.3)": + '@vue/language-core@1.8.27(typescript@5.8.3)': dependencies: - "@volar/language-core": 1.11.1 - "@volar/source-map": 1.11.1 - "@vue/compiler-dom": 3.5.17 - "@vue/shared": 3.5.17 + '@volar/language-core': 1.11.1 + '@volar/source-map': 1.11.1 + '@vue/compiler-dom': 3.5.17 + '@vue/shared': 3.5.17 computeds: 0.0.1 minimatch: 9.0.5 muggle-string: 0.3.1 @@ -7331,38 +5432,38 @@ snapshots: optionalDependencies: typescript: 5.8.3 - "@vue/reactivity@3.5.17": + '@vue/reactivity@3.5.17': dependencies: - "@vue/shared": 3.5.17 + '@vue/shared': 3.5.17 - "@vue/runtime-core@3.5.17": + '@vue/runtime-core@3.5.17': dependencies: - "@vue/reactivity": 3.5.17 - "@vue/shared": 3.5.17 + '@vue/reactivity': 3.5.17 + '@vue/shared': 3.5.17 - "@vue/runtime-dom@3.5.17": + '@vue/runtime-dom@3.5.17': dependencies: - "@vue/reactivity": 3.5.17 - "@vue/runtime-core": 3.5.17 - "@vue/shared": 3.5.17 + '@vue/reactivity': 3.5.17 + '@vue/runtime-core': 3.5.17 + '@vue/shared': 3.5.17 csstype: 3.1.3 - "@vue/server-renderer@3.5.17(vue@3.5.17(typescript@5.8.3))": + '@vue/server-renderer@3.5.17(vue@3.5.17(typescript@5.8.3))': dependencies: - "@vue/compiler-ssr": 3.5.17 - "@vue/shared": 3.5.17 + '@vue/compiler-ssr': 3.5.17 + '@vue/shared': 3.5.17 vue: 3.5.17(typescript@5.8.3) - "@vue/shared@3.5.17": {} + '@vue/shared@3.5.17': {} - "@yarnpkg/lockfile@1.1.0": {} + '@yarnpkg/lockfile@1.1.0': {} - "@yarnpkg/parsers@3.0.2": + '@yarnpkg/parsers@3.0.2': dependencies: js-yaml: 3.14.1 tslib: 2.8.1 - "@zkochan/js-yaml@0.0.7": + '@zkochan/js-yaml@0.0.7': dependencies: argparse: 2.0.1 @@ -7371,8 +5472,15 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 + abab@2.0.6: {} + abbrev@2.0.0: {} + acorn-globals@7.0.1: + dependencies: + acorn: 8.15.0 + acorn-walk: 8.3.4 + acorn-node@1.8.2: dependencies: acorn: 7.4.1 @@ -7391,6 +5499,12 @@ snapshots: add-stream@1.0.0: {} + agent-base@6.0.2(supports-color@8.1.1): + dependencies: + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + agent-base@7.1.3: {} aggregate-error@3.1.0: @@ -7460,75 +5574,75 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - axios@1.10.0: + axios@1.10.0(debug@4.4.1(supports-color@8.1.1)): dependencies: - follow-redirects: 1.15.9 + follow-redirects: 1.15.9(debug@4.4.1(supports-color@8.1.1)) form-data: 4.0.3 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - babel-jest@29.7.0(@babel/core@7.28.0): + babel-jest@29.7.0(@babel/core@7.28.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - "@babel/core": 7.28.0 - "@jest/transform": 29.7.0 - "@types/babel__core": 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.28.0) + '@babel/core': 7.28.0(supports-color@8.1.1) + '@jest/transform': 29.7.0(supports-color@8.1.1) + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1(supports-color@8.1.1) + babel-preset-jest: 29.6.3(@babel/core@7.28.0(supports-color@8.1.1)) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: - supports-color - babel-plugin-istanbul@6.1.1: + babel-plugin-istanbul@6.1.1(supports-color@8.1.1): dependencies: - "@babel/helper-plugin-utils": 7.27.1 - "@istanbuljs/load-nyc-config": 1.1.0 - "@istanbuljs/schema": 0.1.3 - istanbul-lib-instrument: 5.2.1 + '@babel/helper-plugin-utils': 7.27.1 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1(supports-color@8.1.1) test-exclude: 6.0.0 transitivePeerDependencies: - supports-color babel-plugin-jest-hoist@29.6.3: dependencies: - "@babel/template": 7.27.2 - "@babel/types": 7.28.0 - "@types/babel__core": 7.20.5 - "@types/babel__traverse": 7.20.7 + '@babel/template': 7.27.2 + '@babel/types': 7.28.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.7 babel-plugin-macros@3.1.0: dependencies: - "@babel/runtime": 7.27.6 + '@babel/runtime': 7.27.6 cosmiconfig: 7.1.0 resolve: 1.22.10 optional: true - babel-preset-current-node-syntax@1.1.0(@babel/core@7.28.0): - dependencies: - "@babel/core": 7.28.0 - "@babel/plugin-syntax-async-generators": 7.8.4(@babel/core@7.28.0) - "@babel/plugin-syntax-bigint": 7.8.3(@babel/core@7.28.0) - "@babel/plugin-syntax-class-properties": 7.12.13(@babel/core@7.28.0) - "@babel/plugin-syntax-class-static-block": 7.14.5(@babel/core@7.28.0) - "@babel/plugin-syntax-import-attributes": 7.27.1(@babel/core@7.28.0) - "@babel/plugin-syntax-import-meta": 7.10.4(@babel/core@7.28.0) - "@babel/plugin-syntax-json-strings": 7.8.3(@babel/core@7.28.0) - "@babel/plugin-syntax-logical-assignment-operators": 7.10.4(@babel/core@7.28.0) - "@babel/plugin-syntax-nullish-coalescing-operator": 7.8.3(@babel/core@7.28.0) - "@babel/plugin-syntax-numeric-separator": 7.10.4(@babel/core@7.28.0) - "@babel/plugin-syntax-object-rest-spread": 7.8.3(@babel/core@7.28.0) - "@babel/plugin-syntax-optional-catch-binding": 7.8.3(@babel/core@7.28.0) - "@babel/plugin-syntax-optional-chaining": 7.8.3(@babel/core@7.28.0) - "@babel/plugin-syntax-private-property-in-object": 7.14.5(@babel/core@7.28.0) - "@babel/plugin-syntax-top-level-await": 7.14.5(@babel/core@7.28.0) - - babel-preset-jest@29.6.3(@babel/core@7.28.0): - dependencies: - "@babel/core": 7.28.0 + babel-preset-current-node-syntax@1.1.0(@babel/core@7.28.0(supports-color@8.1.1)): + dependencies: + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.0(supports-color@8.1.1)) + + babel-preset-jest@29.6.3(@babel/core@7.28.0(supports-color@8.1.1)): + dependencies: + '@babel/core': 7.28.0(supports-color@8.1.1) babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.0) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.0(supports-color@8.1.1)) balanced-match@1.0.2: {} @@ -7712,7 +5826,7 @@ snapshots: cacache@18.0.4: dependencies: - "@npmcli/fs": 3.1.1 + '@npmcli/fs': 3.1.1 fs-minipass: 3.0.3 glob: 10.4.5 lru-cache: 10.4.3 @@ -7847,6 +5961,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@14.0.3: {} + common-ancestor-path@1.0.1: {} compare-func@2.0.0: @@ -7948,7 +6064,7 @@ snapshots: cosmiconfig@7.1.0: dependencies: - "@types/parse-json": 4.0.2 + '@types/parse-json': 4.0.2 import-fresh: 3.3.1 parse-json: 5.2.0 path-type: 4.0.0 @@ -7993,17 +6109,17 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.12 - create-jest@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)): + create-jest@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)): dependencies: - "@jest/types": 29.6.3 + '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) + jest-config: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: - - "@types/node" + - '@types/node' - babel-plugin-macros - supports-color - ts-node @@ -8033,19 +6149,35 @@ snapshots: cssesc@3.0.0: {} + cssom@0.3.8: {} + + cssom@0.5.0: {} + + cssstyle@2.3.0: + dependencies: + cssom: 0.3.8 + csstype@3.1.3: {} dargs@7.0.0: {} dash-ast@1.0.0: {} + data-urls@3.0.2: + dependencies: + abab: 2.0.6 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + dateformat@3.0.3: {} de-indent@1.0.2: {} - debug@4.4.1: + debug@4.4.1(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 decamelize-keys@1.1.1: dependencies: @@ -8054,6 +6186,8 @@ snapshots: decamelize@1.2.0: {} + decimal.js@10.6.0: {} + dedent@1.5.3(babel-plugin-macros@3.1.0): optionalDependencies: babel-plugin-macros: 3.1.0 @@ -8122,6 +6256,10 @@ snapshots: domain-browser@1.2.0: {} + domexception@4.0.0: + dependencies: + webidl-conversions: 7.0.0 + dot-prop@5.3.0: dependencies: is-obj: 2.0.0 @@ -8183,6 +6321,8 @@ snapshots: entities@4.5.0: {} + entities@6.0.1: {} + env-paths@2.2.1: {} envinfo@7.13.0: {} @@ -8208,16 +6348,57 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + escalade@3.2.0: {} escape-string-regexp@1.0.5: {} escape-string-regexp@2.0.0: {} + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + esprima@4.0.1: {} + estraverse@5.3.0: {} + estree-walker@2.0.2: {} + esutils@2.0.3: {} + eventemitter3@4.0.7: {} events@3.3.0: {} @@ -8255,7 +6436,7 @@ snapshots: expect@29.7.0: dependencies: - "@jest/expect-utils": 29.7.0 + '@jest/expect-utils': 29.7.0 jest-get-type: 29.6.3 jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 @@ -8281,6 +6462,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -8304,7 +6489,9 @@ snapshots: flat@5.0.2: {} - follow-redirects@1.15.9: {} + follow-redirects@1.15.9(debug@4.4.1(supports-color@8.1.1)): + optionalDependencies: + debug: 4.4.1(supports-color@8.1.1) for-each@0.3.5: dependencies: @@ -8345,6 +6532,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -8373,7 +6563,7 @@ snapshots: get-pkg-repo@4.2.1: dependencies: - "@hutson/parse-repository-url": 3.0.2 + '@hutson/parse-repository-url': 3.0.2 hosted-git-info: 4.1.0 through2: 2.0.5 yargs: 16.2.0 @@ -8447,8 +6637,6 @@ snapshots: minipass: 4.2.8 path-scurry: 1.11.1 - globals@11.12.0: {} - gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -8514,25 +6702,44 @@ snapshots: dependencies: lru-cache: 10.4.3 + html-encoding-sniffer@3.0.0: + dependencies: + whatwg-encoding: 2.0.0 + html-escaper@2.0.2: {} htmlescape@1.1.1: {} http-cache-semantics@4.2.0: {} - http-proxy-agent@7.0.2: + http-proxy-agent@5.0.0(supports-color@8.1.1): + dependencies: + '@tootallnate/once': 2.0.1 + agent-base: 6.0.2(supports-color@8.1.1) + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + http-proxy-agent@7.0.2(supports-color@8.1.1): dependencies: agent-base: 7.1.3 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-browserify@1.0.0: {} - https-proxy-agent@7.0.6: + https-proxy-agent@5.0.1(supports-color@8.1.1): + dependencies: + agent-base: 6.0.2(supports-color@8.1.1) + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6(supports-color@8.1.1): dependencies: agent-base: 7.1.3 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8545,7 +6752,6 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - optional: true ieee754@1.2.1: {} @@ -8589,7 +6795,7 @@ snapshots: init-package-json@6.0.3: dependencies: - "@npmcli/package-json": 5.2.0 + '@npmcli/package-json': 5.2.0 npm-package-arg: 11.0.2 promzard: 1.0.2 read: 3.0.1 @@ -8691,6 +6897,8 @@ snapshots: dependencies: isobject: 3.0.1 + is-potential-custom-element-name@1.0.1: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -8734,21 +6942,21 @@ snapshots: istanbul-lib-coverage@3.2.2: {} - istanbul-lib-instrument@5.2.1: + istanbul-lib-instrument@5.2.1(supports-color@8.1.1): dependencies: - "@babel/core": 7.28.0 - "@babel/parser": 7.28.0 - "@istanbuljs/schema": 0.1.3 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/parser': 7.28.0 + '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 transitivePeerDependencies: - supports-color - istanbul-lib-instrument@6.0.3: + istanbul-lib-instrument@6.0.3(supports-color@8.1.1): dependencies: - "@babel/core": 7.28.0 - "@babel/parser": 7.28.0 - "@istanbuljs/schema": 0.1.3 + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/parser': 7.28.0 + '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.7.2 transitivePeerDependencies: @@ -8760,9 +6968,9 @@ snapshots: make-dir: 4.0.0 supports-color: 7.2.0 - istanbul-lib-source-maps@4.0.1: + istanbul-lib-source-maps@4.0.1(supports-color@8.1.1): dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -8775,9 +6983,9 @@ snapshots: jackspeak@3.4.3: dependencies: - "@isaacs/cliui": 8.0.2 + '@isaacs/cliui': 8.0.2 optionalDependencies: - "@pkgjs/parseargs": 0.11.0 + '@pkgjs/parseargs': 0.11.0 jake@10.9.2: dependencies: @@ -8792,13 +7000,13 @@ snapshots: jest-util: 29.7.0 p-limit: 3.1.0 - jest-circus@29.7.0(babel-plugin-macros@3.1.0): + jest-circus@29.7.0(babel-plugin-macros@3.1.0)(supports-color@8.1.1): dependencies: - "@jest/environment": 29.7.0 - "@jest/expect": 29.7.0 - "@jest/test-result": 29.7.0 - "@jest/types": 29.6.3 - "@types/node": 20.19.4 + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0(supports-color@8.1.1) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.4 chalk: 4.1.2 co: 4.6.0 dedent: 1.6.0(babel-plugin-macros@3.1.0) @@ -8806,8 +7014,8 @@ snapshots: jest-each: 29.7.0 jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 + jest-runtime: 29.7.0(supports-color@8.1.1) + jest-snapshot: 29.7.0(supports-color@8.1.1) jest-util: 29.7.0 p-limit: 3.1.0 pretty-format: 29.7.0 @@ -8818,42 +7026,42 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)): + jest-cli@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)): dependencies: - "@jest/core": 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) - "@jest/test-result": 29.7.0 - "@jest/types": 29.6.3 + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) + create-jest: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) + jest-config: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 transitivePeerDependencies: - - "@types/node" + - '@types/node' - babel-plugin-macros - supports-color - ts-node - jest-config@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)): + jest-config@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)): dependencies: - "@babel/core": 7.28.0 - "@jest/test-sequencer": 29.7.0 - "@jest/types": 29.6.3 - babel-jest: 29.7.0(@babel/core@7.28.0) + '@babel/core': 7.28.0(supports-color@8.1.1) + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.0(supports-color@8.1.1))(supports-color@8.1.1) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 glob: 7.2.3 graceful-fs: 4.2.11 - jest-circus: 29.7.0(babel-plugin-macros@3.1.0) + jest-circus: 29.7.0(babel-plugin-macros@3.1.0)(supports-color@8.1.1) jest-environment-node: 29.7.0 jest-get-type: 29.6.3 jest-regex-util: 29.6.3 jest-resolve: 29.7.0 - jest-runner: 29.7.0 + jest-runner: 29.7.0(supports-color@8.1.1) jest-util: 29.7.0 jest-validate: 29.7.0 micromatch: 4.0.8 @@ -8862,7 +7070,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - "@types/node": 20.19.4 + '@types/node': 20.19.4 ts-node: 10.9.2(@types/node@20.19.4)(typescript@5.8.3) transitivePeerDependencies: - babel-plugin-macros @@ -8881,18 +7089,33 @@ snapshots: jest-each@29.7.0: dependencies: - "@jest/types": 29.6.3 + '@jest/types': 29.6.3 chalk: 4.1.2 jest-get-type: 29.6.3 jest-util: 29.7.0 pretty-format: 29.7.0 + jest-environment-jsdom@29.7.0(supports-color@8.1.1): + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/jsdom': 20.0.1 + '@types/node': 20.19.4 + jest-mock: 29.7.0 + jest-util: 29.7.0 + jsdom: 20.0.3(supports-color@8.1.1) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jest-environment-node@29.7.0: dependencies: - "@jest/environment": 29.7.0 - "@jest/fake-timers": 29.7.0 - "@jest/types": 29.6.3 - "@types/node": 20.19.4 + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.4 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -8900,9 +7123,9 @@ snapshots: jest-haste-map@29.7.0: dependencies: - "@jest/types": 29.6.3 - "@types/graceful-fs": 4.1.9 - "@types/node": 20.19.4 + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 20.19.4 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -8928,9 +7151,9 @@ snapshots: jest-message-util@29.7.0: dependencies: - "@babel/code-frame": 7.27.1 - "@jest/types": 29.6.3 - "@types/stack-utils": 2.0.3 + '@babel/code-frame': 7.27.1 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 chalk: 4.1.2 graceful-fs: 4.2.11 micromatch: 4.0.8 @@ -8940,8 +7163,8 @@ snapshots: jest-mock@29.7.0: dependencies: - "@jest/types": 29.6.3 - "@types/node": 20.19.4 + '@jest/types': 29.6.3 + '@types/node': 20.19.4 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -8950,10 +7173,10 @@ snapshots: jest-regex-util@29.6.3: {} - jest-resolve-dependencies@29.7.0: + jest-resolve-dependencies@29.7.0(supports-color@8.1.1): dependencies: jest-regex-util: 29.6.3 - jest-snapshot: 29.7.0 + jest-snapshot: 29.7.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -8969,14 +7192,14 @@ snapshots: resolve.exports: 2.0.3 slash: 3.0.0 - jest-runner@29.7.0: + jest-runner@29.7.0(supports-color@8.1.1): dependencies: - "@jest/console": 29.7.0 - "@jest/environment": 29.7.0 - "@jest/test-result": 29.7.0 - "@jest/transform": 29.7.0 - "@jest/types": 29.6.3 - "@types/node": 20.19.4 + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0(supports-color@8.1.1) + '@jest/types': 29.6.3 + '@types/node': 20.19.4 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -8986,7 +7209,7 @@ snapshots: jest-leak-detector: 29.7.0 jest-message-util: 29.7.0 jest-resolve: 29.7.0 - jest-runtime: 29.7.0 + jest-runtime: 29.7.0(supports-color@8.1.1) jest-util: 29.7.0 jest-watcher: 29.7.0 jest-worker: 29.7.0 @@ -8995,16 +7218,16 @@ snapshots: transitivePeerDependencies: - supports-color - jest-runtime@29.7.0: + jest-runtime@29.7.0(supports-color@8.1.1): dependencies: - "@jest/environment": 29.7.0 - "@jest/fake-timers": 29.7.0 - "@jest/globals": 29.7.0 - "@jest/source-map": 29.6.3 - "@jest/test-result": 29.7.0 - "@jest/transform": 29.7.0 - "@jest/types": 29.6.3 - "@types/node": 20.19.4 + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0(supports-color@8.1.1) + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0(supports-color@8.1.1) + '@jest/types': 29.6.3 + '@types/node': 20.19.4 chalk: 4.1.2 cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.2 @@ -9015,24 +7238,24 @@ snapshots: jest-mock: 29.7.0 jest-regex-util: 29.6.3 jest-resolve: 29.7.0 - jest-snapshot: 29.7.0 + jest-snapshot: 29.7.0(supports-color@8.1.1) jest-util: 29.7.0 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: - supports-color - jest-snapshot@29.7.0: - dependencies: - "@babel/core": 7.28.0 - "@babel/generator": 7.27.5 - "@babel/plugin-syntax-jsx": 7.27.1(@babel/core@7.28.0) - "@babel/plugin-syntax-typescript": 7.27.1(@babel/core@7.28.0) - "@babel/types": 7.27.7 - "@jest/expect-utils": 29.7.0 - "@jest/transform": 29.7.0 - "@jest/types": 29.6.3 - babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.0) + jest-snapshot@29.7.0(supports-color@8.1.1): + dependencies: + '@babel/core': 7.28.0(supports-color@8.1.1) + '@babel/generator': 7.27.5 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0(supports-color@8.1.1)) + '@babel/types': 7.27.7 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0(supports-color@8.1.1) + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.0(supports-color@8.1.1)) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -9049,8 +7272,8 @@ snapshots: jest-util@29.7.0: dependencies: - "@jest/types": 29.6.3 - "@types/node": 20.19.4 + '@jest/types': 29.6.3 + '@types/node': 20.19.4 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -9058,7 +7281,7 @@ snapshots: jest-validate@29.7.0: dependencies: - "@jest/types": 29.6.3 + '@jest/types': 29.6.3 camelcase: 6.3.0 chalk: 4.1.2 jest-get-type: 29.6.3 @@ -9067,9 +7290,9 @@ snapshots: jest-watcher@29.7.0: dependencies: - "@jest/test-result": 29.7.0 - "@jest/types": 29.6.3 - "@types/node": 20.19.4 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.19.4 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -9078,19 +7301,19 @@ snapshots: jest-worker@29.7.0: dependencies: - "@types/node": 20.19.4 + '@types/node': 20.19.4 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)): + jest@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)): dependencies: - "@jest/core": 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) - "@jest/types": 29.6.3 + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) + '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) + jest-cli: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) transitivePeerDependencies: - - "@types/node" + - '@types/node' - babel-plugin-macros - supports-color - ts-node @@ -9108,6 +7331,39 @@ snapshots: jsbn@1.1.0: {} + jsdom@20.0.3(supports-color@8.1.1): + dependencies: + abab: 2.0.6 + acorn: 8.15.0 + acorn-globals: 7.0.1 + cssom: 0.5.0 + cssstyle: 2.3.0 + data-urls: 3.0.2 + decimal.js: 10.6.0 + domexception: 4.0.0 + escodegen: 2.1.0 + form-data: 4.0.3 + html-encoding-sniffer: 3.0.0 + http-proxy-agent: 5.0.0(supports-color@8.1.1) + https-proxy-agent: 5.0.1(supports-color@8.1.1) + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.24 + parse5: 7.3.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 4.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + ws: 8.21.3 + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@3.1.0: {} json-parse-better-errors@1.0.2: {} @@ -9145,15 +7401,15 @@ snapshots: inherits: 2.0.4 stream-splicer: 2.0.1 - lerna@8.2.3(babel-plugin-macros@3.1.0)(encoding@0.1.13): + lerna@8.2.3(babel-plugin-macros@3.1.0)(debug@4.4.1(supports-color@8.1.1))(encoding@0.1.13)(supports-color@8.1.1): dependencies: - "@lerna/create": 8.2.3(babel-plugin-macros@3.1.0)(encoding@0.1.13)(typescript@5.8.3) - "@npmcli/arborist": 7.5.4 - "@npmcli/package-json": 5.2.0 - "@npmcli/run-script": 8.1.0 - "@nx/devkit": 20.8.2(nx@20.8.2) - "@octokit/plugin-enterprise-rest": 6.0.1 - "@octokit/rest": 20.1.2 + '@lerna/create': 8.2.3(babel-plugin-macros@3.1.0)(debug@4.4.1(supports-color@8.1.1))(encoding@0.1.13)(supports-color@8.1.1)(typescript@5.8.3) + '@npmcli/arborist': 7.5.4(supports-color@8.1.1) + '@npmcli/package-json': 5.2.0 + '@npmcli/run-script': 8.1.0(supports-color@8.1.1) + '@nx/devkit': 20.8.2(nx@20.8.2(debug@4.4.1(supports-color@8.1.1))) + '@octokit/plugin-enterprise-rest': 6.0.1 + '@octokit/rest': 20.1.2 aproba: 2.0.0 byte-size: 8.1.1 chalk: 4.1.0 @@ -9184,8 +7440,8 @@ snapshots: is-stream: 2.0.0 jest-diff: 29.7.0 js-yaml: 4.1.0 - libnpmaccess: 8.0.6 - libnpmpublish: 9.0.9 + libnpmaccess: 8.0.6(supports-color@8.1.1) + libnpmpublish: 9.0.9(supports-color@8.1.1) load-json-file: 6.2.0 lodash: 4.17.21 make-dir: 4.0.0 @@ -9194,15 +7450,15 @@ snapshots: node-fetch: 2.6.7(encoding@0.1.13) npm-package-arg: 11.0.2 npm-packlist: 8.0.2 - npm-registry-fetch: 17.1.0 - nx: 20.8.2 + npm-registry-fetch: 17.1.0(supports-color@8.1.1) + nx: 20.8.2(debug@4.4.1(supports-color@8.1.1)) p-map: 4.0.0 p-map-series: 2.1.0 p-pipe: 3.1.0 p-queue: 6.6.2 p-reduce: 2.1.0 p-waterfall: 2.1.1 - pacote: 18.0.6 + pacote: 18.0.6(supports-color@8.1.1) pify: 5.0.0 read-cmd-shim: 4.0.0 resolve-from: 5.0.0 @@ -9228,8 +7484,8 @@ snapshots: yargs: 17.7.2 yargs-parser: 21.1.1 transitivePeerDependencies: - - "@swc-node/register" - - "@swc/core" + - '@swc-node/register' + - '@swc/core' - babel-plugin-macros - bluebird - debug @@ -9238,22 +7494,22 @@ snapshots: leven@3.1.0: {} - libnpmaccess@8.0.6: + libnpmaccess@8.0.6(supports-color@8.1.1): dependencies: npm-package-arg: 11.0.2 - npm-registry-fetch: 17.1.0 + npm-registry-fetch: 17.1.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - libnpmpublish@9.0.9: + libnpmpublish@9.0.9(supports-color@8.1.1): dependencies: ci-info: 4.2.0 normalize-package-data: 6.0.2 npm-package-arg: 11.0.2 - npm-registry-fetch: 17.1.0 + npm-registry-fetch: 17.1.0(supports-color@8.1.1) proc-log: 4.2.0 semver: 7.7.2 - sigstore: 2.3.1 + sigstore: 2.3.1(supports-color@8.1.1) ssri: 10.0.6 transitivePeerDependencies: - supports-color @@ -9314,7 +7570,7 @@ snapshots: magic-string@0.30.17: dependencies: - "@jridgewell/sourcemap-codec": 1.5.4 + '@jridgewell/sourcemap-codec': 1.5.4 make-dir@2.1.0: dependencies: @@ -9327,9 +7583,9 @@ snapshots: make-error@1.3.6: {} - make-fetch-happen@13.0.1: + make-fetch-happen@13.0.1(supports-color@8.1.1): dependencies: - "@npmcli/agent": 2.2.2 + '@npmcli/agent': 2.2.2(supports-color@8.1.1) cacache: 18.0.4 http-cache-semantics: 4.2.0 is-lambda: 1.0.1 @@ -9362,7 +7618,7 @@ snapshots: meow@8.1.2: dependencies: - "@types/minimist": 1.2.5 + '@types/minimist': 1.2.5 camelcase-keys: 6.2.2 decamelize-keys: 1.1.1 hard-rejection: 2.1.0 @@ -9501,7 +7757,7 @@ snapshots: multimatch@5.0.0: dependencies: - "@types/minimatch": 3.0.5 + '@types/minimatch': 3.0.5 array-differ: 3.0.0 array-union: 2.1.0 arrify: 2.0.1 @@ -9525,13 +7781,13 @@ snapshots: optionalDependencies: encoding: 0.1.13 - node-gyp@10.3.1: + node-gyp@10.3.1(supports-color@8.1.1): dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.2 glob: 10.4.5 graceful-fs: 4.2.11 - make-fetch-happen: 13.0.1 + make-fetch-happen: 13.0.1(supports-color@8.1.1) nopt: 7.2.1 proc-log: 4.2.0 semver: 7.7.2 @@ -9605,11 +7861,11 @@ snapshots: npm-package-arg: 11.0.2 semver: 7.7.2 - npm-registry-fetch@17.1.0: + npm-registry-fetch@17.1.0(supports-color@8.1.1): dependencies: - "@npmcli/redact": 2.0.1 + '@npmcli/redact': 2.0.1 jsonparse: 1.3.1 - make-fetch-happen: 13.0.1 + make-fetch-happen: 13.0.1(supports-color@8.1.1) minipass: 7.1.2 minipass-fetch: 3.0.5 minizlib: 2.1.2 @@ -9622,13 +7878,15 @@ snapshots: dependencies: path-key: 3.1.1 - nx@20.8.2: + nwsapi@2.2.24: {} + + nx@20.8.2(debug@4.4.1(supports-color@8.1.1)): dependencies: - "@napi-rs/wasm-runtime": 0.2.4 - "@yarnpkg/lockfile": 1.1.0 - "@yarnpkg/parsers": 3.0.2 - "@zkochan/js-yaml": 0.0.7 - axios: 1.10.0 + '@napi-rs/wasm-runtime': 0.2.4 + '@yarnpkg/lockfile': 1.1.0 + '@yarnpkg/parsers': 3.0.2 + '@zkochan/js-yaml': 0.0.7 + axios: 1.10.0(debug@4.4.1(supports-color@8.1.1)) chalk: 4.1.2 cli-cursor: 3.1.0 cli-spinners: 2.6.1 @@ -9659,16 +7917,16 @@ snapshots: yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: - "@nx/nx-darwin-arm64": 20.8.2 - "@nx/nx-darwin-x64": 20.8.2 - "@nx/nx-freebsd-x64": 20.8.2 - "@nx/nx-linux-arm-gnueabihf": 20.8.2 - "@nx/nx-linux-arm64-gnu": 20.8.2 - "@nx/nx-linux-arm64-musl": 20.8.2 - "@nx/nx-linux-x64-gnu": 20.8.2 - "@nx/nx-linux-x64-musl": 20.8.2 - "@nx/nx-win32-arm64-msvc": 20.8.2 - "@nx/nx-win32-x64-msvc": 20.8.2 + '@nx/nx-darwin-arm64': 20.8.2 + '@nx/nx-darwin-x64': 20.8.2 + '@nx/nx-freebsd-x64': 20.8.2 + '@nx/nx-linux-arm-gnueabihf': 20.8.2 + '@nx/nx-linux-arm64-gnu': 20.8.2 + '@nx/nx-linux-arm64-musl': 20.8.2 + '@nx/nx-linux-x64-gnu': 20.8.2 + '@nx/nx-linux-x64-musl': 20.8.2 + '@nx/nx-win32-arm64-msvc': 20.8.2 + '@nx/nx-win32-x64-msvc': 20.8.2 transitivePeerDependencies: - debug @@ -9777,23 +8035,23 @@ snapshots: package-json-from-dist@1.0.1: {} - pacote@18.0.6: + pacote@18.0.6(supports-color@8.1.1): dependencies: - "@npmcli/git": 5.0.8 - "@npmcli/installed-package-contents": 2.1.0 - "@npmcli/package-json": 5.2.0 - "@npmcli/promise-spawn": 7.0.2 - "@npmcli/run-script": 8.1.0 + '@npmcli/git': 5.0.8 + '@npmcli/installed-package-contents': 2.1.0 + '@npmcli/package-json': 5.2.0 + '@npmcli/promise-spawn': 7.0.2 + '@npmcli/run-script': 8.1.0(supports-color@8.1.1) cacache: 18.0.4 fs-minipass: 3.0.3 minipass: 7.1.2 npm-package-arg: 11.0.2 npm-packlist: 8.0.2 npm-pick-manifest: 9.1.0 - npm-registry-fetch: 17.1.0 + npm-registry-fetch: 17.1.0(supports-color@8.1.1) proc-log: 4.2.0 promise-retry: 2.0.1 - sigstore: 2.3.1 + sigstore: 2.3.1(supports-color@8.1.1) ssri: 10.0.6 tar: 6.2.1 transitivePeerDependencies: @@ -9832,7 +8090,7 @@ snapshots: parse-json@5.2.0: dependencies: - "@babel/code-frame": 7.27.1 + '@babel/code-frame': 7.27.1 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -9845,6 +8103,10 @@ snapshots: dependencies: parse-path: 7.1.0 + parse5@7.3.0: + dependencies: + entities: 6.0.1 + path-browserify@1.0.1: {} path-exists@3.0.0: {} @@ -9880,12 +8142,20 @@ snapshots: sha.js: 2.4.12 to-buffer: 1.2.1 + peggy@5.1.0: + dependencies: + '@peggyjs/from-mem': 3.1.3 + commander: 14.0.3 + source-map-generator: 2.0.6 + picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.2: {} + picomatch@4.0.5: {} + pify@2.3.0: {} pify@3.0.0: {} @@ -9900,6 +8170,14 @@ snapshots: dependencies: find-up: 4.1.0 + playwright-core@1.62.1: {} + + playwright@1.62.1: + dependencies: + playwright-core: 1.62.1 + optionalDependencies: + fsevents: 2.3.2 + possible-typed-array-names@1.1.0: {} postcss-selector-parser@6.1.2: @@ -9917,7 +8195,7 @@ snapshots: pretty-format@29.7.0: dependencies: - "@jest/schemas": 29.6.3 + '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.3.1 @@ -9953,6 +8231,10 @@ snapshots: proxy-from-env@1.1.0: {} + psl@1.15.0: + dependencies: + punycode: 2.3.1 + public-encrypt@4.0.3: dependencies: bn.js: 4.12.2 @@ -9964,6 +8246,8 @@ snapshots: punycode@1.4.1: {} + punycode@2.3.1: {} + pure-rand@6.1.0: {} qs@6.14.0: @@ -9972,6 +8256,8 @@ snapshots: querystring-es3@0.2.1: {} + querystringify@2.2.0: {} + quick-lru@4.0.1: {} randombytes@2.1.0: @@ -10026,7 +8312,7 @@ snapshots: read-pkg@5.2.0: dependencies: - "@types/normalize-package-data": 2.4.4 + '@types/normalize-package-data': 2.4.4 normalize-package-data: 2.5.0 parse-json: 5.2.0 type-fest: 0.6.0 @@ -10065,6 +8351,8 @@ snapshots: require-directory@2.1.1: {} + requires-port@1.0.0: {} + resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 @@ -10106,6 +8394,38 @@ snapshots: hash-base: 3.0.5 inherits: 2.0.4 + rollup@4.62.4: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.62.4 + '@rollup/rollup-android-arm64': 4.62.4 + '@rollup/rollup-darwin-arm64': 4.62.4 + '@rollup/rollup-darwin-x64': 4.62.4 + '@rollup/rollup-freebsd-arm64': 4.62.4 + '@rollup/rollup-freebsd-x64': 4.62.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.4 + '@rollup/rollup-linux-arm-musleabihf': 4.62.4 + '@rollup/rollup-linux-arm64-gnu': 4.62.4 + '@rollup/rollup-linux-arm64-musl': 4.62.4 + '@rollup/rollup-linux-loong64-gnu': 4.62.4 + '@rollup/rollup-linux-loong64-musl': 4.62.4 + '@rollup/rollup-linux-ppc64-gnu': 4.62.4 + '@rollup/rollup-linux-ppc64-musl': 4.62.4 + '@rollup/rollup-linux-riscv64-gnu': 4.62.4 + '@rollup/rollup-linux-riscv64-musl': 4.62.4 + '@rollup/rollup-linux-s390x-gnu': 4.62.4 + '@rollup/rollup-linux-x64-gnu': 4.62.4 + '@rollup/rollup-linux-x64-musl': 4.62.4 + '@rollup/rollup-openbsd-x64': 4.62.4 + '@rollup/rollup-openharmony-arm64': 4.62.4 + '@rollup/rollup-win32-arm64-msvc': 4.62.4 + '@rollup/rollup-win32-ia32-msvc': 4.62.4 + '@rollup/rollup-win32-x64-gnu': 4.62.4 + '@rollup/rollup-win32-x64-msvc': 4.62.4 + fsevents: 2.3.3 + run-async@2.4.1: {} rxjs@7.8.2: @@ -10124,6 +8444,10 @@ snapshots: safer-buffer@2.1.2: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.26.0: {} semver@5.7.2: {} @@ -10132,6 +8456,8 @@ snapshots: semver@7.7.2: {} + semver@7.7.4: {} + set-blocking@2.0.0: {} set-function-length@1.2.2: @@ -10197,14 +8523,14 @@ snapshots: signal-exit@4.1.0: {} - sigstore@2.3.1: + sigstore@2.3.1(supports-color@8.1.1): dependencies: - "@sigstore/bundle": 2.3.2 - "@sigstore/core": 1.1.0 - "@sigstore/protobuf-specs": 0.3.3 - "@sigstore/sign": 2.3.2 - "@sigstore/tuf": 2.3.4 - "@sigstore/verify": 1.2.1 + '@sigstore/bundle': 2.3.2 + '@sigstore/core': 1.1.0 + '@sigstore/protobuf-specs': 0.3.3 + '@sigstore/sign': 2.3.2(supports-color@8.1.1) + '@sigstore/tuf': 2.3.4(supports-color@8.1.1) + '@sigstore/verify': 1.2.1 transitivePeerDependencies: - supports-color @@ -10216,10 +8542,10 @@ snapshots: smart-buffer@4.2.0: {} - socks-proxy-agent@8.0.5: + socks-proxy-agent@8.0.5(supports-color@8.1.1): dependencies: agent-base: 7.1.3 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) socks: 2.8.5 transitivePeerDependencies: - supports-color @@ -10233,6 +8559,8 @@ snapshots: dependencies: is-plain-obj: 1.1.0 + source-map-generator@2.0.6: {} + source-map-js@1.2.1: {} source-map-support@0.5.13: @@ -10361,6 +8689,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: {} + syntax-error@1.4.0: dependencies: acorn-node: 1.8.2 @@ -10386,7 +8716,7 @@ snapshots: test-exclude@6.0.0: dependencies: - "@istanbuljs/schema": 0.1.3 + '@istanbuljs/schema': 0.1.3 glob: 7.2.3 minimatch: 3.1.2 @@ -10408,6 +8738,11 @@ snapshots: fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -10426,18 +8761,29 @@ snapshots: dependencies: is-number: 7.0.0 + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + tr46@0.0.3: {} + tr46@3.0.0: + dependencies: + punycode: 2.3.1 + treeverse@3.0.0: {} trim-newlines@3.0.1: {} - ts-jest@29.4.0(@babel/core@7.28.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)))(typescript@5.8.3): + ts-jest@29.4.0(@babel/core@7.28.0(supports-color@8.1.1))(@jest/transform@29.7.0(supports-color@8.1.1))(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.0(supports-color@8.1.1))(supports-color@8.1.1))(esbuild@0.28.2)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)))(typescript@5.8.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) + jest: 29.7.0(@types/node@20.19.4)(babel-plugin-macros@3.1.0)(supports-color@8.1.1)(ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -10446,20 +8792,21 @@ snapshots: typescript: 5.8.3 yargs-parser: 21.1.1 optionalDependencies: - "@babel/core": 7.28.0 - "@jest/transform": 29.7.0 - "@jest/types": 29.6.3 - babel-jest: 29.7.0(@babel/core@7.28.0) + '@babel/core': 7.28.0(supports-color@8.1.1) + '@jest/transform': 29.7.0(supports-color@8.1.1) + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.28.0(supports-color@8.1.1))(supports-color@8.1.1) + esbuild: 0.28.2 jest-util: 29.7.0 ts-node@10.9.2(@types/node@20.19.4)(typescript@5.8.3): dependencies: - "@cspotcode/source-map-support": 0.8.1 - "@tsconfig/node10": 1.0.11 - "@tsconfig/node12": 1.0.11 - "@tsconfig/node14": 1.0.3 - "@tsconfig/node16": 1.0.4 - "@types/node": 20.19.4 + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.19.4 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -10480,11 +8827,11 @@ snapshots: tty-browserify@0.0.1: {} - tuf-js@2.2.1: + tuf-js@2.2.1(supports-color@8.1.1): dependencies: - "@tufjs/models": 2.0.1 - debug: 4.4.1 - make-fetch-happen: 13.0.1 + '@tufjs/models': 2.0.1 + debug: 4.4.1(supports-color@8.1.1) + make-fetch-happen: 13.0.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -10537,6 +8884,8 @@ snapshots: universal-user-agent@6.0.1: {} + universalify@0.2.0: {} + universalify@2.0.1: {} untildify@4.0.0: {} @@ -10549,6 +8898,11 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + url@0.11.4: dependencies: punycode: 1.4.1 @@ -10574,8 +8928,8 @@ snapshots: v8-to-istanbul@9.3.0: dependencies: - "@jridgewell/trace-mapping": 0.3.29 - "@types/istanbul-lib-coverage": 2.0.6 + '@jridgewell/trace-mapping': 0.3.29 + '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 validate-npm-package-license@3.0.4: @@ -10585,6 +8939,19 @@ snapshots: validate-npm-package-name@5.0.1: {} + vite@7.3.6(@types/node@20.19.4)(yaml@2.8.0): + dependencies: + esbuild: 0.28.2 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + postcss: 8.5.6 + rollup: 4.62.4 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 20.19.4 + fsevents: 2.3.3 + yaml: 2.8.0 + vm-browserify@1.1.2: {} vue-template-compiler@2.7.16: @@ -10594,21 +8961,25 @@ snapshots: vue-tsc@1.8.27(typescript@5.8.3): dependencies: - "@volar/typescript": 1.11.1 - "@vue/language-core": 1.8.27(typescript@5.8.3) + '@volar/typescript': 1.11.1 + '@vue/language-core': 1.8.27(typescript@5.8.3) semver: 7.7.2 typescript: 5.8.3 vue@3.5.17(typescript@5.8.3): dependencies: - "@vue/compiler-dom": 3.5.17 - "@vue/compiler-sfc": 3.5.17 - "@vue/runtime-dom": 3.5.17 - "@vue/server-renderer": 3.5.17(vue@3.5.17(typescript@5.8.3)) - "@vue/shared": 3.5.17 + '@vue/compiler-dom': 3.5.17 + '@vue/compiler-sfc': 3.5.17 + '@vue/runtime-dom': 3.5.17 + '@vue/server-renderer': 3.5.17(vue@3.5.17(typescript@5.8.3)) + '@vue/shared': 3.5.17 optionalDependencies: typescript: 5.8.3 + w3c-xmlserializer@4.0.0: + dependencies: + xml-name-validator: 4.0.0 + walk-up-path@3.0.1: {} walker@1.0.8: @@ -10621,6 +8992,19 @@ snapshots: webidl-conversions@3.0.1: {} + webidl-conversions@7.0.0: {} + + whatwg-encoding@2.0.0: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@3.0.0: {} + + whatwg-url@11.0.0: + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -10701,6 +9085,12 @@ snapshots: type-fest: 0.4.1 write-json-file: 3.2.0 + ws@8.21.3: {} + + xml-name-validator@4.0.0: {} + + xmlchars@2.2.0: {} + xtend@4.0.2: {} y18n@5.0.8: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4340350e..0c283f01 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,6 @@ packages: - - 'packages/*' \ No newline at end of file + - 'packages/*' + - 'playground' +allowBuilds: + esbuild: true + nx: true From c1e99f4f63ab53654b2f12ffaa85bcc5029b0bc9 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:00:16 -0700 Subject: [PATCH 02/22] test(pstricks): add conformance harness against real PSTricks Renders the same source through LaTeX2JS and through genuine PSTricks in Docker, then ranks the pairs by disagreement, so conformance questions are answered by comparison rather than by reading code. render-examples splits the project's own examples into individual pictures (57 from 24 files) and rasterizes each via latex/dvips/ps2pdf/gs; fuzz-corpus generates 336 systematic cases covering every command crossed with each style axis plus draw-order and degenerate-input probes; compare scores pairs on ink coverage, colour histogram and layout occupancy, weighted worst-first. The score is deliberately heuristic: SVG in a browser and Ghostscript output cannot match pixel for pixel, so it orders where to look and never gates. LaTeX2JS-only macros have no upstream equivalent, so they are rewritten to their static state with plot variables pinned and the bindings recorded. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- tools/pstricks-conformance/.gitignore | 5 + tools/pstricks-conformance/README.md | 93 +++++ tools/pstricks-conformance/compare.mjs | 265 +++++++++++++ tools/pstricks-conformance/fuzz-corpus.mjs | 200 ++++++++++ tools/pstricks-conformance/gallery.mjs | 336 +++++++++++++++++ .../pstricks-conformance/render-examples.mjs | 347 ++++++++++++++++++ .../pstricks-conformance/render-reference.mjs | 95 +++++ 7 files changed, 1341 insertions(+) create mode 100644 tools/pstricks-conformance/.gitignore create mode 100644 tools/pstricks-conformance/README.md create mode 100644 tools/pstricks-conformance/compare.mjs create mode 100644 tools/pstricks-conformance/fuzz-corpus.mjs create mode 100644 tools/pstricks-conformance/gallery.mjs create mode 100644 tools/pstricks-conformance/render-examples.mjs create mode 100644 tools/pstricks-conformance/render-reference.mjs diff --git a/tools/pstricks-conformance/.gitignore b/tools/pstricks-conformance/.gitignore new file mode 100644 index 00000000..12003d19 --- /dev/null +++ b/tools/pstricks-conformance/.gitignore @@ -0,0 +1,5 @@ +# Generated corpora and rasterized output — reproducible from the scripts. +corpus/ +examples-ref/ +reference/ +*.html diff --git a/tools/pstricks-conformance/README.md b/tools/pstricks-conformance/README.md new file mode 100644 index 00000000..6248c303 --- /dev/null +++ b/tools/pstricks-conformance/README.md @@ -0,0 +1,93 @@ +# PSTricks conformance harness + +Renders the same source twice — once through LaTeX2JS, once through genuine +PSTricks — and ranks the pairs by how much they disagree. + +The point is that "does this command render correctly?" becomes a question with +an answer, rather than a judgement call against a screenshot. Real PSTricks is +the specification made visual. + +## Requirements + +Docker, and the LaTeX image: + +```sh +docker pull --platform linux/amd64 pyramation/pstricks-latex:latest +``` + +The image is amd64-only, so it runs under emulation on Apple Silicon — roughly +30 seconds for the whole example corpus, two minutes for the generated one. + +PSTricks emits PostScript specials, so rasterizing goes +`latex → dvips → ps2pdf → gs`. `pdflatex` **cannot** render these documents. + +## The four tools + +| Script | What it does | +|---|---| +| `render-examples.mjs` | Splits the project's own `.tex` examples into individual pictures and renders each with real PSTricks | +| `fuzz-corpus.mjs` | Generates a systematic corpus: every command crossed with every style axis, plus draw-order and degenerate-input probes | +| `render-reference.mjs` | Rasterizes that generated corpus | +| `compare.mjs` | Pairs LaTeX2JS output against the reference and scores each pair | +| `gallery.mjs` | Inlines any directory of PNGs into one self-contained HTML page | + +## Typical run + +```sh +cd tools/pstricks-conformance + +# ground truth for the project's own examples +node render-examples.mjs \ + --corpus ../../packages/latex2js/test/corpus \ + --out ./examples-ref + +# LaTeX2JS side (from the playground) +cd ../../playground && pnpm e2e:gallery && cd - + +# score them +node compare.mjs \ + --js ../../playground/renders \ + --ref ./examples-ref/ref \ + --out comparison.html +``` + +For the generated corpus: + +```sh +node fuzz-corpus.mjs --out ./corpus +node render-reference.mjs --corpus ./corpus --jobs 6 +node gallery.mjs --renders ./corpus/ref --out reference.html +``` + +## Reading the score + +The two renderers cannot match pixel for pixel: one is SVG in a browser, the +other is Ghostscript output cropped to a PostScript bounding box, with +different antialiasing and font engines. **The score is a triage ordering, not +a pass/fail gate.** It says where to look. + +Three signals, each chosen to survive rasterizer differences: + +- **ink** — fraction of non-white pixels. Catches missing or excess drawing. +- **colour** — normalized hue histogram. Catches wrong, absent, or unfilled fills. +- **layout** — 16×16 occupancy grid over the ink bounding box, so canvas size + and crop do not dominate. Catches reordering and misplacement. + +Weighted 25 / 30 / 45 into an overall figure, worst first. + +## Examples that cannot be compared directly + +`\userline`, `\uservariable` and `\slider` are LaTeX2JS extensions with no +PSTricks equivalent. `render-examples.mjs` rewrites each to its static state — +a `\userline` becomes the `\psline` it draws before any interaction — and pins +every plot variable to a fixed value, recording the binding in +`manifest.json`. + +A comparison for one of those units is only meaningful if the LaTeX2JS side is +rendered at the **same** bindings. Until that is wired up, treat those pairs as +indicative rather than authoritative. + +The harness also normalizes several places where LaTeX2JS accepts input real +PSTricks rejects — `pow(a,b)`, infix bodies without `algebraic=true`, variable +plot bounds, `plotpoints=1`. Each of those is a dialect decision the project +still owes an answer to: keep the extension, or conform. diff --git a/tools/pstricks-conformance/compare.mjs b/tools/pstricks-conformance/compare.mjs new file mode 100644 index 00000000..1780ef3f --- /dev/null +++ b/tools/pstricks-conformance/compare.mjs @@ -0,0 +1,265 @@ +#!/usr/bin/env node +/** + * compare.mjs — pair LaTeX2JS renders against PSTricks ground truth. + * + * The two renderers do not produce comparable rasters: one is SVG in a browser + * at the picture's own aspect, the other is Ghostscript output cropped to the + * PostScript bounding box, with different antialiasing and font engines. So + * the score here is a triage heuristic, not a pass/fail gate — it orders pairs + * by how likely they are to differ in a way a human should look at. + * + * node compare.mjs --js --ref --out + * + * Signals, each cheap and each robust to rasterizer differences: + * ink fraction of non-white pixels — catches missing or excess drawing + * hue normalized colour histogram — catches wrong or absent fills + * layout 16x16 occupancy grid over the ink bounding box — catches + * structural differences such as reordering or misplacement + */ + +import { readFileSync, writeFileSync, readdirSync, existsSync } from 'node:fs' +import { join, basename, resolve } from 'node:path' +import { inflateSync } from 'node:zlib' + +const argv = process.argv.slice(2) +const flag = (n, d) => (argv.includes(n) ? argv[argv.indexOf(n) + 1] : d) + +const jsDir = resolve(flag('--js', '')) +const refDir = resolve(flag('--ref', '')) +const outFile = resolve(flag('--out', 'comparison.html')) +const GRID = 16 + +/** + * Decodes an 8-bit truecolour PNG to raw samples, undoing the per-scanline + * filters. Returns null for anything else (palette, 16-bit, interlaced) so the + * pair is reported as unscored rather than silently scoring zero. + */ +function decode(path) { + try { + const b = readFileSync(path) + if (b.readUInt32BE(0) !== 0x89504e47) return null + let p = 8, w = 0, h = 0, bd = 0, ct = 0, interlace = 0 + const idat = [] + while (p < b.length) { + const len = b.readUInt32BE(p) + const type = b.toString('ascii', p + 4, p + 8) + if (type === 'IHDR') { + w = b.readUInt32BE(p + 8); h = b.readUInt32BE(p + 12) + bd = b[p + 16]; ct = b[p + 17]; interlace = b[p + 20] + } else if (type === 'IDAT') idat.push(b.subarray(p + 8, p + 8 + len)) + else if (type === 'IEND') break + p += 12 + len + } + if (bd !== 8 || interlace !== 0 || (ct !== 2 && ct !== 6)) return null + + const ch = ct === 6 ? 4 : 3 + const data = inflateSync(Buffer.concat(idat)) + const stride = w * ch + const out = Buffer.alloc(w * h * ch) + let prev = Buffer.alloc(stride) + + for (let y = 0, o = 0; y < h; y++) { + const f = data[o++] + const line = data.subarray(o, o + stride) + o += stride + const cur = Buffer.alloc(stride) + for (let i = 0; i < stride; i++) { + const a = i >= ch ? cur[i - ch] : 0 + const up = prev[i] + const ul = i >= ch ? prev[i - ch] : 0 + let v = line[i] + if (f === 1) v += a + else if (f === 2) v += up + else if (f === 3) v += (a + up) >> 1 + else if (f === 4) { + const pa = Math.abs(up - ul), pb = Math.abs(a - ul), pc = Math.abs(a + up - 2 * ul) + v += (pa <= pb && pa <= pc) ? a : (pb <= pc ? up : ul) + } + cur[i] = v & 255 + } + cur.copy(out, y * stride) + prev = cur + } + return { w, h, ch, data: out } + } catch { return null } +} + +/** Ink = any pixel meaningfully darker or more saturated than white. */ +function isInk(r, g, b) { + return r < 245 || g < 245 || b < 245 +} + +/** + * Reduces an image to comparable descriptors: ink ratio, a coarse hue + * histogram, and an occupancy grid normalized over the ink bounding box so + * differing canvas sizes and crops do not dominate the score. + */ +function describe(img) { + const { w, h, ch, data } = img + let inked = 0 + let minX = w, minY = h, maxX = -1, maxY = -1 + const hue = new Array(7).fill(0) + + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + const i = (y * w + x) * ch + const r = data[i], g = data[i + 1], b = data[i + 2] + if (!isInk(r, g, b)) continue + inked++ + if (x < minX) minX = x + if (x > maxX) maxX = x + if (y < minY) minY = y + if (y > maxY) maxY = y + const mx = Math.max(r, g, b), mn = Math.min(r, g, b) + if (mx - mn < 40) hue[mx < 128 ? 0 : 1]++ // dark / light neutral + else if (r === mx) hue[g > b ? 2 : 3]++ // red-ish / magenta-ish + else if (g === mx) hue[4]++ // green-ish + else hue[b > g ? 5 : 6]++ // blue-ish / cyan-ish + } + } + + const grid = new Array(GRID * GRID).fill(0) + if (maxX >= minX && maxY >= minY) { + const bw = maxX - minX + 1, bh = maxY - minY + 1 + for (let y = minY; y <= maxY; y++) { + for (let x = minX; x <= maxX; x++) { + const i = (y * w + x) * ch + if (!isInk(data[i], data[i + 1], data[i + 2])) continue + const gx = Math.min(GRID - 1, Math.floor(((x - minX) / bw) * GRID)) + const gy = Math.min(GRID - 1, Math.floor(((y - minY) / bh) * GRID)) + grid[gy * GRID + gx]++ + } + } + } + const cells = grid.reduce((a, v) => a + v, 0) || 1 + const total = w * h + + return { + ink: inked / total, + hue: hue.map((v) => v / (inked || 1)), + grid: grid.map((v) => v / cells), + box: maxX >= minX ? { w: maxX - minX + 1, h: maxY - minY + 1 } : null, + } +} + +const sim = (a, b) => 1 - Math.min(1, Math.abs(a - b) / Math.max(a, b, 1e-6)) +const cosine = (a, b) => { + let d = 0, na = 0, nb = 0 + for (let i = 0; i < a.length; i++) { d += a[i] * b[i]; na += a[i] * a[i]; nb += b[i] * b[i] } + return na && nb ? d / Math.sqrt(na * nb) : 0 +} + +function score(a, b) { + const ink = sim(a.ink, b.ink) + const hue = cosine(a.hue, b.hue) + const layout = cosine(a.grid, b.grid) + return { ink, hue, layout, overall: 0.25 * ink + 0.30 * hue + 0.45 * layout } +} + +// --------------------------------------------------------------------- main + +if (!existsSync(jsDir) || !existsSync(refDir)) { + console.error('compare: --js and --ref must both exist') + process.exit(2) +} + +const refs = new Set(readdirSync(refDir).filter((f) => f.endsWith('.png')).map((f) => basename(f, '.png'))) +const pairs = readdirSync(jsDir) + .filter((f) => f.endsWith('.png')) + .map((f) => basename(f, '.png')) + .filter((n) => refs.has(n)) + .sort() + +if (!pairs.length) { + console.error('compare: no filename matches between the two directories') + process.exit(1) +} + +const rows = [] +for (const name of pairs) { + const ja = decode(join(jsDir, `${name}.png`)) + const rb = decode(join(refDir, `${name}.png`)) + if (!ja || !rb) { rows.push({ name, unscored: true }); continue } + const da = describe(ja), db = describe(rb) + rows.push({ + name, + ...score(da, db), + js: { ink: da.ink, box: da.box, b64: readFileSync(join(jsDir, `${name}.png`)).toString('base64') }, + ref: { ink: db.ink, box: db.box, b64: readFileSync(join(refDir, `${name}.png`)).toString('base64') }, + }) +} + +rows.sort((a, b) => (a.overall ?? 2) - (b.overall ?? 2)) + +const pct = (v) => `${(v * 100).toFixed(0)}%` +const band = (v) => (v < 0.55 ? 'bad' : v < 0.75 ? 'warn' : 'good') +const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]) + +const html = `Renderer Comparison + +
    +
    +

    LaTeX2JS vs PSTricks · worst match first

    +

    Renderer Comparison

    +

    The same source rendered twice. Scores are a heuristic — SVG in a browser and Ghostscript output never match pixel for pixel — so treat them as an ordering that says where to look, not as a verdict.

    +
    + ${rows.map((r) => r.unscored ? ` +
    ${esc(r.name)} + unscored
    ` : ` +
    +
    + ${esc(r.name)} + ink ${pct(r.ink)} + colour ${pct(r.hue)} + layout ${pct(r.layout)} + ${pct(r.overall)} +
    +
    +

    LaTeX2JS

    ${esc(r.name)} rendered by LaTeX2JS
    +

    PSTricks reference

    ${esc(r.name)} rendered by PSTricks
    +
    +
    `).join('')} +
    ${rows.length} pairs · heuristic score = 25% ink + 30% colour + 45% layout occupancy
    +
    +` + +writeFileSync(outFile, html) +console.log(`compare: ${rows.length} pairs -> ${outFile} (${(Buffer.byteLength(html) / 1024 / 1024).toFixed(2)} MB)`) +for (const r of rows.slice(0, 12)) { + if (r.unscored) { console.log(` ???? ${r.name}`); continue } + console.log(` ${pct(r.overall).padStart(4)} ${r.name.padEnd(34)} ink=${pct(r.ink)} colour=${pct(r.hue)} layout=${pct(r.layout)}`) +} diff --git a/tools/pstricks-conformance/fuzz-corpus.mjs b/tools/pstricks-conformance/fuzz-corpus.mjs new file mode 100644 index 00000000..3dfe2b2a --- /dev/null +++ b/tools/pstricks-conformance/fuzz-corpus.mjs @@ -0,0 +1,200 @@ +#!/usr/bin/env node +/** + * fuzz-corpus.mjs — generate a systematic PSTricks test corpus. + * + * Each case is emitted twice from one source of truth: + * /tex/.tex the bare pspicture body, for LaTeX2JS + * /doc/.tex the same body wrapped in a standalone LaTeX document, + * for compiling with real PSTricks as ground truth + * /manifest.json id -> { command, axis, body, why } + * + * The point is coverage of the parameter space, not random noise: every case + * names the command and the axis it varies, so a visual diff against real + * PSTricks says exactly which feature is wrong. + * + * node fuzz-corpus.mjs --out ./corpus [--only psline,pscurve] + */ + +import { mkdirSync, writeFileSync, rmSync } from 'node:fs' +import { join } from 'node:path' + +// --------------------------------------------------------------- parameters + +/** Style axes applied across every drawable command. */ +const LINE_STYLES = ['solid', 'dashed', 'dotted', 'none'] +const FILL_STYLES = ['none', 'solid', 'hlines', 'vlines', 'crosshatch'] +const ARROWS = ['-', '->', '<-', '<->', '|-|', '->>'] +const WIDTHS = ['0.5pt', '1pt', '2pt', '4pt'] +const COLORS = ['black', 'red', 'blue', 'green', 'magenta'] + +/** + * Drawable commands and how to instantiate one. + * `body(opt)` takes a bracketed option string (possibly empty) and returns the + * command text; `fills` marks commands that accept a fill style. + */ +const COMMANDS = { + psline: { fills: false, body: (o) => `\\psline${o}(-2,-1)(0,1.5)(2,-1)` }, + psframe: { fills: true, body: (o) => `\\psframe${o}(-2,-1)(2,1)` }, + pspolygon: { fills: true, body: (o) => `\\pspolygon${o}(-2,-1)(0,1.5)(2,-1)(1,-1.5)` }, + pscircle: { fills: true, body: (o) => `\\pscircle${o}(0,0){1.5}` }, + psellipse: { fills: true, body: (o) => `\\psellipse${o}(0,0)(2,1)` }, + pswedge: { fills: true, body: (o) => `\\pswedge${o}(0,0){2}{30}{150}` }, + psarc: { fills: false, body: (o) => `\\psarc${o}(0,0){1.8}{20}{160}` }, + psbezier: { fills: true, body: (o) => `\\psbezier${o}(-2,-1)(-1,2)(1,-2)(2,1)` }, + pscurve: { fills: true, body: (o) => `\\pscurve${o}(-2,-1)(-1,1)(0,-0.5)(1,1.5)(2,0)` }, + psecurve: { fills: false, body: (o) => `\\psecurve${o}(-2,-1)(-1,1)(0,-0.5)(1,1.5)(2,0)` }, + psccurve: { fills: true, body: (o) => `\\psccurve${o}(-2,-1)(-1,1)(0,-0.5)(1,1.5)(2,0)` }, + psdots: { fills: false, body: (o) => `\\psdots${o}(-2,-1)(-1,0)(0,1)(1,0)(2,-1)` }, + psgrid: { fills: false, body: (o) => `\\psgrid${o}(-2,-2)(2,2)` }, + psplot: { fills: false, body: (o) => `\\psplot${o}{-2}{2}{x x mul}` }, + pscustom: { fills: true, body: (o) => `\\pscustom${o}{\\moveto(-2,-1)\\lineto(0,1.5)\\lineto(2,-1)\\closepath}` }, + psaxes: { fills: false, body: (o) => `\\psaxes${o}(0,0)(-2,-2)(2,2)` }, +} + +/** Cases that specifically probe draw order and interaction, not one command. */ +const LAYER_CASES = [ + { + id: 'layer-fill-then-line', + why: 'A solid fill authored before a line: the line must stay on top.', + body: `\\psframe[fillstyle=solid,fillcolor=lightgray](-2,-2)(2,2)\n\\psline[linewidth=2pt,linecolor=red](-2,-2)(2,2)`, + }, + { + id: 'layer-line-then-fill', + why: 'The reverse order: the fill must cover the line.', + body: `\\psline[linewidth=2pt,linecolor=red](-2,-2)(2,2)\n\\psframe[fillstyle=solid,fillcolor=lightgray](-2,-2)(2,2)`, + }, + { + id: 'layer-grid-under-fill', + why: 'psgrid authored first must sit behind a later filled shape.', + body: `\\psgrid(-2,-2)(2,2)\n\\pscircle[fillstyle=solid,fillcolor=cyan](0,0){1.5}`, + }, + { + id: 'layer-grid-over-fill', + why: 'psgrid authored last must sit in front of the fill.', + body: `\\pscircle[fillstyle=solid,fillcolor=cyan](0,0){1.5}\n\\psgrid(-2,-2)(2,2)`, + }, + { + id: 'layer-userline-first', + why: 'Interactive \\userline authored BEFORE a fill. Known-suspect: the initial draw respects source order, but the mousemove re-render removes and re-appends userlines, which can promote them above later elements.', + body: `\\userline[linewidth=2pt,linecolor=blue]{->}(0,0)(2,2)\n\\psframe[fillstyle=solid,fillcolor=lightgray](-1,-1)(1,1)`, + interactive: true, + }, + { + id: 'layer-psplot-first', + why: 'Same probe for \\psplot, which the re-render also removes and re-appends.', + body: `\\psplot[linewidth=2pt,linecolor=red]{-2}{2}{x x mul}\n\\psframe[fillstyle=solid,fillcolor=lightgray](-1,-1)(1,1)`, + interactive: true, + }, + { + id: 'layer-three-deep', + why: 'Three overlapping fills in a strict order; any reordering is obvious.', + body: `\\pscircle[fillstyle=solid,fillcolor=red](-0.6,0){1.2}\n\\pscircle[fillstyle=solid,fillcolor=green](0.6,0){1.2}\n\\pscircle[fillstyle=solid,fillcolor=blue](0,0.8){1.2}`, + }, + { + id: 'layer-rput-nested', + why: 'rput-placed content must land in source order relative to plain shapes.', + body: `\\psframe[fillstyle=solid,fillcolor=yellow](-2,-1)(2,1)\n\\rput(0,0){\\pscircle[fillstyle=solid,fillcolor=blue](0,0){0.6}}\n\\psline[linewidth=2pt](-2,-1)(2,1)`, + }, +] + +/** Degenerate and boundary inputs that should fail visibly, not silently. */ +const EDGE_CASES = [ + { id: 'edge-zero-radius', why: 'Zero-radius circle.', body: `\\pscircle(0,0){0}` }, + { id: 'edge-negative-radius', why: 'Negative radius; PSTricks takes the absolute value.', body: `\\pscircle(0,0){-1.5}` }, + { id: 'edge-inverted-frame', why: 'Corners given in reverse order.', body: `\\psframe(2,1)(-2,-1)` }, + { id: 'edge-single-point-line', why: 'A line with one coordinate.', body: `\\psline(0,0)` }, + { id: 'edge-out-of-bounds', why: 'Geometry far outside the pspicture bounds; must clip, not escape.', body: `\\psline[linewidth=2pt](-40,-40)(40,40)\n\\psframe(-2,-2)(2,2)` }, + { id: 'edge-arc-reversed', why: 'Arc whose end angle precedes its start angle.', body: `\\psarc(0,0){1.5}{200}{20}` }, + { id: 'edge-arc-over-360', why: 'Angles beyond a full turn.', body: `\\psarc(0,0){1.5}{0}{450}` }, + { id: 'edge-wedge-full', why: 'A wedge spanning the whole circle.', body: `\\pswedge[fillstyle=solid,fillcolor=orange](0,0){1.8}{0}{360}` }, + { id: 'edge-decimal-precision', why: 'Long decimals must not be truncated into visible error.', body: `\\psline(-1.9999,-0.9999)(1.9999,0.9999)` }, + { id: 'edge-plot-discontinuous', why: 'A function with a pole inside the plotted range.', body: `\\psplot{-2}{2}{1 x div}` }, + { id: 'edge-plot-constant', why: 'A constant function.', body: `\\psplot{-2}{2}{1}` }, + { id: 'edge-empty-pscustom', why: 'pscustom with no path operators.', body: `\\pscustom{}` }, + { id: 'edge-unknown-command', why: 'An unimplemented command must warn, not crash the whole picture.', body: `\\psline(-2,-1)(2,1)\n\\psunknowncmd(0,0){1}\n\\pscircle(0,0){1}` }, + { id: 'edge-nested-pspicture-content', why: 'Many elements in one picture.', body: Array.from({ length: 12 }, (_, i) => `\\pscircle[linecolor=${COLORS[i % COLORS.length]}](${(i % 5) - 2},${Math.floor(i / 5) - 1}){0.4}`).join('\n') }, +] + +// --------------------------------------------------------------- generation + +const PICTURE = ['(-3,-2.5)', '(3,2.5)'] + +function wrapPicture(body) { + return `\\begin{pspicture}${PICTURE[0]}${PICTURE[1]}\n${body}\n\\end{pspicture}` +} + +/** A standalone LaTeX document that real PSTricks can compile to PDF. */ +function wrapDocument(body) { + return `\\documentclass[border=4pt]{standalone} +\\usepackage{pstricks} +\\usepackage{pst-plot} +\\usepackage{pst-node} +\\usepackage{multido} +\\begin{document} +${wrapPicture(body)} +\\end{document} +` +} + +function* cases(only) { + const want = (name) => !only || only.includes(name) + + // one bare instance per command, the baseline every other case is read against + for (const [name, spec] of Object.entries(COMMANDS)) { + if (!want(name)) continue + yield { id: `${name}-plain`, command: name, axis: 'baseline', why: `${name} with no options.`, body: spec.body('') } + + for (const s of LINE_STYLES) { + yield { id: `${name}-linestyle-${s}`, command: name, axis: 'linestyle', why: `${name} with linestyle=${s}.`, body: spec.body(`[linestyle=${s}]`) } + } + for (const w of WIDTHS) { + yield { id: `${name}-linewidth-${w.replace('.', '_')}`, command: name, axis: 'linewidth', why: `${name} at linewidth=${w}.`, body: spec.body(`[linewidth=${w}]`) } + } + for (const c of COLORS) { + yield { id: `${name}-linecolor-${c}`, command: name, axis: 'linecolor', why: `${name} in ${c}.`, body: spec.body(`[linecolor=${c}]`) } + } + if (spec.fills) { + for (const f of FILL_STYLES) { + yield { id: `${name}-fillstyle-${f}`, command: name, axis: 'fillstyle', why: `${name} with fillstyle=${f}. Hatched styles are the likely gap.`, body: spec.body(`[fillstyle=${f},fillcolor=cyan]`) } + } + } + // arrows only mean something on open paths + if (['psline', 'psarc', 'pscurve', 'psbezier', 'psecurve', 'psplot'].includes(name)) { + for (const a of ARROWS) { + yield { id: `${name}-arrows-${a.replace(/[<>|-]/g, (ch) => ({ '<': 'l', '>': 'r', '|': 'b', '-': 'd' })[ch])}`, command: name, axis: 'arrows', why: `${name} with arrows=${a}.`, body: spec.body(`[arrows=${a}]`) } + } + } + // starred (filled) variants + if (spec.fills) { + yield { id: `${name}-starred`, command: name, axis: 'starred', why: `${name}* — the starred form is solid-filled in the line colour.`, body: spec.body('').replace(`\\${name}`, `\\${name}*`) } + } + } + + for (const c of LAYER_CASES) if (!only) yield { ...c, command: 'layering', axis: 'draw-order' } + for (const c of EDGE_CASES) if (!only) yield { ...c, command: 'edge', axis: 'degenerate' } +} + +// --------------------------------------------------------------------- main + +const argv = process.argv.slice(2) +const outDir = argv.includes('--out') ? argv[argv.indexOf('--out') + 1] : './corpus' +const only = argv.includes('--only') ? argv[argv.indexOf('--only') + 1].split(',') : null + +rmSync(outDir, { recursive: true, force: true }) +mkdirSync(join(outDir, 'tex'), { recursive: true }) +mkdirSync(join(outDir, 'doc'), { recursive: true }) + +const manifest = {} +let n = 0 +for (const c of cases(only)) { + writeFileSync(join(outDir, 'tex', `${c.id}.tex`), `${wrapPicture(c.body)}\n`) + writeFileSync(join(outDir, 'doc', `${c.id}.tex`), wrapDocument(c.body)) + manifest[c.id] = { command: c.command, axis: c.axis, why: c.why, body: c.body, interactive: !!c.interactive } + n++ +} +writeFileSync(join(outDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`) + +const byCommand = {} +for (const m of Object.values(manifest)) byCommand[m.command] = (byCommand[m.command] ?? 0) + 1 +console.log(`fuzz-corpus: ${n} cases -> ${outDir}`) +for (const [k, v] of Object.entries(byCommand).sort((a, b) => b[1] - a[1])) console.log(` ${String(v).padStart(4)} ${k}`) diff --git a/tools/pstricks-conformance/gallery.mjs b/tools/pstricks-conformance/gallery.mjs new file mode 100644 index 00000000..598bb5b8 --- /dev/null +++ b/tools/pstricks-conformance/gallery.mjs @@ -0,0 +1,336 @@ +#!/usr/bin/env node +/** + * gallery.mjs — turn a directory of PNG renders into one self-contained HTML + * review page, with every image inlined as a data URI. + * + * The output has no external references, so it can be published as an Artifact + * and opened from anywhere. + * + * node gallery.mjs --renders [--notes ] [--out ] + * + * The notes file is `{ "": { "status": "ok|flag|bug", "note": "..." } }`. + * A render with no entry is reported as unreviewed rather than silently passing. + */ + +import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs' +import { join, basename } from 'node:path' + +// ---------------------------------------------------------------- input + +function parseArgs(argv) { + const opts = { out: 'gallery.html' } + for (let i = 0; i < argv.length; i++) { + if (argv[i] === '--renders') opts.renders = argv[++i] + else if (argv[i] === '--notes') opts.notes = argv[++i] + else if (argv[i] === '--out') opts.out = argv[++i] + else if (argv[i] === '--title') opts.title = argv[++i] + } + if (!opts.renders) { + console.error('gallery: --renders is required') + process.exit(2) + } + return opts +} + +/** Reads width and height out of a PNG IHDR without decoding the image. */ +function pngSize(buf) { + if (buf.length < 24 || buf.readUInt32BE(0) !== 0x89504e47) return null + return { w: buf.readUInt32BE(16), h: buf.readUInt32BE(20) } +} + +/** Sorts numeric-prefixed plates naturally, then everything else alphabetically. */ +function plateOrder(a, b) { + const na = /^(\d+)/.exec(a), nb = /^(\d+)/.exec(b) + if (na && nb) return Number(na[1]) - Number(nb[1]) + if (na) return -1 + if (nb) return 1 + return a.localeCompare(b) +} + +// ---------------------------------------------------------------- markup + +const esc = (s) => String(s).replace(/[&<>"]/g, (ch) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[ch]) + +const STATUS = { + bug: { label: 'Bug', cls: 'bug' }, + flag: { label: 'Check', cls: 'flag' }, + ok: { label: 'Reviewed', cls: 'ok' }, + ref: { label: 'Reference', cls: 'ref' }, + unreviewed: { label: 'Not reviewed', cls: 'none' }, +} + +/** A render taller than this is presented in its own scrolling frame. */ +const TALL = 1200 + +function plateCard(p) { + const s = STATUS[p.status] ?? STATUS.unreviewed + return ` +
    +
    + ${esc(p.name)} render +
    +
    + ${esc(p.name)} + ${p.w} × ${p.h} + ${s.label} +
    + ${p.note ? `

    ${esc(p.note)}

    ` : ''} +
    ` +} + +function render(plates, opts) { + const bugs = plates.filter((p) => p.status === 'bug') + const flags = plates.filter((p) => p.status === 'flag') + const clean = plates.filter((p) => p.status === 'ok').length + const short = plates.filter((p) => !p.tall) + const tall = plates.filter((p) => p.tall) + const bytes = plates.reduce((n, p) => n + p.bytes, 0) + const title = opts.title ?? 'Render Review' + + return `${esc(title)} + + +
    +
    +

    ${esc(opts.eyebrow ?? 'Contact sheet')}

    +

    ${esc(title)}

    + ${opts.lede ? `

    ${esc(opts.lede)}

    ` : ''} +
    + +
    +
    Plates
    ${plates.length}
    +
    Reviewed clean
    ${clean}
    +
    Bugs
    ${bugs.length}
    +
    To check
    ${flags.length}
    +
    Total size
    ${(bytes / 1024 / 1024).toFixed(1)} MB
    +
    + + ${bugs.length || flags.length ? ` +
    +

    Needs attention

    + ${[...bugs, ...flags].map((p) => ` +
    +
    ${esc(p.name)}
    +

    ${esc(p.note ?? '')}

    +
    `).join('')} +
    ` : ''} + +
    +

    Examples — ${short.length} plates

    +
    ${short.map(plateCard).join('')} +
    +
    + + ${tall.length ? ` +
    +

    Full-page captures — scroll inside each frame

    +
    ${tall.map(plateCard).join('')} +
    +
    ` : ''} + +
    + Source: ${esc(opts.renders)} + Regenerate: pnpm e2e:gallery +
    +
    +` +} + +// ---------------------------------------------------------------- main + +const opts = parseArgs(process.argv.slice(2)) +const notes = opts.notes ? JSON.parse(readFileSync(opts.notes, 'utf8')) : {} + +const files = readdirSync(opts.renders).filter((f) => f.toLowerCase().endsWith('.png')).sort(plateOrder) +if (files.length === 0) { + console.error(`gallery: no PNGs in ${opts.renders}`) + process.exit(1) +} + +const plates = files.map((name) => { + const path = join(opts.renders, name) + const buf = readFileSync(path) + const size = pngSize(buf) ?? { w: 0, h: 0 } + const entry = notes[name] ?? {} + return { + name: basename(name), + b64: buf.toString('base64'), + bytes: statSync(path).size, + w: size.w, + h: size.h, + tall: size.h > TALL, + status: entry.status ?? 'unreviewed', + note: entry.note, + } +}) + +const html = render(plates, { ...opts, ...(notes.__meta ?? {}) }) +writeFileSync(opts.out, html) + +const mb = (Buffer.byteLength(html) / 1024 / 1024).toFixed(2) +console.log(`gallery: ${plates.length} plates -> ${opts.out} (${mb} MB)`) +if (Buffer.byteLength(html) > 16 * 1024 * 1024) { + console.error('gallery: WARNING — over the 16 MB artifact limit; downscale the largest renders') +} diff --git a/tools/pstricks-conformance/render-examples.mjs b/tools/pstricks-conformance/render-examples.mjs new file mode 100644 index 00000000..c4810356 --- /dev/null +++ b/tools/pstricks-conformance/render-examples.mjs @@ -0,0 +1,347 @@ +#!/usr/bin/env node +/** + * render-examples.mjs — render the real LaTeX2JS example corpus with real PSTricks. + * + * The examples are not directly compilable: they use LaTeX2JS-only macros + * (\userline, \uservariable, \slider), CSS colour names PSTricks has never + * heard of, and several files pack many pictures into one document. This + * splits, shims and wraps them, then rasterizes each picture separately so a + * picture is the unit of comparison. + * + * node render-examples.mjs --corpus --out [--dpi 150] + * + * Output: + * /doc/.tex standalone document per picture + * /ref/.png its PSTricks rasterization + * /manifest.json id -> { source, index, shims, body } + */ + +import { readFileSync, writeFileSync, readdirSync, mkdirSync, rmSync, existsSync } from 'node:fs' +import { join, resolve, basename } from 'node:path' +import { spawnSync } from 'node:child_process' + +const IMAGE = 'pyramation/pstricks-latex:latest' + +/** + * CSS colour names the examples use that PSTricks does not define. + * Values are the CSS ones, so the reference matches what the browser draws. + */ +const COLORS = { + lightblue: '0.678,0.847,0.902', + lightgray: '0.827,0.827,0.827', + purple: '0.502,0.000,0.502', + orange: '1.000,0.647,0.000', + gray: '0.502,0.502,0.502', +} + +// ------------------------------------------------------------------ shimming + +/** + * Consumes balanced `{...}` groups starting at `i`, returning the index after + * the last one. Used to drop \userline's trailing expression arguments, which + * only mean something to the interactive renderer. + */ +function skipBraceGroups(src, i) { + for (;;) { + let j = i + while (j < src.length && /\s/.test(src[j])) j++ + if (src[j] !== '{') return i + let depth = 0 + let k = j + for (; k < src.length; k++) { + if (src[k] === '{') depth++ + else if (src[k] === '}') { depth--; if (depth === 0) { k++; break } } + } + if (depth !== 0) return i + i = k + } +} + +/** Consumes one `(...)` group, returning the index after it, or -1. */ +function skipParen(src, i) { + let j = i + while (j < src.length && /\s/.test(src[j])) j++ + if (src[j] !== '(') return -1 + const end = src.indexOf(')', j) + return end === -1 ? -1 : end + 1 +} + +/** + * Default binding for a \uservariable, whose real value is the live cursor + * position. Ground truth has no cursor, so every such variable is pinned and + * the binding is recorded — a JS-side render must use the same value for the + * comparison to mean anything. + */ +const PINNED_USERVAR = 1 + +/** + * Rewrites LaTeX2JS-only macros into their static PSTricks equivalent. + * \userline collapses to the \psline it draws before any interaction; + * \uservariable and \slider are control-plane only and draw nothing, but both + * bind names that plot expressions go on to reference. + */ +function shim(src) { + const applied = new Set() + const bindings = {} + + // \slider{min}{max}{var}{label}{init} — the 5th group is the initial value + for (const m of src.matchAll(/\\slider\{([^{}]*)\}\{([^{}]*)\}\{([^{}]*)\}\{((?:[^{}]|\{[^{}]*\})*)\}\{([^{}]*)\}/g)) { + bindings[m[3].trim()] = m[5].trim() + } + // \uservariable{name}(x,y){expr} — cursor-driven, so pin it + for (const m of src.matchAll(/\\uservariable\{([^{}]*)\}/g)) { + const name = m[1].trim() + if (!(name in bindings)) bindings[name] = String(PINNED_USERVAR) + } + + let out = '' + let i = 0 + + while (i < src.length) { + if (src.startsWith('\\userline', i)) { + let j = i + '\\userline'.length + let opts = '' + if (src[j] === '[') { const e = src.indexOf(']', j); opts = src.slice(j, e + 1); j = e + 1 } + // optional arrow spec {->} — one group that is not a coordinate + let arrows = '' + const m = /^\s*\{([^{}]*)\}/.exec(src.slice(j)) + if (m && /^[-<>|*ocC\[\]() ]*$/.test(m[1])) { arrows = `{${m[1]}}`; j += m[0].length } + const p1 = skipParen(src, j) + if (p1 === -1) { out += src[i++]; continue } + const p2 = skipParen(src, p1) + if (p2 === -1) { out += src[i++]; continue } + const coords = src.slice(j, p2).trim() + const after = skipBraceGroups(src, p2) + out += `\\psline${opts}${arrows}${coords}` + applied.add('userline') + i = after + continue + } + + if (src.startsWith('\\uservariable', i) || src.startsWith('\\slider', i)) { + const name = src.startsWith('\\slider', i) ? 'slider' : 'uservariable' + let j = i + (name === 'slider' ? '\\slider'.length : '\\uservariable'.length) + // both are a run of {...} and (...) groups that draw nothing + for (;;) { + const b = skipBraceGroups(src, j) + const p = skipParen(src, b) + if (p !== -1) { j = p; continue } + if (b !== j) { j = b; continue } + break + } + applied.add(name) + i = j + continue + } + + out += src[i++] + } + + // bare linewidth numbers need a unit; `1.5 pt` is already valid TeX + out = out.replace(/linewidth=(\d+(?:\.\d+)?)(?=[,\]])/g, 'linewidth=$1pt') + // plotpoints is a count, but the corpus writes it as a dimension + out = out.replace(/plotpoints=(\d+(?:\.\d+)?)\s*(pt|cm|mm|in)/g, 'plotpoints=$1') + + const { text, freeVars } = normalizePlots(out, bindings) + return { text, shims: [...applied], bindings, freeVars } +} + +/** + * Rewrites `pow(a, b)` as `(a)^(b)`. LaTeX2JS evaluates plot bodies as + * JavaScript, so it inherits Math.pow; pst-plot's algebraic parser has no such + * function and only understands the `^` operator. + */ +function rewritePow(src) { + for (;;) { + const at = src.indexOf('pow(') + if (at === -1) return src + let depth = 0 + let split = -1 + let end = -1 + for (let i = at + 3; i < src.length; i++) { + const ch = src[i] + if (ch === '(') depth++ + else if (ch === ')') { depth--; if (depth === 0) { end = i; break } } + else if (ch === ',' && depth === 1) split = i + } + if (end === -1 || split === -1) return src + const base = rewritePow(src.slice(at + 4, split)) + const exp = rewritePow(src.slice(split + 1, end)) + src = `${src.slice(0, at)}(${base.trim()})^(${exp.trim()})${src.slice(end + 1)}` + } +} + +/** + * LaTeX2JS accepts infix plot expressions with named variables; PSTricks reads + * RPN PostScript unless told otherwise, and has no such variables. Each psplot + * body is rewritten to the algebraic dialect with its free names substituted. + * Names that remain unbound are reported so the unit can be excluded rather + * than rendered as a misleading reference. + */ +function normalizePlots(src, bindings) { + const unresolved = new Set() + + const text = src.replace( + /\\psplot(\[[^\]]*\])?\{([^{}]*)\}\{([^{}]*)\}\{([^{}]*)\}/g, + (_all, opts, from, to, body) => { + const sub = (s) => s.replace(/\b([A-Za-z_]\w*)\b(?!\s*\()/g, (name) => { + if (name === 'x' || name === 'e' || name === 'Pi') return name + if (name in bindings) return bindings[name] + unresolved.add(name) + return name + }) + + // implicit multiplication (`0.5x^3`) is not valid in either dialect + const fix = (s) => rewritePow(sub(s)).replace(/(\d)\s*([A-Za-z_(])/g, '$1*$2') + + /** + * Plot bounds must reach PostScript as literal numbers. The corpus writes + * them as arithmetic over bound variables (`alpha-3`), so once the + * variables are substituted the remaining arithmetic is folded here. + */ + const bound = (s) => { + const t = fix(s) + if (!/^[\d\s+\-*/.()]+$/.test(t)) return t + try { + const v = Function(`"use strict";return (${t})`)() + return Number.isFinite(v) ? String(Number(v.toFixed(6))) : t + } catch { return t } + } + + // the corpus writes a bare `algebraic` key, which pst-plot does not accept + let o = (opts ?? '').replace(/(^|[[,])\s*algebraic\s*(?=[,\]])/g, '$1algebraic=true') + if (!/algebraic\s*=/.test(o)) { + o = o ? `${o.slice(0, -1)},algebraic=true]` : '[algebraic=true]' + } + // plotpoints=1 appears in the corpus; PSTricks rejects anything below 2 + o = o.replace(/plotpoints=(\d+)/g, (_m, n) => `plotpoints=${Math.max(2, Number(n))}`) + return `\\psplot${o}{${bound(from)}}{${bound(to)}}{${fix(body)}}` + }, + ) + + return { text, freeVars: [...unresolved] } +} + +/** Splits a file into its pspicture blocks; a file with none is one whole-document unit. */ +function splitPictures(src) { + const blocks = [] + const re = /\\begin\{pspicture\}[\s\S]*?\\end\{pspicture\}/g + let m + while ((m = re.exec(src))) blocks.push(m[0]) + return blocks +} + +function wrapDocument(body, { document: whole }) { + const defs = Object.entries(COLORS).map(([n, v]) => `\\definecolor{${n}}{rgb}{${v}}`).join('\n') + const packages = [ + '\\usepackage{pstricks}', + '\\usepackage{pst-plot}', + '\\usepackage{pst-node}', + '\\usepackage{multido}', + '\\usepackage{amsmath,amssymb,amsthm}', + '\\usepackage{xcolor}', + '\\usepackage{hyperref}', + ].join('\n') + + // A whole-document unit needs real page geometry; a lone picture is cropped tight. + const cls = whole + ? '\\documentclass[11pt]{article}\n\\usepackage[paperwidth=7in,paperheight=11in,margin=0.5in]{geometry}\n\\pagestyle{empty}' + : '\\documentclass[border=6pt]{standalone}' + + const envs = whole + ? '\\newtheorem{theorem}{Theorem}\n\\newtheorem{lemma}{Lemma}\n\\newtheorem{proposition}{Proposition}\n\\newtheorem{definition}{Definition}\n' + : '' + + return `${cls}\n${packages}\n${defs}\n${envs}\\begin{document}\n${body}\n\\end{document}\n` +} + +// --------------------------------------------------------------------- main + +const argv = process.argv.slice(2) +const flag = (n, d) => (argv.includes(n) ? argv[argv.indexOf(n) + 1] : d) + +const corpusDir = resolve(flag('--corpus', '.')) +const outDir = resolve(flag('--out', './examples-ref')) +const dpi = flag('--dpi', '150') +const jobs = flag('--jobs', '6') + +if (!existsSync(corpusDir)) { + console.error(`render-examples: no such directory ${corpusDir}`) + process.exit(2) +} + +rmSync(outDir, { recursive: true, force: true }) +mkdirSync(join(outDir, 'doc'), { recursive: true }) +mkdirSync(join(outDir, 'ref'), { recursive: true }) + +const manifest = {} +const files = readdirSync(corpusDir).filter((f) => f.endsWith('.tex')).sort() + +for (const file of files) { + const stem = basename(file, '.tex') + const raw = readFileSync(join(corpusDir, file), 'utf8') + const { text, shims, bindings, freeVars } = shim(raw) + const pictures = splitPictures(text) + + if (pictures.length === 0) { + const id = stem + writeFileSync(join(outDir, 'doc', `${id}.tex`), wrapDocument(text, { document: true })) + manifest[id] = { source: file, index: null, kind: 'document', shims, bindings, freeVars, body: text.slice(0, 400) } + continue + } + + pictures.forEach((body, n) => { + const id = pictures.length === 1 ? stem : `${stem}--p${String(n + 1).padStart(2, '0')}` + writeFileSync(join(outDir, 'doc', `${id}.tex`), wrapDocument(body, { document: false })) + manifest[id] = { source: file, index: n + 1, kind: 'picture', shims, bindings, freeVars, body } + }) +} + +writeFileSync(join(outDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`) + +const ids = Object.keys(manifest) +console.log(`render-examples: ${files.length} files -> ${ids.length} units`) +const shimmed = ids.filter((k) => manifest[k].shims.length) +if (shimmed.length) console.log(` ${shimmed.length} needed extension shims (userline/uservariable/slider)`) + +// ---- rasterize ------------------------------------------------------------- + +const script = ` +set -u +mkdir -p /w/ref /w/ref/logs +render() { + n="$1"; d=$(mktemp -d); cd "$d" + cp "/w/doc/$n.tex" a.tex 2>/dev/null || { echo "MISSING $n"; return; } + if ! latex -interaction=nonstopmode a.tex >latex.log 2>&1; then + cp latex.log "/w/ref/logs/$n.log"; echo "FAIL-LATEX $n"; cd /; rm -rf "$d"; return + fi + dvips -q -o a.ps a.dvi >/dev/null 2>&1 || { echo "FAIL-DVIPS $n"; cd /; rm -rf "$d"; return; } + ps2pdf -dEPSCrop a.ps a.pdf >/dev/null 2>&1 || { echo "FAIL-PS2PDF $n"; cd /; rm -rf "$d"; return; } + gs -q -dNOPAUSE -dBATCH -dALLOWPSTRANSPARENCY -sDEVICE=png16m -r${dpi} \ + -sOutputFile="/w/ref/${'$'}{n}.png" a.pdf >/dev/null 2>&1 || { echo "FAIL-GS $n"; cd /; rm -rf "$d"; return; } + echo "OK $n"; cd /; rm -rf "$d" +} +export -f render +printf '%s\\n' ${ids.map((i) => `'${i}'`).join(' ')} | xargs -P ${jobs} -I{} bash -c 'render "$@"' _ {} +` + +console.log(`render-examples: rasterizing at ${dpi} dpi, ${jobs} parallel`) +const t0 = Date.now() +const run = spawnSync('docker', [ + 'run', '--rm', '--platform', 'linux/amd64', '-v', `${outDir}:/w`, '-w', '/w', + IMAGE, 'bash', '-lc', script, +], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }) + +if (run.error) { console.error(`render-examples: ${run.error.message}`); process.exit(1) } + +const results = {} +for (const line of (run.stdout ?? '').split('\n').filter(Boolean)) { + const [status, name] = line.split(' ') + if (name) results[name] = status +} +writeFileSync(join(outDir, 'ref', 'status.json'), `${JSON.stringify(results, null, 2)}\n`) + +const ok = Object.values(results).filter((s) => s === 'OK').length +const failed = Object.entries(results).filter(([, s]) => s !== 'OK') +console.log(`render-examples: ${ok}/${ids.length} rendered in ${((Date.now() - t0) / 1000).toFixed(0)}s`) +for (const [n, s] of failed) console.log(` ${s.padEnd(12)} ${n}`) diff --git a/tools/pstricks-conformance/render-reference.mjs b/tools/pstricks-conformance/render-reference.mjs new file mode 100644 index 00000000..26df51be --- /dev/null +++ b/tools/pstricks-conformance/render-reference.mjs @@ -0,0 +1,95 @@ +#!/usr/bin/env node +/** + * render-reference.mjs — rasterize a PSTricks corpus with real LaTeX. + * + * Runs the whole corpus inside one container (latex -> dvips -> ps2pdf -> gs), + * producing the ground-truth PNG for every case. These are what the LaTeX2JS + * SVG output is judged against. + * + * node render-reference.mjs --corpus ./pstricks-corpus [--jobs 4] [--dpi 150] + * + * PSTricks emits PostScript specials, so the DVI->PS route is required; + * pdflatex cannot render these documents directly. + */ + +import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs' +import { join, resolve } from 'node:path' +import { spawnSync } from 'node:child_process' + +const IMAGE = 'pyramation/pstricks-latex:latest' + +const argv = process.argv.slice(2) +const flag = (name, fallback) => (argv.includes(name) ? argv[argv.indexOf(name) + 1] : fallback) + +const corpus = resolve(flag('--corpus', './pstricks-corpus')) +const dpi = flag('--dpi', '150') +const jobs = flag('--jobs', '4') +const docDir = join(corpus, 'doc') + +if (!existsSync(docDir)) { + console.error(`render-reference: no doc/ directory in ${corpus} — run fuzz-corpus.mjs first`) + process.exit(2) +} + +const names = readdirSync(docDir).filter((f) => f.endsWith('.tex')).map((f) => f.replace(/\.tex$/, '')) +mkdirSync(join(corpus, 'ref'), { recursive: true }) + +/** + * Compiles every case inside the container. Each case is isolated in its own + * scratch directory so one failure cannot poison the next, and a failing case + * writes its LaTeX log next to the output for diagnosis instead of vanishing. + */ +const script = ` +set -u +mkdir -p /w/ref /w/ref/logs +render() { + n="$1" + d=$(mktemp -d) + cp "/w/doc/$n.tex" "$d/a.tex" 2>/dev/null || { echo "MISSING $n"; return; } + cd "$d" + if ! latex -interaction=nonstopmode -halt-on-error a.tex >latex.log 2>&1; then + cp latex.log "/w/ref/logs/$n.log"; echo "FAIL-LATEX $n"; cd /; rm -rf "$d"; return + fi + if ! dvips -q -o a.ps a.dvi >dvips.log 2>&1; then + cp dvips.log "/w/ref/logs/$n.log"; echo "FAIL-DVIPS $n"; cd /; rm -rf "$d"; return + fi + ps2pdf -dEPSCrop a.ps a.pdf >/dev/null 2>&1 || { echo "FAIL-PS2PDF $n"; cd /; rm -rf "$d"; return; } + gs -q -dNOPAUSE -dBATCH -dALLOWPSTRANSPARENCY -sDEVICE=png16m -r${dpi} \ + -sOutputFile="/w/ref/$n.png" a.pdf >/dev/null 2>&1 || { echo "FAIL-GS $n"; cd /; rm -rf "$d"; return; } + echo "OK $n" + cd /; rm -rf "$d" +} +export -f render +printf '%s\\n' ${names.map((n) => `'${n}'`).join(' ')} | xargs -P ${jobs} -I{} bash -c 'render "$@"' _ {} +` + +console.log(`render-reference: ${names.length} cases at ${dpi} dpi, ${jobs} parallel`) +const t0 = Date.now() +const run = spawnSync('docker', [ + 'run', '--rm', '--platform', 'linux/amd64', + '-v', `${corpus}:/w`, '-w', '/w', + IMAGE, 'bash', '-lc', script, +], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }) + +if (run.error) { + console.error(`render-reference: ${run.error.message}`) + process.exit(1) +} + +const lines = (run.stdout ?? '').split('\n').filter(Boolean) +const results = {} +for (const l of lines) { + const [status, name] = l.split(' ') + if (name) results[name] = status +} +const ok = Object.values(results).filter((s) => s === 'OK').length +const failed = Object.entries(results).filter(([, s]) => s !== 'OK') + +writeFileSync(join(corpus, 'ref', 'status.json'), `${JSON.stringify(results, null, 2)}\n`) + +console.log(`render-reference: ${ok}/${names.length} rendered in ${((Date.now() - t0) / 1000).toFixed(0)}s`) +if (failed.length) { + console.log(`\n${failed.length} failed — logs in ref/logs/:`) + for (const [n, s] of failed.slice(0, 25)) console.log(` ${s.padEnd(12)} ${n}`) + if (failed.length > 25) console.log(` ... and ${failed.length - 25} more`) +} From 6d049c4ec9757362792abcc2608e18f05815f873 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:04:40 -0700 Subject: [PATCH 03/22] fix(latex2js): apply text transforms in sequence, and close proofs with a tombstone parseTextExpression matched the pristine line while matchrepl replaced inside the accumulated contents, so any macro whose argument an earlier transform had already rewritten silently failed to replace. \section{Cauchy--Schwarz} was left as literal source text once -- had become an en dash; the same held for \subsection, \textbf, \textit and \footnote over dashes and quotes. Matching the accumulated value fixes the whole class. The proof environment emitted $\qed$, and MathJax defines no such macro, so every proof ended in a visible "Undefined control sequence" box. It now emits the open square amsthm uses, set flush right. Both defects rendered visibly wrong while the suite stayed green, so they are covered by tests over the interaction that produced them. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- packages/css/latex2js.css | 6 +++ packages/latex2js/src/lib/headers.ts | 5 ++- packages/latex2js/src/lib/parser.ts | 12 ++++- .../latex2js/test/text-transforms.test.ts | 45 +++++++++++++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 packages/latex2js/test/text-transforms.test.ts diff --git a/packages/css/latex2js.css b/packages/css/latex2js.css index 64e25757..d838e8a3 100644 --- a/packages/css/latex2js.css +++ b/packages/css/latex2js.css @@ -27,6 +27,12 @@ p.quotation { font-size: 10pt; } +/* End-of-proof tombstone, set flush right as amsthm places it. */ +span.qed { + display: block; + text-align: right; +} + .nicebox { margin-top: 20px; min-height: 20px; diff --git a/packages/latex2js/src/lib/headers.ts b/packages/latex2js/src/lib/headers.ts index 6df77df0..e150fdbb 100644 --- a/packages/latex2js/src/lib/headers.ts +++ b/packages/latex2js/src/lib/headers.ts @@ -63,7 +63,10 @@ export const Functions = { example: () => '

    Example

    ', problem: () => '

    Problem

    ', proof: () => '

    Proof

    ', - qed: () => '$\\qed$', + // amsthm closes a proof with an open square. Emitted as a character rather + // than as math: MathJax defines no \qed, so the previous `$\qed$` surfaced + // an "Undefined control sequence" box at the end of every proof. + qed: () => '', solution: () => '

    Solution

    ', theorem: () => '

    Theorem

    ' }; diff --git a/packages/latex2js/src/lib/parser.ts b/packages/latex2js/src/lib/parser.ts index f321dd0a..75a8f2cf 100644 --- a/packages/latex2js/src/lib/parser.ts +++ b/packages/latex2js/src/lib/parser.ts @@ -512,8 +512,16 @@ class Parser { // Text / header transforms (reused from the old parser, string-based) // ------------------------------------------------------------------------- - parseTextExpression(line: string, exp: RegExp, k: string, contents: string): string { - var match = line.match(exp); + /** + * Text transforms run in sequence over one line, so each must match the + * value the previous ones produced. Matching the pristine line instead makes + * `matchrepl` search `contents` for a literal that an earlier transform has + * already rewritten, and the replacement silently does nothing — which is + * why `\section{Cauchy--Schwarz}` survived as source text once `--` had + * become an en dash. + */ + parseTextExpression(_line: string, exp: RegExp, k: string, contents: string): string { + var match = contents.match(exp); if (match) { return this.Text.Functions[k].call(this, match, contents); } diff --git a/packages/latex2js/test/text-transforms.test.ts b/packages/latex2js/test/text-transforms.test.ts new file mode 100644 index 00000000..bddcd125 --- /dev/null +++ b/packages/latex2js/test/text-transforms.test.ts @@ -0,0 +1,45 @@ +import LaTeX2JS from '../src'; + +/** + * Text transforms run in sequence over a line, so a macro whose argument + * contains something an earlier transform rewrites is the case that breaks. + * Both defects these cover rendered visibly wrong while the suite stayed green. + */ +const render = (tex: string): string => { + const parsed: any = new LaTeX2JS().parse(tex); + return parsed.map((seg: any) => (seg.lines || []).join('\n')).join('\n'); +}; + +describe('text macros whose arguments are themselves transformed', () => { + it('converts \\section even when the title contains an en dash', () => { + const out = render('\\section{The Cauchy--Schwarz Inequality}\n'); + expect(out).toContain('

    The Cauchy–Schwarz Inequality

    '); + expect(out).not.toContain('\\section'); + }); + + it.each([ + ['\\subsection{A--B}', '

    '], + ['\\textbf{a--b}', ''], + ['\\textit{a---b}', ''], + ['\\footnote{a--b}', ' { + const out = render(`${tex}\n`); + expect(out).toContain(tag); + expect(out).not.toContain(tex.slice(0, tex.indexOf('{'))); + }); + + it('leaves a title alone when nothing else rewrites it', () => { + expect(render('\\section{Plain Title}\n')).toContain('

    Plain Title

    '); + }); +}); + +describe('proof environment', () => { + it('closes with a tombstone character rather than an undefined macro', () => { + const out = render('\\begin{proof}\nBody.\n\\end{proof}\n'); + expect(out).toContain('

    Proof

    '); + expect(out).toContain(''); + // MathJax defines no \qed, so emitting it as math produced a visible + // "Undefined control sequence" box at the end of every proof. + expect(out).not.toContain('\\qed'); + }); +}); From 3fab54fe2553a4074880e9bc27dc47e9cd272fc5 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:15:57 -0700 Subject: [PATCH 04/22] fix(pstricks): invert arc sweep so wedges and arcs bow the correct way MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Y transform inverts the axis, so PSTricks' counter-clockwise sweep is SVG's sweep-flag 0. Both psarc and pswedge passed 1, which traces the complementary arc: every wedge bowed inward, turning 17-pie's five slices into a star and leaving the circle unfilled. Sweep span is now normalised into [0, 2pi), so an end angle preceding the start takes the long way round as PSTricks does, and the large-arc flag follows the real span rather than an absolute difference. A full turn cannot be one SVG arc — start and end coincide — so it is emitted as two half-turns. Sweep direction is invisible to data-extraction tests, so these assert the emitted path. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- packages/pstricks/src/lib/psgraph.ts | 75 +++++++++++++----- packages/pstricks/test/arc-geometry.test.ts | 87 +++++++++++++++++++++ 2 files changed, 143 insertions(+), 19 deletions(-) create mode 100644 packages/pstricks/test/arc-geometry.test.ts diff --git a/packages/pstricks/src/lib/psgraph.ts b/packages/pstricks/src/lib/psgraph.ts index 0e7c8cc7..0c428920 100644 --- a/packages/pstricks/src/lib/psgraph.ts +++ b/packages/pstricks/src/lib/psgraph.ts @@ -71,6 +71,45 @@ function buildCurvePath(data: number[], closed: boolean): string { return d; } +const TAU = Math.PI * 2; + +/** + * SVG arc flags for a PSTricks arc running from `angleA` to `angleB`. + * + * PSTricks always sweeps counter-clockwise in its own coordinates, taking the + * long way round when the end angle precedes the start. `Y` inverts the axis, + * so that counter-clockwise sweep is drawn with SVG's sweep-flag 0 — using 1 + * traces the complementary arc, which is what bowed every `\pswedge` inward + * and turned a pie chart into a star. + * + * @param angleA - start angle in radians + * @param angleB - end angle in radians + * @returns the sweep span plus SVG's large-arc and sweep flags + */ +function arcFlags(angleA: number, angleB: number): { delta: number; large: number; sweep: number } { + let delta = angleB - angleA; + if (!isFinite(delta)) delta = 0; + delta = ((delta % TAU) + TAU) % TAU; + return { delta, large: delta > Math.PI ? 1 : 0, sweep: 0 }; +} + +/** + * A full turn cannot be expressed as one SVG arc, because the start and end + * points coincide. Such a sweep is emitted as two half-turns instead. + * + * @param cx - centre x in device units + * @param cy - centre y in device units + * @param r - radius in device units + * @returns a closed circular path + */ +function fullCirclePath(cx: number, cy: number, r: number): string { + return ( + 'M ' + (cx - r) + ' ' + cy + + ' A ' + r + ' ' + r + ' 0 1 0 ' + (cx + r) + ' ' + cy + + ' A ' + r + ' ' + r + ' 0 1 0 ' + (cx - r) + ' ' + cy + ' Z' + ); +} + function curveRenderer(this: any, svg: any): void { const d = buildCurvePath(this.data, !!this.closed); if (!d) return; @@ -224,17 +263,16 @@ const psgraph: any = { }, psarc(svg: any): void { - const sweep = this.angleB - this.angleA > 0 ? 1 : 0; - const large = Math.abs(this.angleB - this.angleA) > Math.PI ? 1 : 0; + const { delta, large, sweep } = arcFlags(this.angleA, this.angleB); const filled = this.filled || this.fillstyle === 'solid'; - const d = filled - ? 'M ' + this.cx + ' ' + this.cy + - ' L ' + this.A.x + ' ' + this.A.y + - ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + - ' ' + this.B.x + ' ' + this.B.y + ' Z' - : 'M ' + this.A.x + ' ' + this.A.y + - ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + - ' ' + this.B.x + ' ' + this.B.y; + const arc = + ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + + ' ' + this.B.x + ' ' + this.B.y; + const d = delta === 0 + ? fullCirclePath(this.cx, this.cy, this.r) + : filled + ? 'M ' + this.cx + ' ' + this.cy + ' L ' + this.A.x + ' ' + this.A.y + arc + ' Z' + : 'M ' + this.A.x + ' ' + this.A.y + arc; svg .append('svg:path') .attr('d', d) @@ -829,17 +867,16 @@ const psgraph: any = { psccurve: curveRenderer, pswedge(svg: any): void { - const sweep = this.angleB - this.angleA > 0 ? 1 : 0; - const large = Math.abs(this.angleB - this.angleA) > Math.PI ? 1 : 0; - svg - .append('svg:path') - .attr( - 'd', - 'M ' + this.cx + ' ' + this.cy + + const { delta, large, sweep } = arcFlags(this.angleA, this.angleB); + const d = delta === 0 + ? fullCirclePath(this.cx, this.cy, this.r) + : 'M ' + this.cx + ' ' + this.cy + ' L ' + this.A.x + ' ' + this.A.y + ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + - ' ' + this.B.x + ' ' + this.B.y + ' Z' - ) + ' ' + this.B.x + ' ' + this.B.y + ' Z'; + svg + .append('svg:path') + .attr('d', d) .style('stroke-width', this.linewidth) .style('stroke', this.linecolor) .style('stroke-opacity', 1) diff --git a/packages/pstricks/test/arc-geometry.test.ts b/packages/pstricks/test/arc-geometry.test.ts new file mode 100644 index 00000000..90626876 --- /dev/null +++ b/packages/pstricks/test/arc-geometry.test.ts @@ -0,0 +1,87 @@ +import { Expressions, Functions } from '../src/lib/pstricks'; +import psgraph from '../src/lib/psgraph'; + +/** + * Arc sweep direction is invisible to data-extraction tests: the parsed angles + * are correct either way, and only the emitted SVG path says which way round + * the arc actually goes. A wedge drawn with the wrong sweep flag still fills, + * still has the right colour, and still passes a render smoke test — it just + * bows inward, which is how a pie chart rendered as a five-pointed star. + */ +function makeContext() { + return { + xunit: 50, yunit: 50, + x0: -5, y0: -5, x1: 5, y1: 5, + w: 10, h: 10, + variables: {}, + } as any; +} + +/** Collects the `d` attribute of every path a renderer appends. */ +function pathsFrom(name: string, raw: string): string[] { + const ctx = makeContext(); + const m = raw.match((Expressions as any)[name]) as RegExpMatchArray; + expect(m).not.toBeNull(); + const data = (Functions as any)[name].call(ctx, m); + data.global = ctx; + + const out: string[] = []; + const node = { + attr(key: string, value: string) { if (key === 'd') out.push(value); return node; }, + style() { return node; }, + on() { return node; }, + }; + const svg = { append: () => node }; + (psgraph as any)[name].call(data, svg); + return out; +} + +/** Pulls the flags out of an `A rx ry rot large sweep x y` command. */ +function arcCommand(d: string) { + const m = /A\s+([\d.-]+)\s+([\d.-]+)\s+([\d.-]+)\s+([01])\s+([01])\s+([\d.-]+)\s+([\d.-]+)/.exec(d); + expect(m).not.toBeNull(); + return { r: Number(m![1]), large: m![4], sweep: m![5], x: Number(m![6]), y: Number(m![7]) }; +} + +describe('arc sweep direction', () => { + // The Y transform inverts the axis, so PSTricks' counter-clockwise sweep is + // SVG sweep-flag 0. Flag 1 traces the complementary arc. + it.each([ + ['\\pswedge(0,0){3}{0}{72}', 'pswedge'], + ['\\psarc(0,0){3}{20}{160}', 'psarc'], + ])('draws %s counter-clockwise', (raw, name) => { + expect(arcCommand(pathsFrom(name, raw)[0]).sweep).toBe('0'); + }); + + it('marks a span wider than half a turn as a large arc', () => { + expect(arcCommand(pathsFrom('pswedge', '\\pswedge(0,0){3}{0}{200}')[0]).large).toBe('1'); + expect(arcCommand(pathsFrom('pswedge', '\\pswedge(0,0){3}{0}{100}')[0]).large).toBe('0'); + }); + + it('takes the long way round when the end angle precedes the start', () => { + // 200 -> 20 sweeps counter-clockwise through 380, a 180 degree span. + expect(arcCommand(pathsFrom('psarc', '\\psarc(0,0){3}{200}{20}')[0]).large).toBe('0'); + // 200 -> 100 sweeps 260 degrees the same way, which is the large arc. + expect(arcCommand(pathsFrom('psarc', '\\psarc(0,0){3}{200}{100}')[0]).large).toBe('1'); + }); + + it('emits a closed two-arc path for a full turn', () => { + // Start and end coincide, so one SVG arc would collapse to nothing. + const d = pathsFrom('pswedge', '\\pswedge(0,0){3}{0}{360}')[0]; + expect(d.match(/A\s/g)).toHaveLength(2); + expect(d.trim().endsWith('Z')).toBe(true); + }); +}); + +describe('pie chart geometry', () => { + it('gives every wedge of a five-slice pie the same radius and direction', () => { + const wedges = [[0, 72], [72, 144], [144, 216], [216, 288], [288, 360]].map( + ([a, b]) => arcCommand(pathsFrom('pswedge', `\\pswedge(0,0){3}{${a}}{${b}}`)[0]), + ); + for (const w of wedges) { + expect(w.sweep).toBe('0'); + expect(w.large).toBe('0'); + expect(w.r).toBeCloseTo(150, 5); + } + }); +}); From d319295b7a1e441708701dde048db37917b4fcab Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:19:48 -0700 Subject: [PATCH 05/22] feat(pstricks): one fill resolver, with real hatched fill styles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every renderer decided its own fill, in three mutually inconsistent ways: fillstyle=hlines became a solid fill on pspolygon and psarc, and no fill at all on psellipse, pswedge and pscurve, so the same document rendered differently depending on which shape drew it. All twelve sites now route through resolveFill. solid and none behave exactly as before; hlines, vlines and crosshatch gain a real rendering as SVG patterns honouring hatchwidth, hatchsep, hatchangle and hatchcolor, with the starred forms laying their lines over the fill colour. A style that is still unimplemented resolves to no fill everywhere rather than guessing solid on some shapes. hasFill separates the shape decision — whether a path must be closed before it can be filled — from the paint itself, so hatched shapes are built as fillable regions too. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- packages/pstricks/src/lib/psgraph.ts | 127 ++++++++++++++++--- packages/pstricks/test/fill-styles.test.ts | 134 +++++++++++++++++++++ 2 files changed, 245 insertions(+), 16 deletions(-) create mode 100644 packages/pstricks/test/fill-styles.test.ts diff --git a/packages/pstricks/src/lib/psgraph.ts b/packages/pstricks/src/lib/psgraph.ts index 0c428920..2e5d6af1 100644 --- a/packages/pstricks/src/lib/psgraph.ts +++ b/packages/pstricks/src/lib/psgraph.ts @@ -73,6 +73,101 @@ function buildCurvePath(data: number[], closed: boolean): string { const TAU = Math.PI * 2; +/** Points to device units, matching the linewidth conversion in pstricks.ts. */ +const PT_TO_PX = 1.333; + +/** + * Line directions each hatched fill style draws, as offsets from `hatchangle`. + * PSTricks hatches at `hatchangle` for hlines, ninety degrees off for vlines, + * and both for crosshatch — so the default 45 degrees makes hlines diagonal, + * not horizontal. + */ +const HATCH_DIRECTIONS: { [style: string]: number[] } = { + hlines: [0], + vlines: [90], + crosshatch: [0, 90], +}; + +/** PSTricks hatch parameter defaults, in points except the angle and colour. */ +const HATCH_DEFAULTS = { hatchwidth: 0.8, hatchsep: 4, hatchangle: 45, hatchcolor: 'black' }; + +let patternSeq = 0; + +/** Reads a dimension that may carry a `pt` suffix, in device units. */ +function dimension(value: any, fallbackPt: number): number { + if (typeof value === 'number' && isFinite(value)) return value * PT_TO_PX; + const m = typeof value === 'string' ? value.trim().match(/^([\d.]+)\s*(pt)?$/) : null; + return (m ? Number(m[1]) : fallbackPt) * PT_TO_PX; +} + +/** + * Whether a shape has any fill at all. Renderers that must close a path before + * it can be filled ask this; the paint itself comes from {@link resolveFill}. + * + * @param ctx - the shape's parsed data + * @returns true when the shape should be built as a closed, fillable region + */ +function hasFill(ctx: any): boolean { + return !!ctx.filled || (!!ctx.fillstyle && ctx.fillstyle !== 'none'); +} + +/** + * Resolves a shape's SVG fill value, defining a hatch pattern when the style + * calls for one. + * + * Every renderer previously spelled this decision itself, in three mutually + * inconsistent ways: `fillstyle=hlines` became a solid fill on pspolygon and + * psarc, and no fill at all on psellipse, pswedge and pscurve. Routing all of + * them through one resolver makes an unimplemented style behave the same + * everywhere, and gives the hatched styles a real rendering. + * + * @param ctx - the shape's parsed data, carrying fillstyle and hatch options + * @param svg - the container the pattern definition is attached to + * @returns an SVG paint value: a colour, a `url(#…)` pattern, or `none` + */ +function resolveFill(ctx: any, svg: any): string { + const style: string = ctx.fillstyle ?? 'none'; + + // The starred forms set `filled`; they fill flat regardless of style. + if (ctx.filled || style === 'solid') return ctx.fillcolor; + if (style === 'none' || !style) return 'none'; + + const starred = style.endsWith('*'); + const directions = HATCH_DIRECTIONS[starred ? style.slice(0, -1) : style]; + // An unrecognised style is not a fill; guessing solid is what made the same + // input render differently depending on the shape. + if (!directions) return 'none'; + + const sep = Math.max(1, dimension(ctx.hatchsep, HATCH_DEFAULTS.hatchsep)); + const width = Math.max(0.2, dimension(ctx.hatchwidth, HATCH_DEFAULTS.hatchwidth)); + const angle = Number(ctx.hatchangle ?? HATCH_DEFAULTS.hatchangle) || 0; + const color = ctx.hatchcolor ?? HATCH_DEFAULTS.hatchcolor; + + const id = 'l2j-hatch-' + ++patternSeq; + const pattern = svg + .append('svg:defs') + .append('svg:pattern') + .attr('id', id) + .attr('patternUnits', 'userSpaceOnUse') + .attr('width', sep) + .attr('height', sep) + // SVG's y axis runs opposite to the PSTricks angle convention. + .attr('patternTransform', 'rotate(' + -angle + ')'); + + // A starred hatch lays its lines over the fill colour instead of nothing. + if (starred) { + pattern.append('svg:rect').attr('width', sep).attr('height', sep).style('fill', ctx.fillcolor); + } + + for (const d of directions) { + const line = pattern.append('svg:line').style('stroke', color).style('stroke-width', width); + if (d === 0) line.attr('x1', 0).attr('y1', sep / 2).attr('x2', sep).attr('y2', sep / 2); + else line.attr('x1', sep / 2).attr('y1', 0).attr('x2', sep / 2).attr('y2', sep); + } + + return 'url(#' + id + ')'; +} + /** * SVG arc flags for a PSTricks arc running from `angleA` to `angleB`. * @@ -119,7 +214,7 @@ function curveRenderer(this: any, svg: any): void { .style('stroke-width', this.linewidth) .style('stroke', this.linecolor) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'solid' || this.filled ? this.fillcolor : 'none'); + .style('fill', resolveFill(this, svg)); } const psgraph: any = { @@ -143,7 +238,7 @@ const psgraph: any = { }, psframe(svg: any): void { - const filled = this.filled || this.fillstyle === 'solid'; + const filled = hasFill(this); if (filled) { svg .append('svg:rect') @@ -151,7 +246,7 @@ const psgraph: any = { .attr('y', Math.min(this.y1, this.y2)) .attr('width', Math.abs(this.x2 - this.x1)) .attr('height', Math.abs(this.y2 - this.y1)) - .style('fill', this.fillcolor) + .style('fill', resolveFill(this, svg)) .style('stroke', 'none'); } @@ -197,14 +292,14 @@ const psgraph: any = { }, pscircle: function (svg: any) { - const filled = this.filled || this.fillstyle === 'solid'; + const filled = hasFill(this); svg .append('svg:circle') .attr('cx', this.cx) .attr('cy', this.cy) .attr('r', this.r) .style('stroke', this.linecolor) - .style('fill', filled ? this.fillcolor : 'none') + .style('fill', resolveFill(this, svg)) .style('stroke-width', this.linewidth) .style('stroke-opacity', 1); }, @@ -212,7 +307,7 @@ const psgraph: any = { psplot(svg: any): void { var context = []; context.push('M'); - if (this.fillstyle === 'solid') { + if (hasFill(this)) { context.push(this.data[0]); context.push(Y.call(this.global, 0)); } else { @@ -225,7 +320,7 @@ const psgraph: any = { context.push(data); }); - if (this.fillstyle === 'solid') { + if (hasFill(this)) { context.push(this.data[this.data.length - 2]); context.push(Y.call(this.global, 0)); context.push('Z'); @@ -237,7 +332,7 @@ const psgraph: any = { .attr('class', 'psplot') .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'none' ? 'none' : this.fillcolor) + .style('fill', resolveFill(this, svg)) .style('stroke', this.linecolor); }, @@ -258,13 +353,13 @@ const psgraph: any = { .attr('d', context.join(' ')) .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'none' && !this.filled ? 'none' : this.fillcolor) + .style('fill', resolveFill(this, svg)) .style('stroke', 'black'); }, psarc(svg: any): void { const { delta, large, sweep } = arcFlags(this.angleA, this.angleB); - const filled = this.filled || this.fillstyle === 'solid'; + const filled = hasFill(this); const arc = ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + ' ' + this.B.x + ' ' + this.B.y; @@ -278,7 +373,7 @@ const psgraph: any = { .attr('d', d) .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', filled ? this.fillcolor : 'none') + .style('fill', resolveFill(this, svg)) .style('stroke', this.linecolor); }, @@ -834,7 +929,7 @@ const psgraph: any = { .style('stroke', this.linecolor) .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'solid' ? this.fillcolor : 'none'); + .style('fill', resolveFill(this, svg)); }, psbezier(svg: any): void { @@ -860,7 +955,7 @@ const psgraph: any = { .style('stroke-width', this.linewidth) .style('stroke', this.linecolor) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'solid' || this.filled ? this.fillcolor : 'none'); + .style('fill', resolveFill(this, svg)); }, psecurve: curveRenderer, @@ -880,11 +975,11 @@ const psgraph: any = { .style('stroke-width', this.linewidth) .style('stroke', this.linecolor) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'solid' ? this.fillcolor : 'none'); + .style('fill', resolveFill(this, svg)); }, pscustom(svg: any): void { - const filled = this.filled || this.fillstyle === 'solid'; + const filled = hasFill(this); let d = ''; let started = false; (this.commands || []).forEach((cmd: any) => { @@ -919,7 +1014,7 @@ const psgraph: any = { .style('stroke-width', this.linewidth) .style('stroke', this.linestyle === 'none' ? 'none' : this.linecolor) .style('stroke-opacity', 1) - .style('fill', filled ? this.fillcolor : 'none'); + .style('fill', resolveFill(this, svg)); }, diff --git a/packages/pstricks/test/fill-styles.test.ts b/packages/pstricks/test/fill-styles.test.ts new file mode 100644 index 00000000..e49c281a --- /dev/null +++ b/packages/pstricks/test/fill-styles.test.ts @@ -0,0 +1,134 @@ +import { Expressions, Functions } from '../src/lib/pstricks'; +import psgraph from '../src/lib/psgraph'; + +/** + * Fill resolution used to be spelled separately in every renderer, so the same + * `fillstyle` produced different results depending on the shape it was applied + * to. These pin the resolved paint per shape, which is the only place that + * divergence is observable. + */ +function makeContext() { + return { + xunit: 50, yunit: 50, + x0: -5, y0: -5, x1: 5, y1: 5, + w: 10, h: 10, + variables: {}, + } as any; +} + +interface Recorded { + tag: string; + attrs: { [k: string]: string }; + styles: { [k: string]: string }; + children: Recorded[]; +} + +/** Minimal stand-in for the SVG selection, recording the tree that is built. */ +function recorder() { + const root: Recorded = { tag: 'root', attrs: {}, styles: {}, children: [] }; + const wrap = (node: Recorded): any => ({ + append(tag: string) { + const child: Recorded = { tag: tag.replace(/^svg:/, ''), attrs: {}, styles: {}, children: [] }; + node.children.push(child); + return wrap(child); + }, + attr(k: string, v: any) { node.attrs[k] = String(v); return wrap(node); }, + style(k: string, v: any) { node.styles[k] = String(v); return wrap(node); }, + on() { return wrap(node); }, + }); + return { root, svg: wrap(root) }; +} + +/** Renders one command and returns the recorded tree. */ +function render(name: string, raw: string): Recorded { + const ctx = makeContext(); + const m = raw.match((Expressions as any)[name]) as RegExpMatchArray; + expect(m).not.toBeNull(); + const data = (Functions as any)[name].call(ctx, m); + data.global = ctx; + const { root, svg } = recorder(); + (psgraph as any)[name].call(data, svg); + return root; +} + +/** Every fill paint anywhere in the recorded tree, outside pattern definitions. */ +function fills(node: Recorded, insideDefs = false): string[] { + const here = !insideDefs && node.styles.fill ? [node.styles.fill] : []; + const nested = node.children.flatMap((c) => fills(c, insideDefs || node.tag === 'defs')); + return [...here, ...nested]; +} + +function find(node: Recorded, tag: string): Recorded | undefined { + if (node.tag === tag) return node; + for (const c of node.children) { + const hit = find(c, tag); + if (hit) return hit; + } + return undefined; +} + +const SHAPES: Array<[string, string]> = [ + ['pscircle', '\\pscircle[FILL](0,0){2}'], + ['psellipse', '\\psellipse[FILL](0,0)(2,1)'], + ['pspolygon', '\\pspolygon[FILL](-2,-1)(0,1.5)(2,-1)'], + ['pswedge', '\\pswedge[FILL](0,0){2}{30}{150}'], + ['psframe', '\\psframe[FILL](-2,-1)(2,1)'], +]; + +describe('fill styles resolve the same way for every shape', () => { + it.each(SHAPES)('%s fills flat for fillstyle=solid', (name, template) => { + const tree = render(name, template.replace('FILL', 'fillstyle=solid,fillcolor=cyan')); + expect(fills(tree)).toContain('cyan'); + }); + + it.each(SHAPES)('%s draws no fill for fillstyle=none', (name, template) => { + const tree = render(name, template.replace('FILL', 'fillstyle=none,fillcolor=cyan')); + expect(fills(tree).filter((f) => f !== 'none')).toHaveLength(0); + }); + + // The regression: hlines became a solid fill on pspolygon and psarc, and no + // fill at all on psellipse and pswedge. + it.each(SHAPES)('%s hatches for fillstyle=hlines rather than filling flat', (name, template) => { + const tree = render(name, template.replace('FILL', 'fillstyle=hlines,fillcolor=cyan')); + const painted = fills(tree).filter((f) => f !== 'none'); + expect(painted.length).toBeGreaterThan(0); + painted.forEach((f) => expect(f).toMatch(/^url\(#l2j-hatch-\d+\)$/)); + expect(painted).not.toContain('cyan'); + }); + + it.each(SHAPES)('%s draws no fill for an unimplemented style', (name, template) => { + const tree = render(name, template.replace('FILL', 'fillstyle=gradient,fillcolor=cyan')); + expect(fills(tree).filter((f) => f !== 'none')).toHaveLength(0); + }); +}); + +describe('hatch pattern geometry', () => { + it('defines one line for hlines and two for crosshatch', () => { + const one = find(render('pscircle', '\\pscircle[fillstyle=hlines](0,0){2}'), 'pattern')!; + expect(one.children.filter((c) => c.tag === 'line')).toHaveLength(1); + const two = find(render('pscircle', '\\pscircle[fillstyle=crosshatch](0,0){2}'), 'pattern')!; + expect(two.children.filter((c) => c.tag === 'line')).toHaveLength(2); + }); + + it('rotates against the SVG axis so the default hatch runs diagonally', () => { + const p = find(render('pscircle', '\\pscircle[fillstyle=hlines](0,0){2}'), 'pattern')!; + expect(p.attrs.patternTransform).toBe('rotate(-45)'); + expect(p.attrs.patternUnits).toBe('userSpaceOnUse'); + }); + + it('honours hatchangle, hatchsep and hatchcolor', () => { + const p = find( + render('pscircle', '\\pscircle[fillstyle=hlines,hatchangle=0,hatchsep=8pt,hatchcolor=red](0,0){2}'), + 'pattern', + )!; + expect(p.attrs.patternTransform).toBe('rotate(0)'); + expect(Number(p.attrs.width)).toBeCloseTo(8 * 1.333, 3); + expect(p.children.find((c) => c.tag === 'line')!.styles.stroke).toBe('red'); + }); + + it('lays a starred hatch over the fill colour', () => { + const p = find(render('pscircle', '\\pscircle[fillstyle=hlines*,fillcolor=yellow](0,0){2}'), 'pattern')!; + expect(p.children.find((c) => c.tag === 'rect')!.styles.fill).toBe('yellow'); + expect(p.children.filter((c) => c.tag === 'line')).toHaveLength(1); + }); +}); From a761463828ddac1ce0dd0a0af2bcece5943e796e Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:23:08 -0700 Subject: [PATCH 06/22] fix(pstricks): redraw the whole picture in source order on interaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The initial draw walked env.elements in document order, but the pointer handler removed the .userline and .psplot elements and appended them again, iterating the type-grouped plot map. Re-appending put them at the end of the SVG — above every later shape — and regrouped them by command type, so a diagram that was correct on load silently reordered itself the first time the pointer crossed it. Drawing now happens once, into a layer group that the handler replaces wholesale, so the interactive path uses exactly the same source-order walk as the initial one. Interactive elements recompute against the pointer through resolveData; everything else redraws from the data the parser produced. Order after an event is only observable by dispatching one, so the regression test asserts it there rather than on the initial render alone. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- packages/html5/test/pspicture.test.ts | 45 +++++- packages/pstricks/src/lib/psgraph.ts | 190 ++++++++++++++------------ 2 files changed, 144 insertions(+), 91 deletions(-) diff --git a/packages/html5/test/pspicture.test.ts b/packages/html5/test/pspicture.test.ts index b2a1711a..a41983b6 100644 --- a/packages/html5/test/pspicture.test.ts +++ b/packages/html5/test/pspicture.test.ts @@ -10,6 +10,18 @@ function stubViewport(width: number): void { Object.defineProperty(window, 'innerWidth', { value: width, configurable: true }); } +/** + * Drawn shapes in document order. All drawing lives in a layer group the + * interactive redraw replaces wholesale, so this looks at descendants rather + * than direct children, and skips anything inside a pattern definition. + */ +function shapeOrder(svg: SVGElement): string[] { + return Array.from(svg.querySelectorAll('circle, path, rect, ellipse, line')) + .filter((el) => !el.closest('defs')) + .map((el) => el.tagName) + .filter((t) => t === 'circle' || t === 'path'); +} + function parsePspicture(tex: string): any { const latex = new LaTeX2JS(); const parsed = latex.parse(tex); @@ -123,12 +135,33 @@ describe('pspicture component (SVG rendering)', () => { // the old parser grouped by command type (circles before lines); the new // parser renders in document order: circle, line, circle const svg = div.querySelector('svg')!; - const tags = Array.from(svg.children).map((el) => el.tagName); - expect(tags.filter((t) => t === 'circle' || t === 'path')).toEqual([ - 'circle', - 'path', - 'circle' - ]); + expect(shapeOrder(svg)).toEqual(['circle', 'path', 'circle']); + }); + + it('keeps source order after the pointer moves over the picture', () => { + // The interactive redraw used to remove and re-append the interactive + // elements, which put them at the end of the SVG and regrouped them by + // command type. A correct diagram silently reordered itself on first + // hover, so order has to be asserted after an event, not only before one. + const env = parsePspicture(` +\\begin{pspicture}(0,0)(4,4) +\\userline[linewidth=2pt]{->}(0,0)(2,2) +\\pscircle(2,2){1} +\\end{pspicture} + `); + + const div = pspicture(env); + document.body.appendChild(div); + const svg = div.querySelector('svg')!; + + // The userline draws its line plus an arrowhead, so the circle authored + // after it must stay last however many paths precede it. + const before = shapeOrder(svg); + expect(before[before.length - 1]).toBe('circle'); + + svg.dispatchEvent(new MouseEvent('mousemove', { bubbles: true })); + + expect(shapeOrder(svg)).toEqual(before); }); it('renders psdots as small circles', () => { diff --git a/packages/pstricks/src/lib/psgraph.ts b/packages/pstricks/src/lib/psgraph.ts index 2e5d6af1..0e1d12ec 100644 --- a/packages/pstricks/src/lib/psgraph.ts +++ b/packages/pstricks/src/lib/psgraph.ts @@ -774,31 +774,114 @@ const psgraph: any = { pspicture(svg: any): void { var env = this.env; var el = this.$el; + const plots = this.plot; - // Source-order initial draw: the parser records `env.elements` in - // document order, so layers (fills under lines, etc.) respect the author's - // order. Falls back to the old type-grouped iteration for legacy data. + // The parser records `env.elements` in document order, so fills sit under + // lines exactly as authored. const elements = env && env.elements; - if (elements && elements.length) { - elements.forEach((item: any) => { - if (!item || !item.name || item.name.match(/rput/)) return; - if (!psgraph.hasOwnProperty(item.name)) return; - item.data.global = env; - psgraph[item.name].call(item.data, svg); + + /** + * Recomputes an interactive element against the pointer position. Static + * elements keep the data the parser produced. + */ + function resolveData(item: any, coords: number[] | null, variables: any): any { + if (!coords || !item.fn) return item.data; + + if (item.name === 'psplot') { + Object.entries(variables || {}).forEach(([name, value]: [string, any]) => { + env.variables[name] = value; + }); + const d = item.fn.call(env, item.match); + d.global = Object.assign({}, env); + return d; + } + + if (item.name === 'userline') { + const d = item.fn.call(env, item.match); + env.x2 = coords[0]; + env.y2 = coords[1]; + item.data.x2 = env.x2; + item.data.y2 = env.y2; + + if (item.data.xExp2) { + item.data.x2 = d.userx2(coords); + item.data.x1 = d.userx(coords); + } else if (item.data.xExp) { + item.data.x2 = d.userx(coords); + } + + if (item.data.yExp2) { + item.data.y2 = d.usery2(coords); + item.data.y1 = d.usery(coords); + } else if (item.data.yExp) { + item.data.y2 = d.usery(coords); + } + + d.global = Object.assign({}, env); + Object.assign(d, item.data); + return d; + } + + return item.data; + } + + /** Evaluates every \uservariable at the pointer position, in source order. */ + function readVariables(coords: number[]): { [name: string]: any } { + const variables: { [name: string]: any } = {}; + const source = elements && elements.length + ? elements.filter((i: any) => i && i.name === 'uservariable') + : ((plots && plots.uservariable) || []).map((p: any) => ({ ...p, name: 'uservariable' })); + source.forEach((item: any) => { + env.userx = coords[0]; + env.usery = coords[1]; + const dd = item.fn.call(env, item.match); + variables[item.data.name] = dd.value; }); - } else { - Object.keys(this.plot).forEach((key) => { - const plot = this.plot[key]; + return variables; + } + + /** + * Draws the whole picture into a fresh layer. + * + * Redrawing everything is what keeps interaction faithful to the source. + * Removing just the interactive elements and appending them again put them + * at the end of the SVG — on top of every later shape — and re-emitted + * them grouped by command type rather than in document order, so a correct + * diagram silently reordered itself the first time the pointer crossed it. + */ + let layer: any = null; + function drawLayer(coords: number[] | null): void { + if (layer) layer.remove(); + layer = svg.append('svg:g').attr('class', 'pspicture-layer'); + const variables = coords ? readVariables(coords) : {}; + + if (elements && elements.length) { + elements.forEach((item: any) => { + if (!item || !item.name || item.name.match(/rput/)) return; + if (!psgraph.hasOwnProperty(item.name)) return; + const data = resolveData(item, coords, variables); + data.global = env; + psgraph[item.name].call(data, layer); + }); + return; + } + + // Legacy data without an ordered element list: fall back to the + // type-grouped iteration, which cannot express author order. + Object.keys(plots).forEach((key) => { if (key.match(/rput/)) return; - if (psgraph.hasOwnProperty(key)) { - plot.forEach((data: any) => { - data.data.global = env; - psgraph[key].call(data.data, svg); - }); - } + if (!psgraph.hasOwnProperty(key)) return; + plots[key].forEach((entry: any) => { + const item = { name: key, data: entry.data, match: entry.match, fn: entry.fn }; + const data = resolveData(item, coords, variables); + data.global = env; + psgraph[key].call(data, layer); + }); }); } + drawLayer(null); + svg.on( 'touchmove', function (this: any, event: any) { @@ -806,7 +889,7 @@ const psgraph: any = { var touch = event.touches ? event.touches[0] : null; var rect = event.target.getBoundingClientRect(); var touchcoords = touch ? [touch.clientX - rect.left, touch.clientY - rect.top] : [0, 0]; - userEvent(touchcoords); + drawLayer(touchcoords); } ); @@ -814,75 +897,12 @@ const psgraph: any = { 'mousemove', function (this: any, event: any) { var coords = [event.offsetX || 0, event.offsetY || 0]; - userEvent(coords); + drawLayer(coords); } ); - const plots = this.plot; - function userEvent(coords: any): void { - svg.selectAll('.userline').remove(); - svg.selectAll('.psplot').remove(); - var currentEnvironment: { [key: string]: any } = {}; - - Object.entries(plots || {}) - .forEach(([k, plot]: [string, any]) => { - if (k.match(/uservariable/)) { - plot.forEach((data: any) => { - data.env.userx = coords[0]; - data.env.usery = coords[1]; - var dd = data.fn.call(data.env, data.match); - currentEnvironment[data.data.name] = dd.value; - }); - } - }); - - Object.entries(plots || {}) - .forEach(([k, plot]: [string, any]) => { - if (k.match(/psplot/)) { - plot.forEach((data: any) => { - Object.entries(currentEnvironment || {}) - .forEach(([name, variable]: [string, any]) => { - data.env.variables[name] = variable; - }); - var d = data.fn.call(data.env, data.match); - d.global = {}; - Object.assign(d.global, env); - psgraph[k].call(d, svg); - }); - } - if (k.match(/userline/)) { - plot.forEach((data: any) => { - var d = data.fn.call(data.env, data.match); - data.env.x2 = coords[0]; - data.env.y2 = coords[1]; - data.data.x2 = data.env.x2; - data.data.y2 = data.env.y2; - - if (data.data.xExp2) { - data.data.x2 = d.userx2(coords); - data.data.x1 = d.userx(coords); - } else if (data.data.xExp) { - data.data.x2 = d.userx(coords); - } - - if (data.data.yExp2) { - data.data.y2 = d.usery2(coords); - data.data.y1 = d.usery(coords); - } else if (data.data.yExp) { - data.data.y2 = d.usery(coords); - } - - d.global = {}; - Object.assign(d.global, env); - Object.assign(d, data.data); - psgraph[k].call(d, svg); - }); - } - }); - } - - // Enhanced cleanup and RPUT processing - psgraph.processRputElements.call(this, el); + // Enhanced cleanup and RPUT processing + psgraph.processRputElements.call(this, el); }, psdots(svg: any): void { From cf8925fe70a04001c30bc659d1a2f029a08f0e80 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:24:55 -0700 Subject: [PATCH 07/22] fix(corpus): correct a malformed \userline copied across three examples \userline takes its head and tail expressions as brace groups, but the lightblue vector was written (sin(x)}{-y} with an opening parenthesis where a brace belongs. LaTeX2JS parses it without complaint, so it went unnoticed; real LaTeX rejects it outright, and it was the only thing keeping four example pictures from compiling under PSTricks. The same line appears inside a verbatim block on the site, so the typo was being published as documentation for how \userline is written. With this corrected, all 57 example pictures rasterize against real PSTricks. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- packages/latex2js/test/corpus/03.tex | 2 +- packages/latex2js/test/corpus/site-examples-index-1.tex | 4 ++-- packages/latex2js/test/corpus/site-index-1.tex | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/latex2js/test/corpus/03.tex b/packages/latex2js/test/corpus/03.tex index b0b736d7..d8d5187d 100644 --- a/packages/latex2js/test/corpus/03.tex +++ b/packages/latex2js/test/corpus/03.tex @@ -3,5 +3,5 @@ \userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} \userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} \userline[linewidth=2pt,linecolor=purple]{->}(0,0)(2,2){-x}{cos(y)} -\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2)(sin(x)}{-y} +\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2){sin(x)}{-y} \end{pspicture} diff --git a/packages/latex2js/test/corpus/site-examples-index-1.tex b/packages/latex2js/test/corpus/site-examples-index-1.tex index d93ed23a..215fdecd 100644 --- a/packages/latex2js/test/corpus/site-examples-index-1.tex +++ b/packages/latex2js/test/corpus/site-examples-index-1.tex @@ -86,7 +86,7 @@ \userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} \userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} \userline[linewidth=2pt,linecolor=purple]{->}(0,0)(2,2){-x}{cos(y)} -\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2)(sin(x)}{-y} +\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2){sin(x)}{-y} \end{pspicture}
    @@ -98,7 +98,7 @@ \userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} \userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} \userline[linewidth=2pt,linecolor=purple]{->}(0,0)(2,2){-x}{cos(y)} -\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2)(sin(x)}{-y} +\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2){sin(x)}{-y} \end{pspicture} \end{verbatim} diff --git a/packages/latex2js/test/corpus/site-index-1.tex b/packages/latex2js/test/corpus/site-index-1.tex index 70fa1cb0..9bacf04c 100644 --- a/packages/latex2js/test/corpus/site-index-1.tex +++ b/packages/latex2js/test/corpus/site-index-1.tex @@ -68,7 +68,7 @@ \userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} \userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} \userline[linewidth=2pt,linecolor=purple]{->}(0,0)(2,2){-x}{cos(y)} -\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2)(sin(x)}{-y} +\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2){sin(x)}{-y} \end{pspicture} \end{center} @@ -78,7 +78,7 @@ \userline[linewidth=2pt,linecolor=green]{->}(0,0)(2,2){-x}{-y} \userline[linewidth=2pt,linecolor=red]{->}(0,0)(2,2){0}{y} \userline[linewidth=2pt,linecolor=purple]{->}(0,0)(2,2){-x}{cos(y)} -\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2)(sin(x)}{-y} +\userline[linewidth=2pt,linecolor=lightblue]{->}(0,0)(2,2){sin(x)}{-y} \end{verbatim} I can also draw a more complex version, and start to make more useful diagrams to describe vectors: From cede800ddc51986f9775d91b995b4d56c3a19926 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:27:15 -0700 Subject: [PATCH 08/22] fix(conformance): carry \psset scope into each extracted picture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The splitter took pspicture blocks verbatim, dropping any \psset above them. The corpus routinely sets the unit once for several pictures, so those references rendered at the wrong scale — and under standalone cropping that produced a plausible-looking image rather than an obvious failure. 16-curves came out looking rotated, which reads as a renderer bug until you check the document that was actually compiled. Each block now carries the settings in scope where it appeared, and the manifest records them. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- .../pstricks-conformance/render-examples.mjs | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/tools/pstricks-conformance/render-examples.mjs b/tools/pstricks-conformance/render-examples.mjs index c4810356..e0c14a09 100644 --- a/tools/pstricks-conformance/render-examples.mjs +++ b/tools/pstricks-conformance/render-examples.mjs @@ -222,12 +222,26 @@ function normalizePlots(src, bindings) { return { text, freeVars: [...unresolved] } } -/** Splits a file into its pspicture blocks; a file with none is one whole-document unit. */ +/** + * Splits a file into its pspicture blocks. + * + * `\psset` applies to everything that follows it, and the corpus routinely + * sets the unit once above several pictures. Extracting a picture without the + * settings that governed it renders at the wrong scale, which under + * `standalone` cropping produces a plausible-looking but wrong reference — so + * each block carries the settings in scope where it appeared. + * + * A file with no pictures is one whole-document unit. + */ function splitPictures(src) { const blocks = [] - const re = /\\begin\{pspicture\}[\s\S]*?\\end\{pspicture\}/g + const re = /\\psset\{[^{}]*\}|\\begin\{pspicture\}[\s\S]*?\\end\{pspicture\}/g + const settings = [] let m - while ((m = re.exec(src))) blocks.push(m[0]) + while ((m = re.exec(src))) { + if (m[0].startsWith('\\psset')) settings.push(m[0]) + else blocks.push({ body: m[0], settings: [...settings] }) + } return blocks } @@ -290,10 +304,14 @@ for (const file of files) { continue } - pictures.forEach((body, n) => { + pictures.forEach((picture, n) => { const id = pictures.length === 1 ? stem : `${stem}--p${String(n + 1).padStart(2, '0')}` + const body = [...picture.settings, picture.body].join('\n') writeFileSync(join(outDir, 'doc', `${id}.tex`), wrapDocument(body, { document: false })) - manifest[id] = { source: file, index: n + 1, kind: 'picture', shims, bindings, freeVars, body } + manifest[id] = { + source: file, index: n + 1, kind: 'picture', + shims, bindings, freeVars, settings: picture.settings, body, + } }) } From 97188fdd596d58b5ff3e4bcb37f71c86fb66e23f Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:29:48 -0700 Subject: [PATCH 09/22] fix(utils): resolve xcolor tint expressions to CSS colours MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Colour options were handed to the browser verbatim, so `fillcolor=gray!40` arrived as an unusable fill value. An unparsable fill is not ignored — it falls back to black — and example 10's light grey plane rendered as a solid black shape covering the vectors and labels drawn over it. parseOptions now resolves the colour-valued keys through resolveColor, which implements xcolor's mix semantics: `base!N` against white, `base!N!other` against a named colour, chained left to right. Anything without a mix term is returned untouched, so plain colour names keep exactly the CSS meaning they already had. An unknown name, a bad percentage, or an unknown second operand returns the input rather than guessing. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- packages/pstricks/test/pstricks.test.ts | 4 +- packages/utils/src/index.ts | 59 +++++++++++++++++++++++- packages/utils/test/color.test.ts | 60 +++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 packages/utils/test/color.test.ts diff --git a/packages/pstricks/test/pstricks.test.ts b/packages/pstricks/test/pstricks.test.ts index f6810680..8cc12de9 100644 --- a/packages/pstricks/test/pstricks.test.ts +++ b/packages/pstricks/test/pstricks.test.ts @@ -143,7 +143,9 @@ describe('pstricks Functions', () => { const m = match(Expressions.pscustom, '\\pscustom[fillstyle=solid,fillcolor=gray!40]{\\psline(0,0)(4,1.2)}'); const data = Functions.pscustom.call(ctx, m); expect(data.fillstyle).toBe('solid'); - expect(data.fillcolor).toBe('gray!40'); + // xcolor tints are resolved at parse time; passing `gray!40` through to the + // browser produced an unusable fill value, which renders as black. + expect(data.fillcolor).toBe('rgb(204,204,204)'); expect(data.body).toContain('\\psline(0,0)(4,1.2)'); }); diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 2a2bdd1c..a7505e2c 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -45,6 +45,61 @@ export const RE = { coords: '\\(\\s*([^\\)]*),([^\\)]*)\\s*\\)' }; +/** Option keys whose value names a colour. */ +const COLOR_KEYS = ['linecolor', 'fillcolor', 'hatchcolor', 'gridcolor', 'bordercolor', 'shadowcolor', 'labelcolor']; + +/** + * Base colours xcolor mixes against, as RGB triples. Only the names that can + * appear on the left of a `!` need resolving; every other colour is handed to + * the browser unchanged, so plain names keep whatever CSS already gives them. + */ +const BASE_COLORS: { [name: string]: [number, number, number] } = { + red: [255, 0, 0], green: [0, 255, 0], blue: [0, 0, 255], + cyan: [0, 255, 255], magenta: [255, 0, 255], yellow: [255, 255, 0], + black: [0, 0, 0], white: [255, 255, 255], gray: [128, 128, 128], + grey: [128, 128, 128], orange: [255, 165, 0], purple: [128, 0, 128], + brown: [165, 42, 42], pink: [255, 192, 203], olive: [128, 128, 0], + violet: [148, 0, 211], teal: [0, 128, 128], lime: [0, 255, 0], +}; + +/** + * Resolves an xcolor tint expression to a CSS colour. + * + * `gray!40` means forty percent gray against white, and `gray!40!red` mixes + * against red instead. A browser cannot read either, and an unparsable fill + * silently falls back to black — which is how a light grey plane rendered as + * a solid black one. + * + * @param value - a colour name, optionally with `!` mix terms + * @returns a CSS colour; names without a mix term are returned untouched + */ +export const resolveColor = function (value: string): string { + const parts = String(value).split('!').map((p) => p.trim()); + if (parts.length < 2) return value; + + const rgb = (name: string): [number, number, number] | null => + BASE_COLORS[name.toLowerCase()] ?? null; + + let current = rgb(parts[0]); + if (!current) return value; + + for (let i = 1; i < parts.length; i += 2) { + const pct = Number(parts[i]); + if (!isFinite(pct)) return value; + // An omitted second operand mixes against white, as xcolor does. + const against = parts[i + 1] ? rgb(parts[i + 1]) : ([255, 255, 255] as [number, number, number]); + if (!against) return value; + const w = Math.max(0, Math.min(100, pct)) / 100; + current = [ + Math.round(current[0] * w + against[0] * (1 - w)), + Math.round(current[1] * w + against[1] * (1 - w)), + Math.round(current[2] * w + against[2] * (1 - w)), + ]; + } + + return 'rgb(' + current[0] + ',' + current[1] + ',' + current[2] + ')'; +}; + // OPTIONS // converts [showorigin=false,labels=none, Dx=3.14] to {showorigin: 'false', labels: 'none', Dx: '3.14'} export const parseOptions = function (opts: string) { @@ -54,7 +109,9 @@ export const parseOptions = function (opts: string) { all.forEach((option: string) => { var kv = option.split('='); if (kv.length == 2) { - obj[kv[0].trim()] = kv[1].trim(); + const key = kv[0].trim(); + const value = kv[1].trim(); + obj[key] = COLOR_KEYS.indexOf(key) === -1 ? value : resolveColor(value); } }); return obj; diff --git a/packages/utils/test/color.test.ts b/packages/utils/test/color.test.ts new file mode 100644 index 00000000..f01a0a7f --- /dev/null +++ b/packages/utils/test/color.test.ts @@ -0,0 +1,60 @@ +import { resolveColor, parseOptions } from '../src'; + +/** + * xcolor tint expressions reach the browser as a fill value. An unparsable one + * is not ignored — it falls back to black, so `gray!40` painted a light grey + * plane solid black with nothing to indicate anything had gone wrong. + */ +describe('resolveColor', () => { + it('mixes a tint against white', () => { + // 40% of gray(128) plus 60% of white(255) = 204 + expect(resolveColor('gray!40')).toBe('rgb(204,204,204)'); + }); + + it('mixes against a named second operand', () => { + expect(resolveColor('red!50!blue')).toBe('rgb(128,0,128)'); + }); + + it.each([ + ['black!0', 'rgb(255,255,255)'], + ['black!100', 'rgb(0,0,0)'], + ['white!50', 'rgb(255,255,255)'], + ])('resolves the endpoints: %s', (input, expected) => { + expect(resolveColor(input)).toBe(expected); + }); + + it.each(['red', 'lightblue', '#ff0000', 'rgb(1,2,3)'])( + 'leaves %s untouched when there is no mix term', + (input) => { + expect(resolveColor(input)).toBe(input); + }, + ); + + it.each(['notacolor!40', 'gray!notanumber', 'gray!40!notacolor'])( + 'returns %s unchanged rather than guessing', + (input) => { + expect(resolveColor(input)).toBe(input); + }, + ); + + it('clamps a percentage outside the range', () => { + expect(resolveColor('gray!500')).toBe(resolveColor('gray!100')); + expect(resolveColor('gray!-20')).toBe(resolveColor('gray!0')); + }); +}); + +describe('parseOptions resolves colour-valued keys', () => { + it('resolves every colour key', () => { + const o = parseOptions('[fillcolor=gray!40,linecolor=red!50!blue,hatchcolor=gray!40]'); + expect(o.fillcolor).toBe('rgb(204,204,204)'); + expect(o.linecolor).toBe('rgb(128,0,128)'); + expect(o.hatchcolor).toBe('rgb(204,204,204)'); + }); + + it('leaves non-colour options alone even when they contain a bang', () => { + const o = parseOptions('[linewidth=2pt,fillstyle=solid,labels=none]'); + expect(o.linewidth).toBe('2pt'); + expect(o.fillstyle).toBe('solid'); + expect(o.labels).toBe('none'); + }); +}); From d98e2505733fd71174215fc331ca7693b5c24e96 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:31:29 -0700 Subject: [PATCH 10/22] fix(conformance): bind uservariables to their declared starting value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit \uservariable{alpha}(0.1,0){x} states the initial cursor position in its coordinate argument, so alpha starts at 0.1. The harness ignored that and pinned every such variable to 1, producing a faithful reference of a different diagram — example 09's tangent line was drawn at a slope the source never asks for, which reads as a plot bug rather than a harness one. The expression is now evaluated at the declared position, falling back to the old pin only when it is not numeric. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- .../pstricks-conformance/render-examples.mjs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/tools/pstricks-conformance/render-examples.mjs b/tools/pstricks-conformance/render-examples.mjs index e0c14a09..e32ef71a 100644 --- a/tools/pstricks-conformance/render-examples.mjs +++ b/tools/pstricks-conformance/render-examples.mjs @@ -67,13 +67,33 @@ function skipParen(src, i) { } /** - * Default binding for a \uservariable, whose real value is the live cursor - * position. Ground truth has no cursor, so every such variable is pinned and - * the binding is recorded — a JS-side render must use the same value for the - * comparison to mean anything. + * Fallback binding for a \uservariable whose initial value cannot be read. + * Its real value tracks the cursor, and ground truth has no cursor. */ const PINNED_USERVAR = 1 +/** + * Evaluates a \uservariable's expression at its declared starting position. + * + * `\uservariable{alpha}(0.1,0){x}` states the initial cursor position in its + * coordinate argument, so the variable starts at 0.1 — not at some value the + * harness invents. Pinning it elsewhere renders a correct-looking reference of + * a different diagram, which reads as a renderer bug. + * + * @param expr - the variable's expression, over `x` and `y` + * @param x - initial x from the coordinate argument + * @param y - initial y from the coordinate argument + * @returns the value as a string, or null when the expression is not numeric + */ +function initialBinding(expr, x, y) { + const body = expr.trim() + if (!/^[-+*/(). \dxy]+$/.test(body)) return null + try { + const v = Function('x', 'y', `"use strict";return (${body})`)(Number(x), Number(y)) + return Number.isFinite(v) ? String(Number(v.toFixed(6))) : null + } catch { return null } +} + /** * Rewrites LaTeX2JS-only macros into their static PSTricks equivalent. * \userline collapses to the \psline it draws before any interaction; @@ -88,10 +108,12 @@ function shim(src) { for (const m of src.matchAll(/\\slider\{([^{}]*)\}\{([^{}]*)\}\{([^{}]*)\}\{((?:[^{}]|\{[^{}]*\})*)\}\{([^{}]*)\}/g)) { bindings[m[3].trim()] = m[5].trim() } - // \uservariable{name}(x,y){expr} — cursor-driven, so pin it - for (const m of src.matchAll(/\\uservariable\{([^{}]*)\}/g)) { + // \uservariable{name}(x,y){expr} — cursor-driven, so bind it to the value it + // holds at the declared starting position. + for (const m of src.matchAll(/\\uservariable\{([^{}]*)\}\(([^()]*),([^()]*)\)\{([^{}]*)\}/g)) { const name = m[1].trim() - if (!(name in bindings)) bindings[name] = String(PINNED_USERVAR) + if (name in bindings) continue + bindings[name] = initialBinding(m[4], m[2], m[3]) ?? String(PINNED_USERVAR) } let out = '' From 5255e64d582f8a2f0d0540a483942417006165aa Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:32:50 -0700 Subject: [PATCH 11/22] feat(conformance): separate structurally incomparable pairs from the ranking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slider chrome, whole-document typesetting, and unbound plot variables produce differences no renderer change can close. Ranked alongside everything else they sat at the bottom looking like the worst defects, which is exactly where attention goes first. Those pairs are now listed with the reason they cannot be judged on equal terms, and the ranking covers only pairs that can. The README records the divergences that are deliberate — starred shapes honouring fillcolor, document numbering, CSS colour names — so they are not re-discovered as bugs. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- tools/pstricks-conformance/README.md | 40 +++++++++++ tools/pstricks-conformance/compare.mjs | 98 ++++++++++++++++++++------ 2 files changed, 118 insertions(+), 20 deletions(-) diff --git a/tools/pstricks-conformance/README.md b/tools/pstricks-conformance/README.md index 6248c303..28bbb9e7 100644 --- a/tools/pstricks-conformance/README.md +++ b/tools/pstricks-conformance/README.md @@ -48,9 +48,14 @@ cd ../../playground && pnpm e2e:gallery && cd - node compare.mjs \ --js ../../playground/renders \ --ref ./examples-ref/ref \ + --manifest ./examples-ref/manifest.json \ --out comparison.html ``` +Passing `--manifest` lets the comparison separate pairs that cannot be judged +on equal terms from those that can, so the ranking is not dominated by +differences no renderer change could close. + For the generated corpus: ```sh @@ -91,3 +96,38 @@ The harness also normalizes several places where LaTeX2JS accepts input real PSTricks rejects — `pow(a,b)`, infix bodies without `algebraic=true`, variable plot bounds, `plotpoints=1`. Each of those is a dialect decision the project still owes an answer to: keep the extension, or conform. + +## Known divergences + +Differences the comparison surfaces that are **not** defects. Each is a +deliberate choice to keep LaTeX2JS's behaviour; they are recorded so nobody +re-discovers them as bugs. + +### Starred shapes honour `fillcolor` + +`\psframe*[fillcolor=lightblue]` fills light blue here. PSTricks fills the +starred forms with `linecolor` and ignores `fillcolor`, so the same source +prints black. Every use in this repo's examples passes a `fillcolor` and +plainly means it — the bar chart wants blue bars — so the lenient reading +matches author intent, at the cost of conformance. This accounts for the whole +gap on `14-bar-chart` and `18-fills`. + +### Document typesetting + +LaTeX numbers sections, theorems and equations, runs `Theorem 1.` into the +following text, and floats footnotes to the foot of the page. LaTeX2JS emits +headings and inline superscripts, which suits a scrolling page. Numbering is +the one worth revisiting, since it is what makes a document cross-referenceable. + +### Interactive chrome + +`\slider` draws a control in the browser and nothing on paper, which shifts +the ink bounding box enough to dominate a layout score. Those pairs are listed +rather than ranked. + +### Colour names + +Plain colour names resolve through CSS, so `green` is CSS green (`#008000`), +where LaTeX's `green` is pure `#00FF00`. Only `!` mix expressions are resolved +against LaTeX's palette. Changing this would shift colours on every existing +page, so it stays a decision rather than a fix. diff --git a/tools/pstricks-conformance/compare.mjs b/tools/pstricks-conformance/compare.mjs index 1780ef3f..ae98762c 100644 --- a/tools/pstricks-conformance/compare.mjs +++ b/tools/pstricks-conformance/compare.mjs @@ -27,8 +27,28 @@ const flag = (n, d) => (argv.includes(n) ? argv[argv.indexOf(n) + 1] : d) const jsDir = resolve(flag('--js', '')) const refDir = resolve(flag('--ref', '')) const outFile = resolve(flag('--out', 'comparison.html')) +const manifestPath = flag('--manifest', '') const GRID = 16 +/** + * Reasons a pair cannot be compared on equal terms. These are properties of + * the two media, not defects: scoring them alongside the rest just buries the + * real differences under noise that can never be resolved. + */ +function incomparable(entry) { + if (!entry) return null + if ((entry.shims ?? []).includes('slider')) { + return 'The browser draws slider controls that a printed page cannot have, which moves the ink bounding box.' + } + if (entry.kind === 'document') { + return 'A whole document, where LaTeX and the browser make different typesetting choices — numbering, run-in headings, footnote placement.' + } + if ((entry.freeVars ?? []).length) { + return `Plot variables left unbound in the reference: ${entry.freeVars.join(', ')}.` + } + return null +} + /** * Decodes an 8-bit truecolour PNG to raw samples, undoing the per-scanline * filters. Returns null for anything else (palette, 16-bit, interlaced) so the @@ -175,6 +195,10 @@ if (!pairs.length) { process.exit(1) } +const manifest = manifestPath && existsSync(manifestPath) + ? JSON.parse(readFileSync(manifestPath, 'utf8')) + : {} + const rows = [] for (const name of pairs) { const ja = decode(join(jsDir, `${name}.png`)) @@ -183,18 +207,43 @@ for (const name of pairs) { const da = describe(ja), db = describe(rb) rows.push({ name, + caveat: incomparable(manifest[name]), ...score(da, db), js: { ink: da.ink, box: da.box, b64: readFileSync(join(jsDir, `${name}.png`)).toString('base64') }, ref: { ink: db.ink, box: db.box, b64: readFileSync(join(refDir, `${name}.png`)).toString('base64') }, }) } -rows.sort((a, b) => (a.overall ?? 2) - (b.overall ?? 2)) +// Pairs with a structural caveat sort last: they are listed for inspection, +// not ranked as defects. +const ranked = rows.filter((r) => !r.caveat && !r.unscored).sort((a, b) => a.overall - b.overall) +const caveated = rows.filter((r) => r.caveat).sort((a, b) => a.overall - b.overall) +const unscored = rows.filter((r) => r.unscored) const pct = (v) => `${(v * 100).toFixed(0)}%` const band = (v) => (v < 0.55 ? 'bad' : v < 0.75 ? 'warn' : 'good') const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]) + +/** One side-by-side pair. */ +function card(r) { + return ` +
    +
    + ${esc(r.name)} + ink ${pct(r.ink)} + colour ${pct(r.hue)} + layout ${pct(r.layout)} + ${pct(r.overall)} +
    + ${r.caveat ? `

    ${esc(r.caveat)}

    ` : ''} +
    +

    LaTeX2JS

    ${esc(r.name)} rendered by LaTeX2JS
    +

    PSTricks reference

    ${esc(r.name)} rendered by PSTricks
    +
    +
    ` +} + const html = `Renderer Comparison @@ -237,29 +292,32 @@ const html = `Renderer Comparison

    Renderer Comparison

    The same source rendered twice. Scores are a heuristic — SVG in a browser and Ghostscript output never match pixel for pixel — so treat them as an ordering that says where to look, not as a verdict.

    - ${rows.map((r) => r.unscored ? ` -
    ${esc(r.name)} - unscored
    ` : ` -
    -
    - ${esc(r.name)} - ink ${pct(r.ink)} - colour ${pct(r.hue)} - layout ${pct(r.layout)} - ${pct(r.overall)} -
    -
    -

    LaTeX2JS

    ${esc(r.name)} rendered by LaTeX2JS
    -

    PSTricks reference

    ${esc(r.name)} rendered by PSTricks
    -
    -
    `).join('')} -
    ${rows.length} pairs · heuristic score = 25% ink + 30% colour + 45% layout occupancy
    +
    +

    Ranked by disagreement

    + ${ranked.map(card).join('')} +
    + + ${caveated.length ? ` +
    +

    Listed, not ranked

    +

    These differ for reasons no renderer change can close, so they are kept out of the ranking above rather than sitting at the bottom of it looking like defects.

    + ${caveated.map(card).join('')} +
    ` : ''} + + ${unscored.length ? ` +
    +

    Unscored

    + ${unscored.map((r) => `
    ${esc(r.name)}
    `).join('')} +
    ` : ''} + +
    ${ranked.length} ranked · ${caveated.length} listed · heuristic score = 25% ink + 30% colour + 45% layout occupancy
    ` writeFileSync(outFile, html) console.log(`compare: ${rows.length} pairs -> ${outFile} (${(Buffer.byteLength(html) / 1024 / 1024).toFixed(2)} MB)`) -for (const r of rows.slice(0, 12)) { - if (r.unscored) { console.log(` ???? ${r.name}`); continue } +console.log(` ${ranked.length} ranked, ${caveated.length} listed only, ${unscored.length} unscored\n`) +for (const r of ranked.slice(0, 12)) { console.log(` ${pct(r.overall).padStart(4)} ${r.name.padEnd(34)} ink=${pct(r.ink)} colour=${pct(r.hue)} layout=${pct(r.layout)}`) } +for (const r of caveated) console.log(` --- ${r.name.padEnd(34)} ${r.caveat.slice(0, 60)}`) From 83991526650c78095d01aaebaccf205152933758 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:36:30 -0700 Subject: [PATCH 12/22] fix(latex2js): keep an environment's source line intact across macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EnvContent matches Command before Line, and a command's tail stops at the next command, so `\item First with \textbf{bold} text` arrives as two command nodes from one source line. Walking them individually rendered each as its own line, breaking every list item at its first macro — the same text outside a list was unaffected, which is why it read as a list-styling quirk. Text environments now rejoin nodes that share a source line before the text passes run. An empty Line closes the line being built, or is a paragraph break when there is nothing to close, so blank-line handling is unchanged. pspicture keeps the per-node walk, since it depends on receiving commands separately. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- packages/latex2js/src/lib/parser.ts | 53 ++++++++++++++++++++++- packages/latex2js/test/list-lines.test.ts | 48 ++++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 packages/latex2js/test/list-lines.test.ts diff --git a/packages/latex2js/src/lib/parser.ts b/packages/latex2js/src/lib/parser.ts index 75a8f2cf..f75ea411 100644 --- a/packages/latex2js/src/lib/parser.ts +++ b/packages/latex2js/src/lib/parser.ts @@ -187,8 +187,8 @@ class Parser { this.environment.commands = []; env.content.forEach((c) => this.walkContent(c)); } else { - // enumerate / nicebox: content is text lines (with transforms). - env.content.forEach((c) => this.walkContent(c)); + // enumerate / itemize / nicebox: content is text lines (with transforms). + this.walkTextContent(env.content); } if (env.end && env.end.name !== name) { @@ -203,6 +203,55 @@ class Parser { this.newEnvironment('math'); } + /** + * Walk a text environment's content, rejoining the nodes that came from one + * source line. + * + * `EnvContent` matches `Command` before `Line`, and a command's tail stops at + * the next command, so `\item First with \textbf{bold} text` arrives as two + * command nodes. Walking them individually renders each as its own line, + * which broke every list item at its first macro. pspicture keeps the + * per-node walk, because it depends on receiving commands separately. + */ + walkTextContent(content: any[]): void { + let pending: any[] = []; + + const flush = (): void => { + if (!pending.length) return; + const text = pending + .map((n) => (n.kind === 'line' ? this.lineToString(n) : n.raw)) + .join(''); + pending = []; + this.pushMathLine(text); + }; + + content.forEach((node) => { + if (node.kind === 'env') { + flush(); + this.walkEnv(node); + return; + } + + // An empty Line is the newline itself: it closes the line being built, + // or is a genuine paragraph break when there is nothing to close. + if (node.kind === 'line' && node.parts.length === 0) { + if (pending.length) flush(); + else this.pushBlankLine(false); + return; + } + + const at = node.loc && node.loc.line; + const open = pending.length ? pending[0].loc && pending[0].loc.line : at; + if (pending.length && at !== open) flush(); + pending.push(node); + + // A Line node consumed its own EOL, so nothing more belongs to it. + if (node.kind === 'line') flush(); + }); + + flush(); + } + /** * Walk one node of environment content. Behavior depends on the current * environment: inside pspicture we collect commands (and raw lines) for plot diff --git a/packages/latex2js/test/list-lines.test.ts b/packages/latex2js/test/list-lines.test.ts new file mode 100644 index 00000000..bded600a --- /dev/null +++ b/packages/latex2js/test/list-lines.test.ts @@ -0,0 +1,48 @@ +import LaTeX2JS from '../src'; + +/** + * Inside an environment the grammar matches Command before Line, and a + * command's tail stops at the next command — so one source line arrives as + * several nodes. Rendering each as its own line broke list items wherever a + * macro appeared, which is visible only once a macro is present. + */ +const lines = (tex: string): string[] => { + const parsed: any = new LaTeX2JS().parse(tex); + return parsed.flatMap((s: any) => s.lines || []).filter((l: string) => l !== '
    '); +}; + +const list = (body: string) => `\\begin{itemize}\n${body}\n\\end{itemize}\n`; + +describe('list items stay on one line', () => { + it.each([ + ['\\textbf', '\\item First with \\textbf{bold} text', '\\item First with bold text'], + ['\\emph', '\\item First with \\emph{em} text', '\\item First with em text'], + ['\\textit', '\\item A \\textit{b} c', '\\item A b c'], + ])('keeps an item containing %s intact', (_label, source, expected) => { + expect(lines(list(source))).toEqual([expected]); + }); + + it('keeps two macros in one item on the same line', () => { + expect(lines(list('\\item \\textbf{a} then \\textit{b} end'))).toEqual([ + '\\item a then b end', + ]); + }); + + it('still separates one item from the next', () => { + expect(lines(list('\\item First \\textbf{a}\n\\item Second \\textbf{b}'))).toEqual([ + '\\item First a', + '\\item Second b', + ]); + }); + + it('matches how the same text renders outside a list', () => { + expect(lines('Some \\textbf{bold} text here\n')).toEqual(['Some bold text here']); + }); + + it('keeps a blank line between items as a paragraph break', () => { + const parsed: any = new LaTeX2JS().parse(list('\\item One\n\n\\item Two')); + const all = parsed.flatMap((s: any) => s.lines || []); + expect(all).toContain('
    '); + expect(all.filter((l: string) => l !== '
    ')).toEqual(['\\item One', '\\item Two']); + }); +}); From 9c76cc636bec0321e0ffd3e99f64dd0ba4f5bb5e Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:40:38 -0700 Subject: [PATCH 13/22] feat(pstricks): draw psaxes tick labels, and honour ticks and labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit psaxes parsed `ticks` and `labels` and then discarded both, so ticks=none still drew ticks and no axis could ever carry a number — the reference renders number every tick. Both options are now carried onto the data and select which axes get marks and numbers. Two errors surfaced while adding the labels. Ticks stepped from the end of the axis rather than the origin, so an axis spanning -3.5 to 3.5 was marked at half-integers; they now step outward from the origin and land on whole multiples. And because Y inverts the axis, a vertical span arrives with its ends reversed, which made the y loop's condition false immediately — no y tick has ever been drawn. SVGSelection.text narrowed on SVGTextElement, a constructor jsdom does not expose as a global, so the first axis label threw a ReferenceError. Every Element carries textContent, so no narrowing is needed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- packages/pstricks/src/lib/psgraph.ts | 82 +++++++++++++++++++++---- packages/pstricks/src/lib/pstricks.ts | 8 ++- packages/pstricks/test/psaxes.test.ts | 86 +++++++++++++++++++++++++++ packages/utils/src/svg-utils.ts | 11 +++- 4 files changed, 171 insertions(+), 16 deletions(-) create mode 100644 packages/pstricks/test/psaxes.test.ts diff --git a/packages/pstricks/src/lib/psgraph.ts b/packages/pstricks/src/lib/psgraph.ts index 0e1d12ec..1fb645a3 100644 --- a/packages/pstricks/src/lib/psgraph.ts +++ b/packages/pstricks/src/lib/psgraph.ts @@ -392,29 +392,87 @@ const psgraph: any = { .style('stroke-opacity', 1); } + /** + * Tick positions, stepped outward from the origin rather than from the end + * of the axis. Starting at the end puts every mark at whatever offset the + * axis happens to begin on, so an axis spanning -3.5 to 3.5 was ticked and + * labelled at half-integers instead of on the whole numbers. + */ + const positions = (from: number, to: number, at: number, step: number): number[] => { + if (!(step > 0) || !isFinite(step)) return []; + // Y inverts the axis, so a vertical span arrives with its ends the other + // way round. Walking it as given produced no y ticks at all. + const lo = Math.min(from, to); + const hi = Math.max(from, to); + const out: number[] = []; + for (let v = at; v <= hi + 1e-6; v += step) out.push(v); + for (let v = at - step; v >= lo - 1e-6; v -= step) out.unshift(v); + return out; + }; + var xticks = () => { - for (var x = xaxis[0]; x <= xaxis[1]; x += this.dx) { + positions(xaxis[0], xaxis[1], origin[0], this.dx).forEach((x) => { line(x, origin[1] - 5, x, origin[1] + 5); - } + }); }; var yticks = () => { - for (var y = yaxis[0]; y <= yaxis[1]; y += this.dy) { + positions(yaxis[0], yaxis[1], origin[1], this.dy).forEach((y) => { line(origin[0] - 5, y, origin[0] + 5, y); - } + }); + }; + + const env = this.global || {}; + + /** Draws one tick number, positioned clear of its axis. */ + const label = (text: string, x: number, y: number, anchor: string) => { + svg + .append('svg:text') + .attr('x', x) + .attr('y', y) + .attr('text-anchor', anchor) + .attr('font-size', 13) + .attr('font-family', 'serif') + .style('fill', 'black') + .text(text); + }; + + /** Tick values are device coordinates; labels need the value they stand for. */ + const value = (device: number, axis: 'x' | 'y'): number => { + const n = axis === 'x' + ? device / env.xunit - env.w + env.x1 + : env.y1 - device / env.yunit; + return Math.abs(n) < 1e-9 ? 0 : Number(n.toFixed(4)); + }; + + const xlabels = () => { + positions(xaxis[0], xaxis[1], origin[0], this.dx).forEach((x) => { + label(String(value(x, 'x')), x, origin[1] + 20, 'middle'); + }); + }; + + const ylabels = () => { + positions(yaxis[0], yaxis[1], origin[1], this.dy).forEach((y) => { + // The origin's own number belongs to the x axis; drawing it again here + // would stack two glyphs in the same place. + if (Math.abs(y - origin[1]) < 1e-6) return; + label(String(value(y, 'y')), origin[0] - 10, y + 4, 'end'); + }); }; line(xaxis[0], origin[1], xaxis[1], origin[1]); line(origin[0], yaxis[0], origin[0], yaxis[1]); - if (this.ticks.match(/all/)) { - xticks(); - yticks(); - } else if (this.ticks.match(/x/)) { - xticks(); - } else if (this.ticks.match(/y/)) { - yticks(); - } + const selects = (option: string, axis: 'x' | 'y'): boolean => { + const v = String(option ?? 'all'); + if (v.match(/none/)) return false; + return !!(v.match(/all/) || v.match(axis)); + }; + + if (selects(this.ticks, 'x')) xticks(); + if (selects(this.ticks, 'y')) yticks(); + if (env.xunit && selects(this.labels, 'x')) xlabels(); + if (env.yunit && selects(this.labels, 'y')) ylabels(); if (this.arrows[0]) { svg diff --git a/packages/pstricks/src/lib/pstricks.ts b/packages/pstricks/src/lib/pstricks.ts index eea8e4c4..2d47e1f7 100644 --- a/packages/pstricks/src/lib/pstricks.ts +++ b/packages/pstricks/src/lib/pstricks.ts @@ -174,7 +174,8 @@ export const Functions = { dy: 1 * this.yunit, arrows: [0, 0], dots: [0, 0], - ticks: 'all' + ticks: 'all', + labels: 'all' }; if (m[1]) { var options = parseOptions(m[1]); @@ -184,6 +185,11 @@ export const Functions = { if (options.Dy) { obj.dy = Number(options.Dy) * this.yunit; } + // `ticks` and `labels` select which axes get marks and numbers; both + // accept all / x / y / none. Dropping them meant ticks=none still drew + // ticks and labels could never be turned on. + if (options.ticks) obj.ticks = options.ticks; + if (options.labels) obj.labels = options.labels; } // arrows? var l = parseArrows(m[2]); diff --git a/packages/pstricks/test/psaxes.test.ts b/packages/pstricks/test/psaxes.test.ts new file mode 100644 index 00000000..628694e1 --- /dev/null +++ b/packages/pstricks/test/psaxes.test.ts @@ -0,0 +1,86 @@ +import { Expressions, Functions } from '../src/lib/pstricks'; +import psgraph from '../src/lib/psgraph'; + +/** + * `ticks` and `labels` were parsed and discarded, so ticks=none still drew + * ticks and no axis ever carried a number. Ticks also stepped from the end of + * the axis rather than the origin, which put every mark at whatever offset the + * axis happened to start on. + */ +function makeContext() { + return { + xunit: 50, yunit: 50, + x0: -5, y0: -5, x1: 5, y1: 5, + w: 10, h: 10, + variables: {}, + } as any; +} + +interface Node { tag: string; attrs: { [k: string]: string }; text?: string; children: Node[] } + +function recorder() { + const root: Node = { tag: 'root', attrs: {}, children: [] }; + const wrap = (node: Node): any => ({ + append(tag: string) { + const child: Node = { tag: tag.replace(/^svg:/, ''), attrs: {}, children: [] }; + node.children.push(child); + return wrap(child); + }, + attr(k: string, v: any) { node.attrs[k] = String(v); return wrap(node); }, + style() { return wrap(node); }, + text(v: string) { node.text = String(v); return wrap(node); }, + on() { return wrap(node); }, + }); + return { root, svg: wrap(root) }; +} + +function render(raw: string) { + const ctx = makeContext(); + const m = raw.match(Expressions.psaxes) as RegExpMatchArray; + expect(m).not.toBeNull(); + const data = Functions.psaxes.call(ctx, m); + data.global = ctx; + const { root, svg } = recorder(); + psgraph.psaxes.call(data, svg); + return { + labels: root.children.filter((c) => c.tag === 'text').map((c) => c.text!), + // Arrowheads are closed paths; the two axis lines and every tick are open + // ones, so ticks are the open paths beyond those two. + tickCount: + root.children.filter((c) => c.tag === 'path' && c.attrs.d && !c.attrs.d.includes('Z')).length - 2, + }; +} + +describe('psaxes labels', () => { + it('numbers ticks on whole units, stepping from the origin', () => { + const { labels } = render('\\psaxes{->}(0,0)(-3,-3)(3,3)'); + expect(labels).toContain('0'); + expect(labels).toContain('3'); + expect(labels).toContain('-3'); + // Stepping from the axis end would land these on fractions instead. + labels.forEach((l) => expect(Number.isInteger(Number(l))).toBe(true)); + }); + + it('draws the origin number once', () => { + const { labels } = render('\\psaxes{->}(0,0)(-2,-2)(2,2)'); + expect(labels.filter((l) => l === '0')).toHaveLength(1); + }); + + it.each([ + ['labels=none', 0], + ['labels=x', 5], + ['labels=y', 4], + ])('honours %s', (opt, count) => { + expect(render(`\\psaxes[${opt}]{->}(0,0)(-2,-2)(2,2)`).labels).toHaveLength(count); + }); + + it('honours ticks=none', () => { + expect(render('\\psaxes[ticks=none]{->}(0,0)(-2,-2)(2,2)').tickCount).toBe(0); + expect(render('\\psaxes{->}(0,0)(-2,-2)(2,2)').tickCount).toBeGreaterThan(0); + }); + + it('respects a Dx step', () => { + const { labels } = render('\\psaxes[Dx=2,labels=x]{->}(0,0)(-4,-4)(4,4)'); + expect(labels).toEqual(['-4', '-2', '0', '2', '4']); + }); +}); diff --git a/packages/utils/src/svg-utils.ts b/packages/utils/src/svg-utils.ts index 20ce139b..ab5889c5 100644 --- a/packages/utils/src/svg-utils.ts +++ b/packages/utils/src/svg-utils.ts @@ -67,11 +67,16 @@ export class SVGSelection { return this.elements[0] || null; } + /** + * Sets an element's text content. + * + * `textContent` is defined on every Element, so no narrowing is needed — and + * testing `instanceof SVGTextElement` threw a ReferenceError outright in any + * DOM that does not expose that constructor as a global, jsdom included. + */ text(content: string): SVGSelection { this.elements.forEach(el => { - if (el instanceof SVGTextElement || el instanceof HTMLElement) { - el.textContent = content; - } + el.textContent = content; }); return this; } From 9ae8c99922ee594be463b82f90ac7acc36a11fd0 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 16:41:31 -0700 Subject: [PATCH 14/22] fix(conformance): measure ink against the drawing, not the canvas Ghostscript crops to the PostScript bounding box while the browser fills a fixed viewport, so ink as a fraction of the whole canvas mostly reported how differently the two pad their output. Simple pictures scored worst purely for sitting in more whitespace. Ink is now measured against the drawing's own bounding box, the same frame the layout signal already uses. The ranking now separates on what the renderers actually draw: the two lowest are the starred-fill divergence, and their gap is entirely colour, with ink and layout at 92% and above. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- tools/pstricks-conformance/README.md | 5 ++++- tools/pstricks-conformance/compare.mjs | 10 +++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/tools/pstricks-conformance/README.md b/tools/pstricks-conformance/README.md index 28bbb9e7..21d651a7 100644 --- a/tools/pstricks-conformance/README.md +++ b/tools/pstricks-conformance/README.md @@ -73,7 +73,10 @@ a pass/fail gate.** It says where to look. Three signals, each chosen to survive rasterizer differences: -- **ink** — fraction of non-white pixels. Catches missing or excess drawing. +- **ink** — non-white pixels as a fraction of the drawing's own bounding box. + Measured against the box rather than the canvas, because Ghostscript crops to + the PostScript bounding box while the browser fills a fixed viewport, and + scoring over the full canvas mostly reports that difference in padding. - **colour** — normalized hue histogram. Catches wrong, absent, or unfilled fills. - **layout** — 16×16 occupancy grid over the ink bounding box, so canvas size and crop do not dominate. Catches reordering and misplacement. diff --git a/tools/pstricks-conformance/compare.mjs b/tools/pstricks-conformance/compare.mjs index ae98762c..af3e0ee8 100644 --- a/tools/pstricks-conformance/compare.mjs +++ b/tools/pstricks-conformance/compare.mjs @@ -152,13 +152,17 @@ function describe(img) { } } const cells = grid.reduce((a, v) => a + v, 0) || 1 - const total = w * h + const box = maxX >= minX ? { w: maxX - minX + 1, h: maxY - minY + 1 } : null return { - ink: inked / total, + // Ink is measured against the drawing's own bounding box, not the canvas. + // The two renderers pad very differently — Ghostscript crops to the + // PostScript bounding box while the browser fills a fixed viewport — so + // ink over the full canvas mostly reports that difference in padding. + ink: box ? inked / (box.w * box.h) : 0, hue: hue.map((v) => v / (inked || 1)), grid: grid.map((v) => v / cells), - box: maxX >= minX ? { w: maxX - minX + 1, h: maxY - minY + 1 } : null, + box, } } From fa3726dd1b6eb9f370abff8780f1b47e8fc59a72 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 17:35:12 -0700 Subject: [PATCH 15/22] fix(pstricks): measure arc endpoints from the arc centre, and stop filling by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Endpoints were transformed from `r*cos(theta)` alone, placing them as though every arc were centred on the picture origin. A pie at (0,0) looked correct, which is why this survived — the same wedge anywhere else collapsed into a spike reaching back to the origin. The centre is now added before the transform, so an arc is the same shape wherever it sits. psarc and pswedge also defaulted to fillstyle=solid with a black fillcolor, so an unstarred \psarc drew a solid black wedge instead of the open curve PSTricks draws. PSTricks fills nothing unless a fillstyle is given or the starred form is used; both now default to none. No existing example relies on the old default — every pswedge and the one psarc in the corpus either sets a fillstyle or is starred. Both surfaced from stress cases placing arcs away from the origin, which the example corpus never did. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- packages/latex2js/test/corpus/stress-arcs.tex | 11 ++++ .../test/corpus/stress-axes-nolabels.tex | 4 ++ .../test/corpus/stress-axes-offset.tex | 4 ++ .../test/corpus/stress-axes-options.tex | 4 ++ .../test/corpus/stress-axes-plain.tex | 4 ++ packages/latex2js/test/corpus/stress-axes.tex | 6 ++ .../latex2js/test/corpus/stress-fills.tex | 8 +++ .../latex2js/test/corpus/stress-layers.tex | 11 ++++ .../latex2js/test/corpus/stress-tints.tex | 9 +++ packages/pstricks/src/lib/psgraph.ts | 7 ++- packages/pstricks/src/lib/pstricks.ts | 63 +++++++++++++------ packages/pstricks/test/arc-geometry.test.ts | 45 +++++++++++++ 12 files changed, 157 insertions(+), 19 deletions(-) create mode 100644 packages/latex2js/test/corpus/stress-arcs.tex create mode 100644 packages/latex2js/test/corpus/stress-axes-nolabels.tex create mode 100644 packages/latex2js/test/corpus/stress-axes-offset.tex create mode 100644 packages/latex2js/test/corpus/stress-axes-options.tex create mode 100644 packages/latex2js/test/corpus/stress-axes-plain.tex create mode 100644 packages/latex2js/test/corpus/stress-axes.tex create mode 100644 packages/latex2js/test/corpus/stress-fills.tex create mode 100644 packages/latex2js/test/corpus/stress-layers.tex create mode 100644 packages/latex2js/test/corpus/stress-tints.tex diff --git a/packages/latex2js/test/corpus/stress-arcs.tex b/packages/latex2js/test/corpus/stress-arcs.tex new file mode 100644 index 00000000..4b2a8b8a --- /dev/null +++ b/packages/latex2js/test/corpus/stress-arcs.tex @@ -0,0 +1,11 @@ +\psset{unit=0.9cm} +\begin{pspicture}(0,0)(13,7) +\pswedge[fillstyle=solid,fillcolor=red](2,5){1.6}{0}{90} +\pswedge[fillstyle=solid,fillcolor=blue](5,5){1.6}{90}{270} +\pswedge[fillstyle=solid,fillcolor=green](8,5){1.6}{200}{20} +\pswedge[fillstyle=solid,fillcolor=orange](11,5){1.6}{0}{360} +\psarc[linewidth=2pt,linecolor=black](2,1.8){1.4}{30}{150} +\psarc[linewidth=2pt,linecolor=red](5,1.8){1.4}{150}{30} +\psarc[linewidth=2pt,linecolor=blue](8,1.8){1.4}{0}{270} +\psarc[linewidth=2pt,linecolor=purple](11,1.8){1.4}{-45}{45} +\end{pspicture} diff --git a/packages/latex2js/test/corpus/stress-axes-nolabels.tex b/packages/latex2js/test/corpus/stress-axes-nolabels.tex new file mode 100644 index 00000000..99f1111d --- /dev/null +++ b/packages/latex2js/test/corpus/stress-axes-nolabels.tex @@ -0,0 +1,4 @@ +\psset{unit=0.8cm} +\begin{pspicture}(-5,-4)(5,4) +\psaxes[labels=none]{->}(0,0)(-4,-3)(4,3) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/stress-axes-offset.tex b/packages/latex2js/test/corpus/stress-axes-offset.tex new file mode 100644 index 00000000..749b3aa8 --- /dev/null +++ b/packages/latex2js/test/corpus/stress-axes-offset.tex @@ -0,0 +1,4 @@ +\psset{unit=0.8cm} +\begin{pspicture}(-1,-1)(9,6) +\psaxes{->}(0,0)(0,0)(8,5) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/stress-axes-options.tex b/packages/latex2js/test/corpus/stress-axes-options.tex new file mode 100644 index 00000000..c5bc1d00 --- /dev/null +++ b/packages/latex2js/test/corpus/stress-axes-options.tex @@ -0,0 +1,4 @@ +\psset{unit=0.8cm} +\begin{pspicture}(-5,-4)(5,4) +\psaxes[Dx=2,Dy=1]{<->}(0,0)(-4,-3)(4,3) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/stress-axes-plain.tex b/packages/latex2js/test/corpus/stress-axes-plain.tex new file mode 100644 index 00000000..bea565d4 --- /dev/null +++ b/packages/latex2js/test/corpus/stress-axes-plain.tex @@ -0,0 +1,4 @@ +\psset{unit=0.8cm} +\begin{pspicture}(-5,-4)(5,4) +\psaxes{->}(0,0)(-4,-3)(4,3) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/stress-axes.tex b/packages/latex2js/test/corpus/stress-axes.tex new file mode 100644 index 00000000..f721f0b2 --- /dev/null +++ b/packages/latex2js/test/corpus/stress-axes.tex @@ -0,0 +1,6 @@ +\psset{unit=0.7cm} +\begin{pspicture}(-6,-5)(6,5) +\psaxes[Dx=1,Dy=1]{<->}(0,0)(-5,-4)(5,4) +\psplot[algebraic,linewidth=2pt,linecolor=red]{-4}{4}{x^2/4-2} +\psplot[algebraic,linewidth=2pt,linecolor=blue]{-4}{4}{2*sin(x)} +\end{pspicture} diff --git a/packages/latex2js/test/corpus/stress-fills.tex b/packages/latex2js/test/corpus/stress-fills.tex new file mode 100644 index 00000000..b4dbb90f --- /dev/null +++ b/packages/latex2js/test/corpus/stress-fills.tex @@ -0,0 +1,8 @@ +\psset{unit=1cm} +\begin{pspicture}(0,0)(13,4) +\psframe[fillstyle=none](0.4,0.4)(2.4,3.4) +\psframe[fillstyle=solid,fillcolor=cyan](2.9,0.4)(4.9,3.4) +\psframe[fillstyle=hlines](5.4,0.4)(7.4,3.4) +\psframe[fillstyle=vlines,hatchcolor=red](7.9,0.4)(9.9,3.4) +\psframe[fillstyle=crosshatch,hatchsep=3pt](10.4,0.4)(12.4,3.4) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/stress-layers.tex b/packages/latex2js/test/corpus/stress-layers.tex new file mode 100644 index 00000000..4472b9e2 --- /dev/null +++ b/packages/latex2js/test/corpus/stress-layers.tex @@ -0,0 +1,11 @@ +\psset{unit=1cm} +\begin{pspicture}(0,0)(12,7) +\psgrid[gridcolor=gray!30](0,0)(12,7) +\pscircle[fillstyle=solid,fillcolor=yellow!60](3,3.5){2.6} +\psframe[fillstyle=crosshatch,hatchcolor=blue,hatchsep=6pt](1,2)(5,5) +\pspolygon[fillstyle=solid,fillcolor=red!50](6,1)(9,6)(12,1) +\pswedge[fillstyle=hlines,hatchangle=0,hatchcolor=green](9,4){2.4}{20}{160} +\psline[linewidth=3pt,linecolor=black]{<->}(0,0)(12,7) +\pscircle[fillstyle=vlines,hatchcolor=purple,hatchsep=5pt](6,3.5){1.4} +\psdots[dotsize=6pt](2,6)(6,6)(10,6) +\end{pspicture} diff --git a/packages/latex2js/test/corpus/stress-tints.tex b/packages/latex2js/test/corpus/stress-tints.tex new file mode 100644 index 00000000..6cdf8be8 --- /dev/null +++ b/packages/latex2js/test/corpus/stress-tints.tex @@ -0,0 +1,9 @@ +\psset{unit=1cm} +\begin{pspicture}(0,0)(13,3) +\psframe[fillstyle=solid,fillcolor=blue!20,linestyle=none](0.2,0.5)(2.2,2.5) +\psframe[fillstyle=solid,fillcolor=blue!40,linestyle=none](2.4,0.5)(4.4,2.5) +\psframe[fillstyle=solid,fillcolor=blue!60,linestyle=none](4.6,0.5)(6.6,2.5) +\psframe[fillstyle=solid,fillcolor=blue!80,linestyle=none](6.8,0.5)(8.8,2.5) +\psframe[fillstyle=solid,fillcolor=blue,linestyle=none](9,0.5)(11,2.5) +\psframe[fillstyle=solid,fillcolor=red!50!blue,linestyle=none](11.2,0.5)(13,2.5) +\end{pspicture} diff --git a/packages/pstricks/src/lib/psgraph.ts b/packages/pstricks/src/lib/psgraph.ts index 1fb645a3..934497aa 100644 --- a/packages/pstricks/src/lib/psgraph.ts +++ b/packages/pstricks/src/lib/psgraph.ts @@ -447,7 +447,12 @@ const psgraph: any = { const xlabels = () => { positions(xaxis[0], xaxis[1], origin[0], this.dx).forEach((x) => { - label(String(value(x, 'x')), x, origin[1] + 20, 'middle'); + // The origin's number sits directly under the y axis, which would draw + // the axis line straight through the glyph, so it shifts clear of it + // and serves both axes — as it does on a hand-drawn pair of axes. + const atOrigin = Math.abs(x - origin[0]) < 1e-6; + if (atOrigin) label(String(value(x, 'x')), x - 7, origin[1] + 20, 'end'); + else label(String(value(x, 'x')), x, origin[1] + 20, 'middle'); }); }; diff --git a/packages/pstricks/src/lib/pstricks.ts b/packages/pstricks/src/lib/pstricks.ts index 2d47e1f7..49222b15 100644 --- a/packages/pstricks/src/lib/pstricks.ts +++ b/packages/pstricks/src/lib/pstricks.ts @@ -22,6 +22,41 @@ function parseLinewidth(value: string): number { return Number(m[1]) * (m[2] ? 1.333 : 1); } +/** + * Device-space endpoints of an arc, measured from the arc's own centre. + * + * The radius is an offset from `(cx, cy)`, not from the picture origin, so the + * centre has to be added before the coordinate transform. Transforming + * `r*cos(theta)` alone places both endpoints as though every arc were centred + * on the origin — correct only for one that happens to be, which is why a pie + * at (0,0) looked right while the same wedge anywhere else collapsed to a + * spike reaching back to the origin. + * + * @param cx - centre x in picture units (empty or absent means 0) + * @param cy - centre y in picture units + * @param r - radius in picture units + * @param angleA - start angle in radians + * @param angleB - end angle in radians + * @returns the `A` and `B` endpoints in device coordinates + */ +function arcEndpoints( + this: any, + cx: any, + cy: any, + r: any, + angleA: number, + angleB: number +): { A: { x: number; y: number }; B: { x: number; y: number } } { + const ox = cx === undefined || cx === '' ? 0 : Number(cx); + const oy = cy === undefined || cy === '' ? 0 : Number(cy); + const radius = Number(r); + const at = (angle: number) => ({ + x: X.call(this, ox + radius * Math.cos(angle)), + y: Y.call(this, oy + radius * Math.sin(angle)) + }); + return { A: at(angleA), B: at(angleB) }; +} + export const Expressions = { pspicture: /\\begin\{pspicture\}\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, psframe: /\\psframe\*?(\[[^\]]*\])?\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, @@ -306,7 +341,10 @@ export const Functions = { var obj: any = { linecolor: 'black', linestyle: 'solid', - fillstyle: 'solid', + // PSTricks leaves every shape unfilled unless a fillstyle is + // given or the starred form is used; an unstarred \psarc is an open + // curve, not a solid black wedge. + fillstyle: 'none', fillcolor: 'black', linewidth: 2, arrows: arrows, @@ -335,14 +373,7 @@ export const Functions = { obj.r = Number(m[5]) * this.xunit; obj.angleA = (Number(m[6]) * Math.PI) / 180; obj.angleB = (Number(m[7]) * Math.PI) / 180; - obj.A = { - x: X.call(this, Number(m[5]) * Math.cos(obj.angleA)), - y: Y.call(this, Number(m[5]) * Math.sin(obj.angleA)) - }; - obj.B = { - x: X.call(this, Number(m[5]) * Math.cos(obj.angleB)), - y: Y.call(this, Number(m[5]) * Math.sin(obj.angleB)) - }; + Object.assign(obj, arcEndpoints.call(this, m[3], m[4], m[5], obj.angleA, obj.angleB)); return obj; }, psline(this: PSTricksContext, m: any) { @@ -620,7 +651,10 @@ export const Functions = { var obj: any = { linecolor: 'black', linestyle: 'solid', - fillstyle: 'solid', + // PSTricks leaves every shape unfilled unless a fillstyle is + // given or the starred form is used; an unstarred \psarc is an open + // curve, not a solid black wedge. + fillstyle: 'none', fillcolor: 'black', linewidth: 2 }; @@ -630,14 +664,7 @@ export const Functions = { obj.r = Number(m[4]) * this.xunit; obj.angleA = (Number(m[5]) * Math.PI) / 180; obj.angleB = (Number(m[6]) * Math.PI) / 180; - obj.A = { - x: X.call(this, Number(m[4]) * Math.cos(obj.angleA)), - y: Y.call(this, Number(m[4]) * Math.sin(obj.angleA)) - }; - obj.B = { - x: X.call(this, Number(m[4]) * Math.cos(obj.angleB)), - y: Y.call(this, Number(m[4]) * Math.sin(obj.angleB)) - }; + Object.assign(obj, arcEndpoints.call(this, m[2], m[3], m[4], obj.angleA, obj.angleB)); return obj; }, pscustom(this: PSTricksContext, m: any) { diff --git a/packages/pstricks/test/arc-geometry.test.ts b/packages/pstricks/test/arc-geometry.test.ts index 90626876..287f1fe4 100644 --- a/packages/pstricks/test/arc-geometry.test.ts +++ b/packages/pstricks/test/arc-geometry.test.ts @@ -73,6 +73,51 @@ describe('arc sweep direction', () => { }); }); +describe('arc endpoints are measured from the arc centre', () => { + // The endpoints were transformed from `r*cos(theta)` alone, which places them + // as though every arc were centred on the picture origin. A pie at (0,0) + // looked right; the same wedge anywhere else collapsed into a spike reaching + // back to the origin. + it.each([ + ['pswedge', '\\pswedge(3,2){1}{0}{90}', 'pswedge(0,0){1}{0}{90}'], + ['psarc', '\\psarc(3,2){1}{0}{90}', 'psarc(0,0){1}{0}{90}'], + ])('%s at an offset centre is the same shape translated', (name, offset) => { + const a = arcCommand(pathsFrom(name, offset)[0]); + const b = arcCommand(pathsFrom(name, offset.replace('(3,2)', '(0,0)'))[0]); + // ctx has xunit = yunit = 50, so a centre 3 right and 2 up moves the + // endpoint 150 right and 100 up (y inverted). + expect(a.x - b.x).toBeCloseTo(150, 3); + expect(a.y - b.y).toBeCloseTo(-100, 3); + expect(a.r).toBeCloseTo(b.r, 6); + }); + + it('puts a quarter wedge endpoint one radius from its centre', () => { + // \pswedge(3,2){1}{0}{90} ends at (3,3): one unit above the centre. + const { x, y } = arcCommand(pathsFrom('pswedge', '\\pswedge(3,2){1}{0}{90}')[0]); + expect(x).toBeCloseTo((10 - (5 - 3)) * 50, 3); + expect(y).toBeCloseTo((5 - 3) * 50, 3); + }); +}); + +describe('shapes are unfilled unless asked', () => { + // PSTricks fills nothing by default; an unstarred \psarc is an open curve. + it('draws an unstarred psarc as an open path', () => { + const d = pathsFrom('psarc', '\\psarc[linecolor=red](2,2){1}{30}{150}')[0]; + expect(d.trim().endsWith('Z')).toBe(false); + expect(d).not.toContain(' L '); + }); + + it('closes a starred psarc into a filled wedge', () => { + const d = pathsFrom('psarc', '\\psarc*(2,2){1}{30}{150}')[0]; + expect(d.trim().endsWith('Z')).toBe(true); + }); + + it('closes an unstarred psarc once a fillstyle is given', () => { + const d = pathsFrom('psarc', '\\psarc[fillstyle=solid,fillcolor=red](2,2){1}{30}{150}')[0]; + expect(d.trim().endsWith('Z')).toBe(true); + }); +}); + describe('pie chart geometry', () => { it('gives every wedge of a five-slice pie the same radius and direction', () => { const wedges = [[0, 72], [72, 144], [144, 216], [216, 288], [288, 360]].map( From f09d537c4cf64843cab81015b9901edc2138a646 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 17:39:11 -0700 Subject: [PATCH 16/22] test(pstricks): add a stress corpus, and a before/after view of a change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nine stress cases covering combinations the example corpus never had: arcs at every angle configuration including reversed and full turns, arcs placed away from the origin, every fill style side by side, deep interleaved layering, xcolor tint ramps, and five axis configurations. Two of the bugs fixed in this branch were found by these and by nothing else. before-after.mjs pairs each example as it rendered before a change, as it renders now, and what PSTricks draws for the same source, ordered by how much moved. Where the canvases match it compares per pixel — an ink-position signature cannot see a recolour, so a plane turning from black to light grey registered as no change at all; where the drawing changed size it falls back to the coarse signature and says so rather than reporting a meaningless percentage. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- tools/pstricks-conformance/before-after.mjs | 248 ++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 tools/pstricks-conformance/before-after.mjs diff --git a/tools/pstricks-conformance/before-after.mjs b/tools/pstricks-conformance/before-after.mjs new file mode 100644 index 00000000..05c42c7b --- /dev/null +++ b/tools/pstricks-conformance/before-after.mjs @@ -0,0 +1,248 @@ +#!/usr/bin/env node +/** + * before-after.mjs — three-way view of a rendering change. + * + * Pairs each example as it rendered before a change, as it renders now, and + * what real PSTricks draws for the same source, ordered by how much the render + * actually moved. Cases that did not change are dropped: the point is to see + * what a change did, not to re-read the whole corpus. + * + * node before-after.mjs --before --after --ref \ + * --notes notes.json --out page.html + */ + +import { readFileSync, writeFileSync, readdirSync, existsSync } from 'node:fs' +import { join, basename, resolve } from 'node:path' +import { inflateSync } from 'node:zlib' + +const argv = process.argv.slice(2) +const flag = (n, d) => (argv.includes(n) ? argv[argv.indexOf(n) + 1] : d) + +const beforeDir = resolve(flag('--before', '')) +const afterDir = resolve(flag('--after', '')) +const refDir = flag('--ref', '') ? resolve(flag('--ref', '')) : null +const notesPath = flag('--notes', '') +const outFile = resolve(flag('--out', 'before-after.html')) +const GRID = 24 + +/** Decodes an 8-bit truecolour PNG; returns null for anything else. */ +function decode(path) { + try { + const b = readFileSync(path) + if (b.readUInt32BE(0) !== 0x89504e47) return null + let p = 8, w = 0, h = 0, bd = 0, ct = 0, il = 0 + const idat = [] + while (p < b.length) { + const len = b.readUInt32BE(p) + const type = b.toString('ascii', p + 4, p + 8) + if (type === 'IHDR') { w = b.readUInt32BE(p + 8); h = b.readUInt32BE(p + 12); bd = b[p + 16]; ct = b[p + 17]; il = b[p + 20] } + else if (type === 'IDAT') idat.push(b.subarray(p + 8, p + 8 + len)) + else if (type === 'IEND') break + p += 12 + len + } + if (bd !== 8 || il !== 0 || (ct !== 2 && ct !== 6)) return null + const ch = ct === 6 ? 4 : 3 + const data = inflateSync(Buffer.concat(idat)) + const stride = w * ch + const out = Buffer.alloc(w * h * ch) + let prev = Buffer.alloc(stride) + for (let y = 0, o = 0; y < h; y++) { + const f = data[o++] + const line = data.subarray(o, o + stride); o += stride + const cur = Buffer.alloc(stride) + for (let i = 0; i < stride; i++) { + const a = i >= ch ? cur[i - ch] : 0 + const up = prev[i] + const ul = i >= ch ? prev[i - ch] : 0 + let v = line[i] + if (f === 1) v += a + else if (f === 2) v += up + else if (f === 3) v += (a + up) >> 1 + else if (f === 4) { + const pa = Math.abs(up - ul), pb = Math.abs(a - ul), pc = Math.abs(a + up - 2 * ul) + v += (pa <= pb && pa <= pc) ? a : (pb <= pc ? up : ul) + } + cur[i] = v & 255 + } + cur.copy(out, y * stride); prev = cur + } + return { w, h, ch, data: out } + } catch { return null } +} + +/** + * Coarse per-cell ink signature over the whole canvas. Both sides come from the + * same renderer at the same size, so no bounding-box normalization is wanted + * here — a shape moving within the canvas is exactly what should register. + */ +function signature(img) { + const { w, h, ch, data } = img + const grid = new Array(GRID * GRID).fill(0) + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + const i = (y * w + x) * ch + if (data[i] > 244 && data[i + 1] > 244 && data[i + 2] > 244) continue + grid[Math.min(GRID - 1, Math.floor((y / h) * GRID)) * GRID + Math.min(GRID - 1, Math.floor((x / w) * GRID))]++ + } + } + const total = grid.reduce((a, v) => a + v, 0) || 1 + return grid.map((v) => v / total) +} + +/** Total absolute difference between two signatures, 0 (identical) to 2. */ +const signatureDrift = (a, b) => a.reduce((sum, v, i) => sum + Math.abs(v - b[i]), 0) + +/** + * Fraction of pixels that changed noticeably. + * + * Both sides come from the same renderer at the same canvas size, so they can + * be compared directly — and must be: an ink-position signature cannot see a + * recolour, so a plane turning from solid black to light grey registered as no + * change at all. + * + * @returns the changed fraction, or null when the two canvases differ in size + */ +function pixelDrift(a, b) { + if (a.w !== b.w || a.h !== b.h) return null + let changed = 0 + const n = a.w * a.h + for (let i = 0; i < n; i++) { + const ai = i * a.ch + const bi = i * b.ch + const d = Math.abs(a.data[ai] - b.data[bi]) + + Math.abs(a.data[ai + 1] - b.data[bi + 1]) + + Math.abs(a.data[ai + 2] - b.data[bi + 2]) + if (d > 24) changed++ + } + return changed / n +} + +const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]) +const b64 = (p) => readFileSync(p).toString('base64') + +// --------------------------------------------------------------------- main + +if (!existsSync(beforeDir) || !existsSync(afterDir)) { + console.error('before-after: --before and --after must both exist') + process.exit(2) +} + +const notes = notesPath && existsSync(notesPath) ? JSON.parse(readFileSync(notesPath, 'utf8')) : {} +const names = readdirSync(afterDir).filter((f) => f.endsWith('.png')).map((f) => basename(f, '.png')).sort() + +const rows = [] +for (const name of names) { + const bPath = join(beforeDir, `${name}.png`) + const aPath = join(afterDir, `${name}.png`) + const isNew = !existsSync(bPath) + const a = decode(aPath) + if (!a) continue + + let moved = isNew ? Infinity : 0 + let resized = false + if (!isNew) { + const b = decode(bPath) + if (!b) continue + const direct = pixelDrift(b, a) + if (direct === null) { + // Different canvas sizes mean the drawing itself changed height, which is + // a change in its own right; per-pixel comparison no longer applies. + resized = true + moved = signatureDrift(signature(b), signature(a)) + } else { + moved = direct + } + } + + const rPath = refDir ? join(refDir, `${name}.png`) : null + rows.push({ + name, + isNew, + moved, + resized, + note: notes[name], + before: isNew ? null : b64(bPath), + after: b64(aPath), + ref: rPath && existsSync(rPath) ? b64(rPath) : null, + }) +} + +// Below this is antialiasing noise, not a change worth showing. +const THRESHOLD = 0.0004 +const changed = rows.filter((r) => r.isNew || r.moved > THRESHOLD).sort((a, b) => b.moved - a.moved) +const unchanged = rows.length - changed.length + +const card = (r) => ` +
    +
    + ${esc(r.name)} + ${r.isNew + ? 'new' + : r.resized + ? 'reflowed — the drawing changed size' + : `${(r.moved * 100).toFixed(1)}% of pixels changed`} +
    + ${r.note ? `

    ${esc(r.note)}

    ` : ''} +
    + ${r.isNew ? '' : `

    Before

    ${esc(r.name)} before
    `} +

    After

    ${esc(r.name)} after
    + ${r.ref ? `

    PSTricks

    ${esc(r.name)} reference
    ` : ''} +
    +
    ` + +const html = `Before and After + +
    +
    +

    LaTeX2JS · rendering changes

    +

    What the Fixes Changed

    +

    Every example whose render actually moved, worst first, against what real PSTricks draws for the same source. ${unchanged} unchanged examples are omitted.

    +
    + ${changed.map(card).join('')} +
    ${changed.length} changed · ${unchanged} unchanged · both sides rendered by LaTeX2JS at the same size, compared per pixel
    +
    +` + +writeFileSync(outFile, html) +console.log(`before-after: ${changed.length} changed, ${unchanged} unchanged -> ${outFile} (${(Buffer.byteLength(html) / 1024 / 1024).toFixed(2)} MB)`) +for (const r of changed.slice(0, 15)) { + const tag = r.isNew ? 'new' : r.resized ? 'reflow' : (r.moved * 100).toFixed(2) + '%' + console.log(` ${tag.padStart(7)} ${r.name}`) +} From 65ce6760d610360bb51b0914db56a719de753e42 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 18:36:08 -0700 Subject: [PATCH 17/22] fix(pstricks): draw a real psgrid, and give an arrowed axis end to its arrowhead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A PSTricks grid is three things: fine subdivision lines, a heavier line on each unit, and optional coordinate numbers. Only the unit lines were drawn, in `linecolor` — which `gridcolor` could not override, so setting it did nothing. subgriddiv, subgridcolor, subgridwidth, gridcolor and gridwidth are now honoured, with the subdivisions drawn first so the unit lines sit over them. Grid numbers are opt-in rather than on by default. PSTricks draws them outside the grid on an unbounded page; an SVG is sized to the picture's declared bounds, so on a grid reaching the edge they would land outside the viewport and be clipped. When asked for they are clamped inside so they always show. psaxes also gives an arrowed end of an axis to its arrowhead: verified against PSTricks, where an arrow suppresses both the tick and its number at that end while the un-arrowed end keeps both. Only a tick coincident with the tip is dropped, so one that merely falls short of it survives. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- packages/pstricks/src/lib/psgraph.ts | 96 +++++++++++++++++++++++---- packages/pstricks/src/lib/pstricks.ts | 13 +++- packages/pstricks/test/psaxes.test.ts | 32 +++++++-- 3 files changed, 122 insertions(+), 19 deletions(-) diff --git a/packages/pstricks/src/lib/psgraph.ts b/packages/pstricks/src/lib/psgraph.ts index 934497aa..b63c41c3 100644 --- a/packages/pstricks/src/lib/psgraph.ts +++ b/packages/pstricks/src/lib/psgraph.ts @@ -398,6 +398,16 @@ const psgraph: any = { * axis happens to begin on, so an axis spanning -3.5 to 3.5 was ticked and * labelled at half-integers instead of on the whole numbers. */ + /** + * An axis end that carries an arrowhead, or null. `arrows[0]` points at the + * low end of each axis and `arrows[1]` at the high end, matching the order + * the arrowheads are drawn below. + */ + const arrowedEnds = (axis: number[]): Array => [ + this.arrows[0] ? axis[0] : null, + this.arrows[1] ? axis[1] : null, + ]; + const positions = (from: number, to: number, at: number, step: number): number[] => { if (!(step > 0) || !isFinite(step)) return []; // Y inverts the axis, so a vertical span arrives with its ends the other @@ -407,7 +417,12 @@ const psgraph: any = { const out: number[] = []; for (let v = at; v <= hi + 1e-6; v += step) out.push(v); for (let v = at - step; v >= lo - 1e-6; v -= step) out.unshift(v); - return out; + + // PSTricks gives an arrowhead the end of the axis to itself: where one is + // drawn, the tick and its number are both suppressed. A tick that merely + // falls short of the tip keeps them, so only a coincident one is dropped. + const suppressed = arrowedEnds([from, to]).filter((v): v is number => v !== null); + return out.filter((v) => !suppressed.some((end) => Math.abs(v - end) < 1e-6)); }; var xticks = () => { @@ -980,26 +995,79 @@ const psgraph: any = { } }, + /** + * A PSTricks grid is three things, not one: fine subdivision lines, a heavier + * line on each unit, and the coordinate numbered along the left and bottom + * edges. Only the unit lines were drawn, in `linecolor` — which `gridcolor` + * could not override — so a grid was a flat mesh with no reading on it. + */ psgrid(svg: any): void { const x0 = this.x0, y0 = this.y0, x1 = this.x1, y1 = this.y1; - for (let x = x0; x <= x1 + 0.001; x += this.xunit) { + const gridcolor = this.gridcolor ?? this.linecolor; + const gridwidth = dimension(this.gridwidth, 0.8); + const subdiv = Math.max(0, Math.floor(Number(this.subgriddiv ?? 5))); + const subcolor = this.subgridcolor ?? 'gray'; + const subwidth = dimension(this.subgridwidth, 0.4); + + const rule = (a: number, b: number, c: number, d: number, color: string, width: number) => { svg .append('svg:line') - .attr('x1', x).attr('y1', y0) - .attr('x2', x).attr('y2', y1) - .style('stroke', this.linecolor) - .style('stroke-width', this.gridwidth) + .attr('x1', a).attr('y1', b).attr('x2', c).attr('y2', d) + .style('stroke', color) + .style('stroke-width', width) .style('stroke-opacity', 1); + }; + + /** Line offsets across a span, stepping by `step` from `origin`. */ + const rungs = (lo: number, hi: number, origin: number, step: number): number[] => { + if (!(step > 0) || !isFinite(step)) return []; + const out: number[] = []; + for (let v = origin; v <= hi + 1e-6; v += step) out.push(v); + for (let v = origin - step; v >= lo - 1e-6; v -= step) out.unshift(v); + return out; + }; + + const ox = this.originX ?? x0; + const oy = this.originY ?? y0; + + // Subdivisions first, so the unit lines and labels sit over them. + if (subdiv > 1) { + for (const x of rungs(x0, x1, ox, this.xunit / subdiv)) rule(x, y0, x, y1, subcolor, subwidth); + for (const y of rungs(y0, y1, oy, this.yunit / subdiv)) rule(x0, y, x1, y, subcolor, subwidth); } - for (let y = y0; y <= y1 + 0.001; y += this.yunit) { + + const xs = rungs(x0, x1, ox, this.xunit); + const ys = rungs(y0, y1, oy, this.yunit); + for (const x of xs) rule(x, y0, x, y1, gridcolor, gridwidth); + for (const y of ys) rule(x0, y, x1, y, gridcolor, gridwidth); + + // Grid numbers are off unless asked for. PSTricks draws them outside the + // grid on an unbounded page; an SVG is sized to the picture's declared + // bounds, so on a grid that reaches the edge — the common case — they would + // land outside the viewport and be clipped away. A default nobody can see + // is worse than no default, so they are opt-in and clamped inside. + if (!this.gridlabels || this.gridlabels === 'none' || this.gridlabels === '0') return; + const size = dimension(this.gridlabels, 10); + const labelcolor = this.gridlabelcolor ?? 'black'; + const text = (s: string, x: number, y: number, anchor: string) => { svg - .append('svg:line') - .attr('x1', x0).attr('y1', y) - .attr('x2', x1).attr('y2', y) - .style('stroke', this.linecolor) - .style('stroke-width', this.gridwidth) - .style('stroke-opacity', 1); - } + .append('svg:text') + .attr('x', x).attr('y', y) + .attr('text-anchor', anchor) + .attr('font-size', size) + .attr('font-family', 'serif') + .style('fill', labelcolor) + .text(s); + }; + + const round = (n: number) => (Math.abs(n) < 1e-9 ? 0 : Number(n.toFixed(4))); + const env = this.global || {}; + // Clamped inside the picture so a grid flush with the edge still shows its + // numbers rather than pushing them out of the viewport. + const belowY = Math.min(y1 + size + 4, (env.h ?? 0) * (env.yunit ?? 1) - 2); + const leftX = Math.max(x0 - 4, size); + for (const x of xs) text(String(round(x / env.xunit - env.w + env.x1)), x, belowY, 'middle'); + for (const y of ys) text(String(round(env.y1 - y / env.yunit)), leftX, y + size / 3, 'end'); }, psellipse(svg: any): void { diff --git a/packages/pstricks/src/lib/pstricks.ts b/packages/pstricks/src/lib/pstricks.ts index 49222b15..b2bedf20 100644 --- a/packages/pstricks/src/lib/pstricks.ts +++ b/packages/pstricks/src/lib/pstricks.ts @@ -573,7 +573,14 @@ export const Functions = { linecolor: 'black', linestyle: 'solid', linewidth: 0.5, - gridwidth: 0.5 + // PSTricks grid defaults: a heavier line on the unit, five finer + // subdivisions between, and the coordinate numbered along two edges. + gridcolor: 'black', + gridwidth: '0.8pt', + subgriddiv: 5, + subgridcolor: 'gray', + subgridwidth: '0.4pt', + gridlabelcolor: 'black' }; if (m[1]) Object.assign(obj, parseOptions(m[1])); // \psgrid[opts](x0,y0)(x1,y1) — defaults to the whole pspicture bounds. @@ -590,6 +597,10 @@ export const Functions = { obj.y1 = Math.max(y0, y1); obj.xunit = this.xunit; obj.yunit = this.yunit; + // The renderer numbers each line, which needs the picture coordinate the + // device position stands for. + obj.originX = X.call(this, 0); + obj.originY = Y.call(this, 0); return obj; }, psellipse(this: PSTricksContext, m: any) { diff --git a/packages/pstricks/test/psaxes.test.ts b/packages/pstricks/test/psaxes.test.ts index 628694e1..85469af0 100644 --- a/packages/pstricks/test/psaxes.test.ts +++ b/packages/pstricks/test/psaxes.test.ts @@ -53,7 +53,7 @@ function render(raw: string) { describe('psaxes labels', () => { it('numbers ticks on whole units, stepping from the origin', () => { - const { labels } = render('\\psaxes{->}(0,0)(-3,-3)(3,3)'); + const { labels } = render('\\psaxes(0,0)(-3,-3)(3,3)'); expect(labels).toContain('0'); expect(labels).toContain('3'); expect(labels).toContain('-3'); @@ -68,8 +68,8 @@ describe('psaxes labels', () => { it.each([ ['labels=none', 0], - ['labels=x', 5], - ['labels=y', 4], + ['labels=x', 4], + ['labels=y', 3], ])('honours %s', (opt, count) => { expect(render(`\\psaxes[${opt}]{->}(0,0)(-2,-2)(2,2)`).labels).toHaveLength(count); }); @@ -81,6 +81,30 @@ describe('psaxes labels', () => { it('respects a Dx step', () => { const { labels } = render('\\psaxes[Dx=2,labels=x]{->}(0,0)(-4,-4)(4,4)'); - expect(labels).toEqual(['-4', '-2', '0', '2', '4']); + // 4 is the arrowed end, so it carries the arrowhead instead of a number. + expect(labels).toEqual(['-4', '-2', '0', '2']); + }); +}); + +describe('an arrowhead takes the end of its axis', () => { + // Verified against PSTricks: with {->} the tick and the number at the + // positive end are both suppressed, while the un-arrowed end keeps both. + it('suppresses the tick and the number where an arrow is drawn', () => { + const arrowed = render('\\psaxes[labels=x]{->}(0,0)(-3,-3)(3,3)'); + const plain = render('\\psaxes[labels=x](0,0)(-3,-3)(3,3)'); + expect(plain.labels).toContain('3'); + expect(arrowed.labels).not.toContain('3'); + expect(arrowed.tickCount).toBe(plain.tickCount - 2); // one per axis + }); + + it('leaves the un-arrowed end alone', () => { + expect(render('\\psaxes[labels=x]{->}(0,0)(-3,-3)(3,3)').labels).toContain('-3'); + }); + + it('suppresses both ends when both carry an arrow', () => { + const { labels } = render('\\psaxes[labels=x]{<->}(0,0)(-3,-3)(3,3)'); + expect(labels).not.toContain('3'); + expect(labels).not.toContain('-3'); + expect(labels).toContain('2'); }); }); From 73879201be5ce493e7922c1edc4b18d7d8307409 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 18:38:04 -0700 Subject: [PATCH 18/22] fix(latex2js): stop stacking blank lines against block elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A blank source line becomes a
    , and a heading carries its own margins, so a break next to one produced two gaps where the author asked for one. Runs of breaks also survived verbatim — the parser snapshot pinned nine consecutive
    between two paragraphs — which spread a short document over roughly half again the height it needs. Breaks adjacent to a block element are dropped, a run collapses to one, and trailing breaks are trimmed. Spacing is left to the elements' own margins, where it can be controlled from CSS. pspicture and verbatim keep their lines untouched. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- packages/latex2js/src/lib/parser.ts | 27 +++++++++++++++++++ .../test/__snapshots__/parser.test.ts.snap | 24 ++--------------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/latex2js/src/lib/parser.ts b/packages/latex2js/src/lib/parser.ts index f75ea411..5b72011c 100644 --- a/packages/latex2js/src/lib/parser.ts +++ b/packages/latex2js/src/lib/parser.ts @@ -356,12 +356,39 @@ class Parser { return this.isIgnored('\\begin{' + name + '}'); } + /** + * A blank source line becomes a `
    `, but a heading already carries its own + * margins, so a `
    ` next to one stacks two gaps where the author asked for + * one. Dropping the adjacent break leaves the heading's own spacing to do the + * work — and a run of breaks collapses to a single paragraph gap. + */ + collapseBreaks(lines: string[]): string[] { + const isBlock = (l: string) => /^\s*<(h[1-6]|ul|ol|li|p|div|table|blockquote)\b/i.test(l); + const out: string[] = []; + for (const line of lines) { + if (line !== '
    ') { + while (isBlock(line) && out[out.length - 1] === '
    ') out.pop(); + out.push(line); + continue; + } + if (!out.length) continue; + if (isBlock(out[out.length - 1])) continue; + if (out[out.length - 1] === '
    ') continue; + out.push(line); + } + while (out[out.length - 1] === '
    ') out.pop(); + return out; + } + newEnvironment(type: string): void { if ( this.environment && (this.environment.lines.length || this.environment.type !== 'math') ) { this.environment.settings = { ...this.settings }; + if (!this.environment.type.match(/pspicture|verbatim/)) { + this.environment.lines = this.collapseBreaks(this.environment.lines); + } this.objects.push(this.environment); } this.environment = { diff --git a/packages/latex2js/test/__snapshots__/parser.test.ts.snap b/packages/latex2js/test/__snapshots__/parser.test.ts.snap index a13511ea..e0cefedd 100644 --- a/packages/latex2js/test/__snapshots__/parser.test.ts.snap +++ b/packages/latex2js/test/__snapshots__/parser.test.ts.snap @@ -4,10 +4,7 @@ exports[`Parser parse 1`] = ` [ { "lines": [ - "
    ", "Let's get to the point. The core of PSTricks is graphics!", - "
    ", - "
    ", ], "settings": { "fillstyle": "none", @@ -613,11 +610,7 @@ exports[`Parser parse 1`] = ` }, { "lines": [ - "
    ", - "
    ", - "
    ", "which can be produced using the following $\\TeX$:", - "
    ", ], "settings": { "fillstyle": "none", @@ -665,10 +658,7 @@ exports[`Parser parse 1`] = ` "type": "verbatim", }, { - "lines": [ - "
    ", - "
    ", - ], + "lines": [], "settings": { "fillstyle": "none", "h": 10, @@ -693,10 +683,7 @@ exports[`Parser parser 1`] = ` [ { "lines": [ - "
    ", "Let's get to the point. The core of PSTricks is graphics!", - "
    ", - "
    ", ], "settings": { "fillstyle": "none", @@ -1302,11 +1289,7 @@ exports[`Parser parser 1`] = ` }, { "lines": [ - "
    ", - "
    ", - "
    ", "which can be produced using the following $\\TeX$:", - "
    ", ], "settings": { "fillstyle": "none", @@ -1354,10 +1337,7 @@ exports[`Parser parser 1`] = ` "type": "verbatim", }, { - "lines": [ - "
    ", - "
    ", - ], + "lines": [], "settings": { "fillstyle": "none", "h": 10, From 4ab6f8bb352ac93d5830d811f20ffb984096c984 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 18:47:21 -0700 Subject: [PATCH 19/22] fix(ci): let packageManager decide the pnpm version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pnpm/action-setup refuses to install when the version is named both in its own `version` input and in package.json's `packageManager`, so every run has failed during setup — before installing a single dependency. Dropping the input leaves `packageManager` as the one place the version is declared, which is also what keeps local and CI runs on the same pnpm. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- .github/workflows/run-tests.yaml | 6 +- bundle/latex2html5.bundle.js | 798 ++++++++++++++++++++++++------- 2 files changed, 632 insertions(+), 172 deletions(-) diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 4c7da12e..2329375a 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -14,10 +14,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + # The pnpm version comes from `packageManager` in package.json. Naming it + # here as well makes the action refuse to install rather than pick one. - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 11 run_install: false - name: Install Node.js @@ -52,10 +53,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + # The pnpm version comes from `packageManager` in package.json. Naming it + # here as well makes the action refuse to install rather than pick one. - uses: pnpm/action-setup@v4 name: Install pnpm with: - version: 11 run_install: false - name: Install Node.js diff --git a/bundle/latex2html5.bundle.js b/bundle/latex2html5.bundle.js index 26a2424d..9dbf1480 100644 --- a/bundle/latex2html5.bundle.js +++ b/bundle/latex2html5.bundle.js @@ -1833,7 +1833,10 @@ exports.Functions = { example: () => '

    Example

    ', problem: () => '

    Problem

    ', proof: () => '

    Proof

    ', - qed: () => '$\\qed$', + // amsthm closes a proof with an open square. Emitted as a character rather + // than as math: MathJax defines no \qed, so the previous `$\qed$` surfaced + // an "Undefined control sequence" box at the end of every proof. + qed: () => '', solution: () => '

    Solution

    ', theorem: () => '

    Theorem

    ' }; @@ -2038,8 +2041,8 @@ class Parser { env.content.forEach((c) => this.walkContent(c)); } else { - // enumerate / nicebox: content is text lines (with transforms). - env.content.forEach((c) => this.walkContent(c)); + // enumerate / itemize / nicebox: content is text lines (with transforms). + this.walkTextContent(env.content); } if (env.end && env.end.name !== name) { this.diagnose('warning', `\\end{${env.end.name}} does not match \\begin{${name}}`, env.end.loc); @@ -2049,6 +2052,53 @@ class Parser { } this.newEnvironment('math'); } + /** + * Walk a text environment's content, rejoining the nodes that came from one + * source line. + * + * `EnvContent` matches `Command` before `Line`, and a command's tail stops at + * the next command, so `\item First with \textbf{bold} text` arrives as two + * command nodes. Walking them individually renders each as its own line, + * which broke every list item at its first macro. pspicture keeps the + * per-node walk, because it depends on receiving commands separately. + */ + walkTextContent(content) { + let pending = []; + const flush = () => { + if (!pending.length) + return; + const text = pending + .map((n) => (n.kind === 'line' ? this.lineToString(n) : n.raw)) + .join(''); + pending = []; + this.pushMathLine(text); + }; + content.forEach((node) => { + if (node.kind === 'env') { + flush(); + this.walkEnv(node); + return; + } + // An empty Line is the newline itself: it closes the line being built, + // or is a genuine paragraph break when there is nothing to close. + if (node.kind === 'line' && node.parts.length === 0) { + if (pending.length) + flush(); + else + this.pushBlankLine(false); + return; + } + const at = node.loc && node.loc.line; + const open = pending.length ? pending[0].loc && pending[0].loc.line : at; + if (pending.length && at !== open) + flush(); + pending.push(node); + // A Line node consumed its own EOL, so nothing more belongs to it. + if (node.kind === 'line') + flush(); + }); + flush(); + } /** * Walk one node of environment content. Behavior depends on the current * environment: inside pspicture we collect commands (and raw lines) for plot @@ -2152,10 +2202,41 @@ class Parser { isIgnoredEnv(name) { return this.isIgnored('\\begin{' + name + '}'); } + /** + * A blank source line becomes a `
    `, but a heading already carries its own + * margins, so a `
    ` next to one stacks two gaps where the author asked for + * one. Dropping the adjacent break leaves the heading's own spacing to do the + * work — and a run of breaks collapses to a single paragraph gap. + */ + collapseBreaks(lines) { + const isBlock = (l) => /^\s*<(h[1-6]|ul|ol|li|p|div|table|blockquote)\b/i.test(l); + const out = []; + for (const line of lines) { + if (line !== '
    ') { + while (isBlock(line) && out[out.length - 1] === '
    ') + out.pop(); + out.push(line); + continue; + } + if (!out.length) + continue; + if (isBlock(out[out.length - 1])) + continue; + if (out[out.length - 1] === '
    ') + continue; + out.push(line); + } + while (out[out.length - 1] === '
    ') + out.pop(); + return out; + } newEnvironment(type) { if (this.environment && (this.environment.lines.length || this.environment.type !== 'math')) { this.environment.settings = { ...this.settings }; + if (!this.environment.type.match(/pspicture|verbatim/)) { + this.environment.lines = this.collapseBreaks(this.environment.lines); + } this.objects.push(this.environment); } this.environment = { @@ -2327,8 +2408,16 @@ class Parser { // ------------------------------------------------------------------------- // Text / header transforms (reused from the old parser, string-based) // ------------------------------------------------------------------------- - parseTextExpression(line, exp, k, contents) { - var match = line.match(exp); + /** + * Text transforms run in sequence over one line, so each must match the + * value the previous ones produced. Matching the pristine line instead makes + * `matchrepl` search `contents` for a literal that an earlier transform has + * already rewritten, and the replacement silently does nothing — which is + * why `\section{Cauchy--Schwarz}` survived as source text once `--` had + * become an en dash. + */ + parseTextExpression(_line, exp, k, contents) { + var match = contents.match(exp); if (match) { return this.Text.Functions[k].call(this, match, contents); } @@ -2861,6 +2950,128 @@ function buildCurvePath(data, closed) { } return d; } +const TAU = Math.PI * 2; +/** Points to device units, matching the linewidth conversion in pstricks.ts. */ +const PT_TO_PX = 1.333; +/** + * Line directions each hatched fill style draws, as offsets from `hatchangle`. + * PSTricks hatches at `hatchangle` for hlines, ninety degrees off for vlines, + * and both for crosshatch — so the default 45 degrees makes hlines diagonal, + * not horizontal. + */ +const HATCH_DIRECTIONS = { + hlines: [0], + vlines: [90], + crosshatch: [0, 90], +}; +/** PSTricks hatch parameter defaults, in points except the angle and colour. */ +const HATCH_DEFAULTS = { hatchwidth: 0.8, hatchsep: 4, hatchangle: 45, hatchcolor: 'black' }; +let patternSeq = 0; +/** Reads a dimension that may carry a `pt` suffix, in device units. */ +function dimension(value, fallbackPt) { + if (typeof value === 'number' && isFinite(value)) + return value * PT_TO_PX; + const m = typeof value === 'string' ? value.trim().match(/^([\d.]+)\s*(pt)?$/) : null; + return (m ? Number(m[1]) : fallbackPt) * PT_TO_PX; +} +/** + * Whether a shape has any fill at all. Renderers that must close a path before + * it can be filled ask this; the paint itself comes from {@link resolveFill}. + * + * @param ctx - the shape's parsed data + * @returns true when the shape should be built as a closed, fillable region + */ +function hasFill(ctx) { + return !!ctx.filled || (!!ctx.fillstyle && ctx.fillstyle !== 'none'); +} +/** + * Resolves a shape's SVG fill value, defining a hatch pattern when the style + * calls for one. + * + * Every renderer previously spelled this decision itself, in three mutually + * inconsistent ways: `fillstyle=hlines` became a solid fill on pspolygon and + * psarc, and no fill at all on psellipse, pswedge and pscurve. Routing all of + * them through one resolver makes an unimplemented style behave the same + * everywhere, and gives the hatched styles a real rendering. + * + * @param ctx - the shape's parsed data, carrying fillstyle and hatch options + * @param svg - the container the pattern definition is attached to + * @returns an SVG paint value: a colour, a `url(#…)` pattern, or `none` + */ +function resolveFill(ctx, svg) { + const style = ctx.fillstyle ?? 'none'; + // The starred forms set `filled`; they fill flat regardless of style. + if (ctx.filled || style === 'solid') + return ctx.fillcolor; + if (style === 'none' || !style) + return 'none'; + const starred = style.endsWith('*'); + const directions = HATCH_DIRECTIONS[starred ? style.slice(0, -1) : style]; + // An unrecognised style is not a fill; guessing solid is what made the same + // input render differently depending on the shape. + if (!directions) + return 'none'; + const sep = Math.max(1, dimension(ctx.hatchsep, HATCH_DEFAULTS.hatchsep)); + const width = Math.max(0.2, dimension(ctx.hatchwidth, HATCH_DEFAULTS.hatchwidth)); + const angle = Number(ctx.hatchangle ?? HATCH_DEFAULTS.hatchangle) || 0; + const color = ctx.hatchcolor ?? HATCH_DEFAULTS.hatchcolor; + const id = 'l2j-hatch-' + ++patternSeq; + const pattern = svg + .append('svg:defs') + .append('svg:pattern') + .attr('id', id) + .attr('patternUnits', 'userSpaceOnUse') + .attr('width', sep) + .attr('height', sep) + // SVG's y axis runs opposite to the PSTricks angle convention. + .attr('patternTransform', 'rotate(' + -angle + ')'); + // A starred hatch lays its lines over the fill colour instead of nothing. + if (starred) { + pattern.append('svg:rect').attr('width', sep).attr('height', sep).style('fill', ctx.fillcolor); + } + for (const d of directions) { + const line = pattern.append('svg:line').style('stroke', color).style('stroke-width', width); + if (d === 0) + line.attr('x1', 0).attr('y1', sep / 2).attr('x2', sep).attr('y2', sep / 2); + else + line.attr('x1', sep / 2).attr('y1', 0).attr('x2', sep / 2).attr('y2', sep); + } + return 'url(#' + id + ')'; +} +/** + * SVG arc flags for a PSTricks arc running from `angleA` to `angleB`. + * + * PSTricks always sweeps counter-clockwise in its own coordinates, taking the + * long way round when the end angle precedes the start. `Y` inverts the axis, + * so that counter-clockwise sweep is drawn with SVG's sweep-flag 0 — using 1 + * traces the complementary arc, which is what bowed every `\pswedge` inward + * and turned a pie chart into a star. + * + * @param angleA - start angle in radians + * @param angleB - end angle in radians + * @returns the sweep span plus SVG's large-arc and sweep flags + */ +function arcFlags(angleA, angleB) { + let delta = angleB - angleA; + if (!isFinite(delta)) + delta = 0; + delta = ((delta % TAU) + TAU) % TAU; + return { delta, large: delta > Math.PI ? 1 : 0, sweep: 0 }; +} +/** + * A full turn cannot be expressed as one SVG arc, because the start and end + * points coincide. Such a sweep is emitted as two half-turns instead. + * + * @param cx - centre x in device units + * @param cy - centre y in device units + * @param r - radius in device units + * @returns a closed circular path + */ +function fullCirclePath(cx, cy, r) { + return ('M ' + (cx - r) + ' ' + cy + + ' A ' + r + ' ' + r + ' 0 1 0 ' + (cx + r) + ' ' + cy + + ' A ' + r + ' ' + r + ' 0 1 0 ' + (cx - r) + ' ' + cy + ' Z'); +} function curveRenderer(svg) { const d = buildCurvePath(this.data, !!this.closed); if (!d) @@ -2871,7 +3082,7 @@ function curveRenderer(svg) { .style('stroke-width', this.linewidth) .style('stroke', this.linecolor) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'solid' || this.filled ? this.fillcolor : 'none'); + .style('fill', resolveFill(this, svg)); } const psgraph = { env: null, @@ -2891,7 +3102,7 @@ const psgraph = { }; }, psframe(svg) { - const filled = this.filled || this.fillstyle === 'solid'; + const filled = hasFill(this); if (filled) { svg .append('svg:rect') @@ -2899,7 +3110,7 @@ const psgraph = { .attr('y', Math.min(this.y1, this.y2)) .attr('width', Math.abs(this.x2 - this.x1)) .attr('height', Math.abs(this.y2 - this.y1)) - .style('fill', this.fillcolor) + .style('fill', resolveFill(this, svg)) .style('stroke', 'none'); } svg @@ -2940,21 +3151,21 @@ const psgraph = { .style('stroke-opacity', 1); }, pscircle: function (svg) { - const filled = this.filled || this.fillstyle === 'solid'; + const filled = hasFill(this); svg .append('svg:circle') .attr('cx', this.cx) .attr('cy', this.cy) .attr('r', this.r) .style('stroke', this.linecolor) - .style('fill', filled ? this.fillcolor : 'none') + .style('fill', resolveFill(this, svg)) .style('stroke-width', this.linewidth) .style('stroke-opacity', 1); }, psplot(svg) { var context = []; context.push('M'); - if (this.fillstyle === 'solid') { + if (hasFill(this)) { context.push(this.data[0]); context.push(utils_1.Y.call(this.global, 0)); } @@ -2966,7 +3177,7 @@ const psgraph = { this.data.forEach((data) => { context.push(data); }); - if (this.fillstyle === 'solid') { + if (hasFill(this)) { context.push(this.data[this.data.length - 2]); context.push(utils_1.Y.call(this.global, 0)); context.push('Z'); @@ -2977,7 +3188,7 @@ const psgraph = { .attr('class', 'psplot') .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'none' ? 'none' : this.fillcolor) + .style('fill', resolveFill(this, svg)) .style('stroke', this.linecolor); }, pspolygon(svg) { @@ -2995,27 +3206,25 @@ const psgraph = { .attr('d', context.join(' ')) .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'none' && !this.filled ? 'none' : this.fillcolor) + .style('fill', resolveFill(this, svg)) .style('stroke', 'black'); }, psarc(svg) { - const sweep = this.angleB - this.angleA > 0 ? 1 : 0; - const large = Math.abs(this.angleB - this.angleA) > Math.PI ? 1 : 0; - const filled = this.filled || this.fillstyle === 'solid'; - const d = filled - ? 'M ' + this.cx + ' ' + this.cy + - ' L ' + this.A.x + ' ' + this.A.y + - ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + - ' ' + this.B.x + ' ' + this.B.y + ' Z' - : 'M ' + this.A.x + ' ' + this.A.y + - ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + - ' ' + this.B.x + ' ' + this.B.y; + const { delta, large, sweep } = arcFlags(this.angleA, this.angleB); + const filled = hasFill(this); + const arc = ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + + ' ' + this.B.x + ' ' + this.B.y; + const d = delta === 0 + ? fullCirclePath(this.cx, this.cy, this.r) + : filled + ? 'M ' + this.cx + ' ' + this.cy + ' L ' + this.A.x + ' ' + this.A.y + arc + ' Z' + : 'M ' + this.A.x + ' ' + this.A.y + arc; svg .append('svg:path') .attr('d', d) .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', filled ? this.fillcolor : 'none') + .style('fill', resolveFill(this, svg)) .style('stroke', this.linecolor); }, psaxes(svg) { @@ -3030,28 +3239,106 @@ const psgraph = { .style('stroke', 'rgb(0,0,0)') .style('stroke-opacity', 1); } + /** + * Tick positions, stepped outward from the origin rather than from the end + * of the axis. Starting at the end puts every mark at whatever offset the + * axis happens to begin on, so an axis spanning -3.5 to 3.5 was ticked and + * labelled at half-integers instead of on the whole numbers. + */ + /** + * An axis end that carries an arrowhead, or null. `arrows[0]` points at the + * low end of each axis and `arrows[1]` at the high end, matching the order + * the arrowheads are drawn below. + */ + const arrowedEnds = (axis) => [ + this.arrows[0] ? axis[0] : null, + this.arrows[1] ? axis[1] : null, + ]; + const positions = (from, to, at, step) => { + if (!(step > 0) || !isFinite(step)) + return []; + // Y inverts the axis, so a vertical span arrives with its ends the other + // way round. Walking it as given produced no y ticks at all. + const lo = Math.min(from, to); + const hi = Math.max(from, to); + const out = []; + for (let v = at; v <= hi + 1e-6; v += step) + out.push(v); + for (let v = at - step; v >= lo - 1e-6; v -= step) + out.unshift(v); + // PSTricks gives an arrowhead the end of the axis to itself: where one is + // drawn, the tick and its number are both suppressed. A tick that merely + // falls short of the tip keeps them, so only a coincident one is dropped. + const suppressed = arrowedEnds([from, to]).filter((v) => v !== null); + return out.filter((v) => !suppressed.some((end) => Math.abs(v - end) < 1e-6)); + }; var xticks = () => { - for (var x = xaxis[0]; x <= xaxis[1]; x += this.dx) { + positions(xaxis[0], xaxis[1], origin[0], this.dx).forEach((x) => { line(x, origin[1] - 5, x, origin[1] + 5); - } + }); }; var yticks = () => { - for (var y = yaxis[0]; y <= yaxis[1]; y += this.dy) { + positions(yaxis[0], yaxis[1], origin[1], this.dy).forEach((y) => { line(origin[0] - 5, y, origin[0] + 5, y); - } + }); + }; + const env = this.global || {}; + /** Draws one tick number, positioned clear of its axis. */ + const label = (text, x, y, anchor) => { + svg + .append('svg:text') + .attr('x', x) + .attr('y', y) + .attr('text-anchor', anchor) + .attr('font-size', 13) + .attr('font-family', 'serif') + .style('fill', 'black') + .text(text); + }; + /** Tick values are device coordinates; labels need the value they stand for. */ + const value = (device, axis) => { + const n = axis === 'x' + ? device / env.xunit - env.w + env.x1 + : env.y1 - device / env.yunit; + return Math.abs(n) < 1e-9 ? 0 : Number(n.toFixed(4)); + }; + const xlabels = () => { + positions(xaxis[0], xaxis[1], origin[0], this.dx).forEach((x) => { + // The origin's number sits directly under the y axis, which would draw + // the axis line straight through the glyph, so it shifts clear of it + // and serves both axes — as it does on a hand-drawn pair of axes. + const atOrigin = Math.abs(x - origin[0]) < 1e-6; + if (atOrigin) + label(String(value(x, 'x')), x - 7, origin[1] + 20, 'end'); + else + label(String(value(x, 'x')), x, origin[1] + 20, 'middle'); + }); + }; + const ylabels = () => { + positions(yaxis[0], yaxis[1], origin[1], this.dy).forEach((y) => { + // The origin's own number belongs to the x axis; drawing it again here + // would stack two glyphs in the same place. + if (Math.abs(y - origin[1]) < 1e-6) + return; + label(String(value(y, 'y')), origin[0] - 10, y + 4, 'end'); + }); }; line(xaxis[0], origin[1], xaxis[1], origin[1]); line(origin[0], yaxis[0], origin[0], yaxis[1]); - if (this.ticks.match(/all/)) { - xticks(); - yticks(); - } - else if (this.ticks.match(/x/)) { + const selects = (option, axis) => { + const v = String(option ?? 'all'); + if (v.match(/none/)) + return false; + return !!(v.match(/all/) || v.match(axis)); + }; + if (selects(this.ticks, 'x')) xticks(); - } - else if (this.ticks.match(/y/)) { + if (selects(this.ticks, 'y')) yticks(); - } + if (env.xunit && selects(this.labels, 'x')) + xlabels(); + if (env.yunit && selects(this.labels, 'y')) + ylabels(); if (this.arrows[0]) { svg .append('path') @@ -3361,103 +3648,119 @@ const psgraph = { pspicture(svg) { var env = this.env; var el = this.$el; - // Source-order initial draw: the parser records `env.elements` in - // document order, so layers (fills under lines, etc.) respect the author's - // order. Falls back to the old type-grouped iteration for legacy data. + const plots = this.plot; + // The parser records `env.elements` in document order, so fills sit under + // lines exactly as authored. const elements = env && env.elements; - if (elements && elements.length) { - elements.forEach((item) => { - if (!item || !item.name || item.name.match(/rput/)) - return; - if (!psgraph.hasOwnProperty(item.name)) - return; - item.data.global = env; - psgraph[item.name].call(item.data, svg); + /** + * Recomputes an interactive element against the pointer position. Static + * elements keep the data the parser produced. + */ + function resolveData(item, coords, variables) { + if (!coords || !item.fn) + return item.data; + if (item.name === 'psplot') { + Object.entries(variables || {}).forEach(([name, value]) => { + env.variables[name] = value; + }); + const d = item.fn.call(env, item.match); + d.global = Object.assign({}, env); + return d; + } + if (item.name === 'userline') { + const d = item.fn.call(env, item.match); + env.x2 = coords[0]; + env.y2 = coords[1]; + item.data.x2 = env.x2; + item.data.y2 = env.y2; + if (item.data.xExp2) { + item.data.x2 = d.userx2(coords); + item.data.x1 = d.userx(coords); + } + else if (item.data.xExp) { + item.data.x2 = d.userx(coords); + } + if (item.data.yExp2) { + item.data.y2 = d.usery2(coords); + item.data.y1 = d.usery(coords); + } + else if (item.data.yExp) { + item.data.y2 = d.usery(coords); + } + d.global = Object.assign({}, env); + Object.assign(d, item.data); + return d; + } + return item.data; + } + /** Evaluates every \uservariable at the pointer position, in source order. */ + function readVariables(coords) { + const variables = {}; + const source = elements && elements.length + ? elements.filter((i) => i && i.name === 'uservariable') + : ((plots && plots.uservariable) || []).map((p) => ({ ...p, name: 'uservariable' })); + source.forEach((item) => { + env.userx = coords[0]; + env.usery = coords[1]; + const dd = item.fn.call(env, item.match); + variables[item.data.name] = dd.value; }); - } - else { - Object.keys(this.plot).forEach((key) => { - const plot = this.plot[key]; + return variables; + } + /** + * Draws the whole picture into a fresh layer. + * + * Redrawing everything is what keeps interaction faithful to the source. + * Removing just the interactive elements and appending them again put them + * at the end of the SVG — on top of every later shape — and re-emitted + * them grouped by command type rather than in document order, so a correct + * diagram silently reordered itself the first time the pointer crossed it. + */ + let layer = null; + function drawLayer(coords) { + if (layer) + layer.remove(); + layer = svg.append('svg:g').attr('class', 'pspicture-layer'); + const variables = coords ? readVariables(coords) : {}; + if (elements && elements.length) { + elements.forEach((item) => { + if (!item || !item.name || item.name.match(/rput/)) + return; + if (!psgraph.hasOwnProperty(item.name)) + return; + const data = resolveData(item, coords, variables); + data.global = env; + psgraph[item.name].call(data, layer); + }); + return; + } + // Legacy data without an ordered element list: fall back to the + // type-grouped iteration, which cannot express author order. + Object.keys(plots).forEach((key) => { if (key.match(/rput/)) return; - if (psgraph.hasOwnProperty(key)) { - plot.forEach((data) => { - data.data.global = env; - psgraph[key].call(data.data, svg); - }); - } + if (!psgraph.hasOwnProperty(key)) + return; + plots[key].forEach((entry) => { + const item = { name: key, data: entry.data, match: entry.match, fn: entry.fn }; + const data = resolveData(item, coords, variables); + data.global = env; + psgraph[key].call(data, layer); + }); }); } + drawLayer(null); svg.on('touchmove', function (event) { event.preventDefault(); var touch = event.touches ? event.touches[0] : null; var rect = event.target.getBoundingClientRect(); var touchcoords = touch ? [touch.clientX - rect.left, touch.clientY - rect.top] : [0, 0]; - userEvent(touchcoords); + drawLayer(touchcoords); }); svg.on('mousemove', function (event) { var coords = [event.offsetX || 0, event.offsetY || 0]; - userEvent(coords); + drawLayer(coords); }); - const plots = this.plot; - function userEvent(coords) { - svg.selectAll('.userline').remove(); - svg.selectAll('.psplot').remove(); - var currentEnvironment = {}; - Object.entries(plots || {}) - .forEach(([k, plot]) => { - if (k.match(/uservariable/)) { - plot.forEach((data) => { - data.env.userx = coords[0]; - data.env.usery = coords[1]; - var dd = data.fn.call(data.env, data.match); - currentEnvironment[data.data.name] = dd.value; - }); - } - }); - Object.entries(plots || {}) - .forEach(([k, plot]) => { - if (k.match(/psplot/)) { - plot.forEach((data) => { - Object.entries(currentEnvironment || {}) - .forEach(([name, variable]) => { - data.env.variables[name] = variable; - }); - var d = data.fn.call(data.env, data.match); - d.global = {}; - Object.assign(d.global, env); - psgraph[k].call(d, svg); - }); - } - if (k.match(/userline/)) { - plot.forEach((data) => { - var d = data.fn.call(data.env, data.match); - data.env.x2 = coords[0]; - data.env.y2 = coords[1]; - data.data.x2 = data.env.x2; - data.data.y2 = data.env.y2; - if (data.data.xExp2) { - data.data.x2 = d.userx2(coords); - data.data.x1 = d.userx(coords); - } - else if (data.data.xExp) { - data.data.x2 = d.userx(coords); - } - if (data.data.yExp2) { - data.data.y2 = d.usery2(coords); - data.data.y1 = d.usery(coords); - } - else if (data.data.yExp) { - data.data.y2 = d.usery(coords); - } - d.global = {}; - Object.assign(d.global, env); - Object.assign(d, data.data); - psgraph[k].call(d, svg); - }); - } - }); - } // Enhanced cleanup and RPUT processing psgraph.processRputElements.call(this, el); }, @@ -3472,26 +3775,82 @@ const psgraph = { .style('stroke', 'none'); } }, + /** + * A PSTricks grid is three things, not one: fine subdivision lines, a heavier + * line on each unit, and the coordinate numbered along the left and bottom + * edges. Only the unit lines were drawn, in `linecolor` — which `gridcolor` + * could not override — so a grid was a flat mesh with no reading on it. + */ psgrid(svg) { const x0 = this.x0, y0 = this.y0, x1 = this.x1, y1 = this.y1; - for (let x = x0; x <= x1 + 0.001; x += this.xunit) { + const gridcolor = this.gridcolor ?? this.linecolor; + const gridwidth = dimension(this.gridwidth, 0.8); + const subdiv = Math.max(0, Math.floor(Number(this.subgriddiv ?? 5))); + const subcolor = this.subgridcolor ?? 'gray'; + const subwidth = dimension(this.subgridwidth, 0.4); + const rule = (a, b, c, d, color, width) => { svg .append('svg:line') - .attr('x1', x).attr('y1', y0) - .attr('x2', x).attr('y2', y1) - .style('stroke', this.linecolor) - .style('stroke-width', this.gridwidth) + .attr('x1', a).attr('y1', b).attr('x2', c).attr('y2', d) + .style('stroke', color) + .style('stroke-width', width) .style('stroke-opacity', 1); - } - for (let y = y0; y <= y1 + 0.001; y += this.yunit) { + }; + /** Line offsets across a span, stepping by `step` from `origin`. */ + const rungs = (lo, hi, origin, step) => { + if (!(step > 0) || !isFinite(step)) + return []; + const out = []; + for (let v = origin; v <= hi + 1e-6; v += step) + out.push(v); + for (let v = origin - step; v >= lo - 1e-6; v -= step) + out.unshift(v); + return out; + }; + const ox = this.originX ?? x0; + const oy = this.originY ?? y0; + // Subdivisions first, so the unit lines and labels sit over them. + if (subdiv > 1) { + for (const x of rungs(x0, x1, ox, this.xunit / subdiv)) + rule(x, y0, x, y1, subcolor, subwidth); + for (const y of rungs(y0, y1, oy, this.yunit / subdiv)) + rule(x0, y, x1, y, subcolor, subwidth); + } + const xs = rungs(x0, x1, ox, this.xunit); + const ys = rungs(y0, y1, oy, this.yunit); + for (const x of xs) + rule(x, y0, x, y1, gridcolor, gridwidth); + for (const y of ys) + rule(x0, y, x1, y, gridcolor, gridwidth); + // Grid numbers are off unless asked for. PSTricks draws them outside the + // grid on an unbounded page; an SVG is sized to the picture's declared + // bounds, so on a grid that reaches the edge — the common case — they would + // land outside the viewport and be clipped away. A default nobody can see + // is worse than no default, so they are opt-in and clamped inside. + if (!this.gridlabels || this.gridlabels === 'none' || this.gridlabels === '0') + return; + const size = dimension(this.gridlabels, 10); + const labelcolor = this.gridlabelcolor ?? 'black'; + const text = (s, x, y, anchor) => { svg - .append('svg:line') - .attr('x1', x0).attr('y1', y) - .attr('x2', x1).attr('y2', y) - .style('stroke', this.linecolor) - .style('stroke-width', this.gridwidth) - .style('stroke-opacity', 1); - } + .append('svg:text') + .attr('x', x).attr('y', y) + .attr('text-anchor', anchor) + .attr('font-size', size) + .attr('font-family', 'serif') + .style('fill', labelcolor) + .text(s); + }; + const round = (n) => (Math.abs(n) < 1e-9 ? 0 : Number(n.toFixed(4))); + const env = this.global || {}; + // Clamped inside the picture so a grid flush with the edge still shows its + // numbers rather than pushing them out of the viewport. + const belowY = Math.min(y1 + size + 4, (env.h ?? 0) * (env.yunit ?? 1) - 2); + const leftX = Math.max(x0 - 4, size); + for (const x of xs) + text(String(round(x / env.xunit - env.w + env.x1)), x, belowY, 'middle'); + for (const y of ys) + text(String(round(env.y1 - y / env.yunit)), leftX, y + size / 3, 'end'); }, psellipse(svg) { svg @@ -3503,7 +3862,7 @@ const psgraph = { .style('stroke', this.linecolor) .style('stroke-width', this.linewidth) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'solid' ? this.fillcolor : 'none'); + .style('fill', resolveFill(this, svg)); }, psbezier(svg) { svg @@ -3525,26 +3884,28 @@ const psgraph = { .style('stroke-width', this.linewidth) .style('stroke', this.linecolor) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'solid' || this.filled ? this.fillcolor : 'none'); + .style('fill', resolveFill(this, svg)); }, psecurve: curveRenderer, psccurve: curveRenderer, pswedge(svg) { - const sweep = this.angleB - this.angleA > 0 ? 1 : 0; - const large = Math.abs(this.angleB - this.angleA) > Math.PI ? 1 : 0; + const { delta, large, sweep } = arcFlags(this.angleA, this.angleB); + const d = delta === 0 + ? fullCirclePath(this.cx, this.cy, this.r) + : 'M ' + this.cx + ' ' + this.cy + + ' L ' + this.A.x + ' ' + this.A.y + + ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + + ' ' + this.B.x + ' ' + this.B.y + ' Z'; svg .append('svg:path') - .attr('d', 'M ' + this.cx + ' ' + this.cy + - ' L ' + this.A.x + ' ' + this.A.y + - ' A ' + this.r + ' ' + this.r + ' 0 ' + large + ' ' + sweep + - ' ' + this.B.x + ' ' + this.B.y + ' Z') + .attr('d', d) .style('stroke-width', this.linewidth) .style('stroke', this.linecolor) .style('stroke-opacity', 1) - .style('fill', this.fillstyle === 'solid' ? this.fillcolor : 'none'); + .style('fill', resolveFill(this, svg)); }, pscustom(svg) { - const filled = this.filled || this.fillstyle === 'solid'; + const filled = hasFill(this); let d = ''; let started = false; (this.commands || []).forEach((cmd) => { @@ -3598,7 +3959,7 @@ const psgraph = { .style('stroke-width', this.linewidth) .style('stroke', this.linestyle === 'none' ? 'none' : this.linecolor) .style('stroke-opacity', 1) - .style('fill', filled ? this.fillcolor : 'none'); + .style('fill', resolveFill(this, svg)); }, processRputElements(el) { // Validate container @@ -3709,6 +4070,33 @@ function parseLinewidth(value) { return 2; return Number(m[1]) * (m[2] ? 1.333 : 1); } +/** + * Device-space endpoints of an arc, measured from the arc's own centre. + * + * The radius is an offset from `(cx, cy)`, not from the picture origin, so the + * centre has to be added before the coordinate transform. Transforming + * `r*cos(theta)` alone places both endpoints as though every arc were centred + * on the origin — correct only for one that happens to be, which is why a pie + * at (0,0) looked right while the same wedge anywhere else collapsed to a + * spike reaching back to the origin. + * + * @param cx - centre x in picture units (empty or absent means 0) + * @param cy - centre y in picture units + * @param r - radius in picture units + * @param angleA - start angle in radians + * @param angleB - end angle in radians + * @returns the `A` and `B` endpoints in device coordinates + */ +function arcEndpoints(cx, cy, r, angleA, angleB) { + const ox = cx === undefined || cx === '' ? 0 : Number(cx); + const oy = cy === undefined || cy === '' ? 0 : Number(cy); + const radius = Number(r); + const at = (angle) => ({ + x: utils_1.X.call(this, ox + radius * Math.cos(angle)), + y: utils_1.Y.call(this, oy + radius * Math.sin(angle)) + }); + return { A: at(angleA), B: at(angleB) }; +} exports.Expressions = { pspicture: /\\begin\{pspicture\}\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, psframe: /\\psframe\*?(\[[^\]]*\])?\(\s*(.*),(.*)\s*\)\(\s*(.*),(.*)\s*\)/, @@ -3832,7 +4220,8 @@ exports.Functions = { dy: 1 * this.yunit, arrows: [0, 0], dots: [0, 0], - ticks: 'all' + ticks: 'all', + labels: 'all' }; if (m[1]) { var options = (0, utils_1.parseOptions)(m[1]); @@ -3842,6 +4231,13 @@ exports.Functions = { if (options.Dy) { obj.dy = Number(options.Dy) * this.yunit; } + // `ticks` and `labels` select which axes get marks and numbers; both + // accept all / x / y / none. Dropping them meant ticks=none still drew + // ticks and labels could never be turned on. + if (options.ticks) + obj.ticks = options.ticks; + if (options.labels) + obj.labels = options.labels; } // arrows? var l = (0, utils_1.parseArrows)(m[2]); @@ -3961,7 +4357,10 @@ exports.Functions = { var obj = { linecolor: 'black', linestyle: 'solid', - fillstyle: 'solid', + // PSTricks leaves every shape unfilled unless a fillstyle is + // given or the starred form is used; an unstarred \psarc is an open + // curve, not a solid black wedge. + fillstyle: 'none', fillcolor: 'black', linewidth: 2, arrows: arrows, @@ -3990,14 +4389,7 @@ exports.Functions = { obj.r = Number(m[5]) * this.xunit; obj.angleA = (Number(m[6]) * Math.PI) / 180; obj.angleB = (Number(m[7]) * Math.PI) / 180; - obj.A = { - x: utils_1.X.call(this, Number(m[5]) * Math.cos(obj.angleA)), - y: utils_1.Y.call(this, Number(m[5]) * Math.sin(obj.angleA)) - }; - obj.B = { - x: utils_1.X.call(this, Number(m[5]) * Math.cos(obj.angleB)), - y: utils_1.Y.call(this, Number(m[5]) * Math.sin(obj.angleB)) - }; + Object.assign(obj, arcEndpoints.call(this, m[3], m[4], m[5], obj.angleA, obj.angleB)); return obj; }, psline(m) { @@ -4201,7 +4593,14 @@ exports.Functions = { linecolor: 'black', linestyle: 'solid', linewidth: 0.5, - gridwidth: 0.5 + // PSTricks grid defaults: a heavier line on the unit, five finer + // subdivisions between, and the coordinate numbered along two edges. + gridcolor: 'black', + gridwidth: '0.8pt', + subgriddiv: 5, + subgridcolor: 'gray', + subgridwidth: '0.4pt', + gridlabelcolor: 'black' }; if (m[1]) Object.assign(obj, (0, utils_1.parseOptions)(m[1])); @@ -4219,6 +4618,10 @@ exports.Functions = { obj.y1 = Math.max(y0, y1); obj.xunit = this.xunit; obj.yunit = this.yunit; + // The renderer numbers each line, which needs the picture coordinate the + // device position stands for. + obj.originX = utils_1.X.call(this, 0); + obj.originY = utils_1.Y.call(this, 0); return obj; }, psellipse(m) { @@ -4281,7 +4684,10 @@ exports.Functions = { var obj = { linecolor: 'black', linestyle: 'solid', - fillstyle: 'solid', + // PSTricks leaves every shape unfilled unless a fillstyle is + // given or the starred form is used; an unstarred \psarc is an open + // curve, not a solid black wedge. + fillstyle: 'none', fillcolor: 'black', linewidth: 2 }; @@ -4292,14 +4698,7 @@ exports.Functions = { obj.r = Number(m[4]) * this.xunit; obj.angleA = (Number(m[5]) * Math.PI) / 180; obj.angleB = (Number(m[6]) * Math.PI) / 180; - obj.A = { - x: utils_1.X.call(this, Number(m[4]) * Math.cos(obj.angleA)), - y: utils_1.Y.call(this, Number(m[4]) * Math.sin(obj.angleA)) - }; - obj.B = { - x: utils_1.X.call(this, Number(m[4]) * Math.cos(obj.angleB)), - y: utils_1.Y.call(this, Number(m[4]) * Math.sin(obj.angleB)) - }; + Object.assign(obj, arcEndpoints.call(this, m[2], m[3], m[4], obj.angleA, obj.angleB)); return obj; }, pscustom(m) { @@ -4725,7 +5124,7 @@ function parseExpression(source) { },{}],23:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.MATH_CONSTANTS = exports.MATH_FUNCTIONS = exports.ExpressionError = exports.parseExpression = exports.select = exports.SVGSelection = exports.dotType = exports.arrowType = exports.Yinv = exports.Y = exports.Xinv = exports.X = exports.evaluate = exports.parseArrows = exports.parseOptions = exports.RE = exports.convertUnits = exports.matchrepl = exports.simplerepl = void 0; +exports.MATH_CONSTANTS = exports.MATH_FUNCTIONS = exports.ExpressionError = exports.parseExpression = exports.select = exports.SVGSelection = exports.dotType = exports.arrowType = exports.Yinv = exports.Y = exports.Xinv = exports.X = exports.evaluate = exports.parseArrows = exports.parseOptions = exports.resolveColor = exports.RE = exports.convertUnits = exports.matchrepl = exports.simplerepl = void 0; const expression_1 = require("./expression"); const simplerepl = function (regex, replace) { return function (_m, contents) { @@ -4773,6 +5172,58 @@ exports.RE = { coordsOpt: '(\\(\\s*([^\\)]*),([^\\)]*)\\s*\\))?', coords: '\\(\\s*([^\\)]*),([^\\)]*)\\s*\\)' }; +/** Option keys whose value names a colour. */ +const COLOR_KEYS = ['linecolor', 'fillcolor', 'hatchcolor', 'gridcolor', 'bordercolor', 'shadowcolor', 'labelcolor']; +/** + * Base colours xcolor mixes against, as RGB triples. Only the names that can + * appear on the left of a `!` need resolving; every other colour is handed to + * the browser unchanged, so plain names keep whatever CSS already gives them. + */ +const BASE_COLORS = { + red: [255, 0, 0], green: [0, 255, 0], blue: [0, 0, 255], + cyan: [0, 255, 255], magenta: [255, 0, 255], yellow: [255, 255, 0], + black: [0, 0, 0], white: [255, 255, 255], gray: [128, 128, 128], + grey: [128, 128, 128], orange: [255, 165, 0], purple: [128, 0, 128], + brown: [165, 42, 42], pink: [255, 192, 203], olive: [128, 128, 0], + violet: [148, 0, 211], teal: [0, 128, 128], lime: [0, 255, 0], +}; +/** + * Resolves an xcolor tint expression to a CSS colour. + * + * `gray!40` means forty percent gray against white, and `gray!40!red` mixes + * against red instead. A browser cannot read either, and an unparsable fill + * silently falls back to black — which is how a light grey plane rendered as + * a solid black one. + * + * @param value - a colour name, optionally with `!` mix terms + * @returns a CSS colour; names without a mix term are returned untouched + */ +const resolveColor = function (value) { + const parts = String(value).split('!').map((p) => p.trim()); + if (parts.length < 2) + return value; + const rgb = (name) => BASE_COLORS[name.toLowerCase()] ?? null; + let current = rgb(parts[0]); + if (!current) + return value; + for (let i = 1; i < parts.length; i += 2) { + const pct = Number(parts[i]); + if (!isFinite(pct)) + return value; + // An omitted second operand mixes against white, as xcolor does. + const against = parts[i + 1] ? rgb(parts[i + 1]) : [255, 255, 255]; + if (!against) + return value; + const w = Math.max(0, Math.min(100, pct)) / 100; + current = [ + Math.round(current[0] * w + against[0] * (1 - w)), + Math.round(current[1] * w + against[1] * (1 - w)), + Math.round(current[2] * w + against[2] * (1 - w)), + ]; + } + return 'rgb(' + current[0] + ',' + current[1] + ',' + current[2] + ')'; +}; +exports.resolveColor = resolveColor; // OPTIONS // converts [showorigin=false,labels=none, Dx=3.14] to {showorigin: 'false', labels: 'none', Dx: '3.14'} const parseOptions = function (opts) { @@ -4782,7 +5233,9 @@ const parseOptions = function (opts) { all.forEach((option) => { var kv = option.split('='); if (kv.length == 2) { - obj[kv[0].trim()] = kv[1].trim(); + const key = kv[0].trim(); + const value = kv[1].trim(); + obj[key] = COLOR_KEYS.indexOf(key) === -1 ? value : (0, exports.resolveColor)(value); } }); return obj; @@ -5013,11 +5466,16 @@ class SVGSelection { node() { return this.elements[0] || null; } + /** + * Sets an element's text content. + * + * `textContent` is defined on every Element, so no narrowing is needed — and + * testing `instanceof SVGTextElement` threw a ReferenceError outright in any + * DOM that does not expose that constructor as a global, jsdom included. + */ text(content) { this.elements.forEach(el => { - if (el instanceof SVGTextElement || el instanceof HTMLElement) { - el.textContent = content; - } + el.textContent = content; }); return this; } From 7f2d78f504de40f4325adb939b425e76a8271379 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 18:51:47 -0700 Subject: [PATCH 20/22] ci: run the PSTricks conformance comparison, and stop wasting runs Adds a third job that renders every example with genuine PSTricks and scores it against the LaTeX2JS output, publishing the comparison as an artifact. Only the deterministic half gates: a picture that stops compiling under PSTricks fails the job, while the similarity score is a heuristic between two rasterizers and is published for review rather than enforced. The runner is amd64, so the image runs natively there instead of emulated. Also: superseded runs on a branch are cancelled rather than queued behind each other, jobs carry timeouts, Playwright browsers are cached against their exact version so an upgrade misses the cache instead of restoring browsers the new Playwright refuses, and the e2e job publishes one browsable gallery page beside the loose PNGs. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- .github/workflows/run-tests.yaml | 112 +++++++++++++++++- .gitignore | 3 + .../pstricks-conformance/render-examples.mjs | 7 ++ 3 files changed, 118 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 2329375a..67d47d0f 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -6,9 +6,16 @@ on: pull_request: workflow_dispatch: +# A branch only needs its newest commit checked; superseded runs are cancelled +# so a series of pushes does not queue behind itself. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: test: runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout repository @@ -48,13 +55,12 @@ jobs: e2e: runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@v4 - # The pnpm version comes from `packageManager` in package.json. Naming it - # here as well makes the action refuse to install rather than pick one. - uses: pnpm/action-setup@v4 name: Install pnpm with: @@ -69,17 +75,115 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Resolve the Playwright version + # The browser cache is keyed on the exact version, so an upgrade misses + # the cache instead of restoring browsers the new Playwright refuses. + id: pw + run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT" + + - name: Restore Playwright browsers + id: pw-cache + uses: actions/cache@v4 + with: + path: playground/.browsers + key: playwright-${{ runner.os }}-${{ steps.pw.outputs.version }} + - name: Install Playwright browsers (workspace-local) + # System dependencies are not part of the cached directory, so they are + # installed on every run; the browser download is what the cache saves. run: | export PLAYWRIGHT_BROWSERS_PATH="$GITHUB_WORKSPACE/playground/.browsers" - pnpm --filter @latex2js/playground exec playwright install --with-deps chromium + if [ "${{ steps.pw-cache.outputs.cache-hit }}" = "true" ]; then + pnpm --filter @latex2js/playground exec playwright install-deps chromium + else + pnpm --filter @latex2js/playground exec playwright install --with-deps chromium + fi - name: Run browser tests (gallery + interactive) run: pnpm e2e + - name: Build a browsable gallery of the renderings + # One self-contained page is easier to review than a folder of PNGs. + run: node tools/pstricks-conformance/gallery.mjs + --renders playground/renders + --title "LaTeX2JS renders" + --out playground/renders/gallery.html + - name: Upload renderings as artifacts uses: actions/upload-artifact@v4 with: name: example-renderings - path: playground/renders/*.png + path: | + playground/renders/*.png + playground/renders/gallery.html + if-no-files-found: error + + conformance: + # Compares LaTeX2JS output against genuine PSTricks. Only the "does every + # picture still compile" half gates the build; the similarity score is a + # heuristic between two different rasterizers and is published for review + # rather than enforced. + runs-on: ubuntu-latest + timeout-minutes: 30 + if: github.event_name != 'pull_request' || !github.event.pull_request.draft + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + name: Install pnpm + with: + run_install: false + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Pull the PSTricks image + # Carries a full TeX Live with PSTricks; the runner is amd64, so it runs + # natively here rather than emulated as it does on Apple silicon. + run: docker pull --platform linux/amd64 pyramation/pstricks-latex:latest + + - name: Render every example with real PSTricks + # Fails the job if a picture stops compiling — that is a definite + # regression, unlike a shifted similarity score. + run: node tools/pstricks-conformance/render-examples.mjs + --corpus packages/latex2js/test/corpus + --out tools/pstricks-conformance/examples-ref + --jobs 4 + + - name: Restore Playwright browsers + uses: actions/cache@v4 + with: + path: playground/.browsers + key: playwright-conformance-${{ runner.os }} + + - name: Install Playwright browsers (workspace-local) + run: | + export PLAYWRIGHT_BROWSERS_PATH="$GITHUB_WORKSPACE/playground/.browsers" + pnpm --filter @latex2js/playground exec playwright install --with-deps chromium + + - name: Render every example with LaTeX2JS + run: pnpm --filter @latex2js/playground e2e:gallery + + - name: Score the two against each other + run: node tools/pstricks-conformance/compare.mjs + --js playground/renders + --ref tools/pstricks-conformance/examples-ref/ref + --manifest tools/pstricks-conformance/examples-ref/manifest.json + --out conformance.html + + - name: Upload the comparison + uses: actions/upload-artifact@v4 + with: + name: pstricks-conformance + path: | + conformance.html + tools/pstricks-conformance/examples-ref/ref/*.png if-no-files-found: error diff --git a/.gitignore b/.gitignore index 966e24e2..8f123ba4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ playground/.browsers playground/test-results playground/playwright-report playground/renders + +# Conformance report written at the repo root by the CI comparison step. +conformance.html diff --git a/tools/pstricks-conformance/render-examples.mjs b/tools/pstricks-conformance/render-examples.mjs index e32ef71a..761ec0d4 100644 --- a/tools/pstricks-conformance/render-examples.mjs +++ b/tools/pstricks-conformance/render-examples.mjs @@ -385,3 +385,10 @@ const ok = Object.values(results).filter((s) => s === 'OK').length const failed = Object.entries(results).filter(([, s]) => s !== 'OK') console.log(`render-examples: ${ok}/${ids.length} rendered in ${((Date.now() - t0) / 1000).toFixed(0)}s`) for (const [n, s] of failed) console.log(` ${s.padEnd(12)} ${n}`) + +// A picture that stops compiling under real PSTricks is a definite regression, +// unlike the comparison score, so this is worth failing a build over. +if (failed.length) { + console.error(`render-examples: ${failed.length} picture(s) no longer compile; logs in ref/logs/`) + process.exit(1) +} From b311bc21e1d5fa71c0494bc50a36218daba08511 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 22:10:55 -0700 Subject: [PATCH 21/22] ci: only run conformance when a rendering could have changed The conformance job pulls a multi-gigabyte TeX image and takes over two minutes, which is wasted on a change that cannot alter what gets drawn. A preceding job diffs against the base commit and decides. The package list is the dependency closure of the html5 renderer rather than a guess: html5 pulls in latex2js, pstricks, utils and macros; latex2js and pstricks both pull in settings; settings pulls in utils. settings and macros were missing from the first draft of the list, which would have skipped the job on a change that can move a rendering. The lockfile and root manifest are included too, since a dependency bump can shift output without a source edit. The filter fails open: an unknown or unreachable base commit, a failed diff, or a manual run all take the job. Skipping a correctness check on a bad guess is the expensive mistake; a wasted run is only slow. It also prints the file list and the reason it decided, so a surprising skip can be read straight from the log rather than reproduced. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- .github/workflows/run-tests.yaml | 58 +++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 67d47d0f..a26a6da0 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -118,14 +118,70 @@ jobs: playground/renders/gallery.html if-no-files-found: error + changes: + # Decides whether the conformance job has anything to check. That job pulls + # a multi-gigabyte TeX image and takes minutes, which is not worth spending + # on a change that cannot alter what gets drawn. + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + rendering: ${{ steps.filter.outputs.rendering }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + # Both sides of the comparison have to be present to diff them. + fetch-depth: 0 + + - name: Decide whether rendering could have changed + id: filter + env: + BASE: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} + run: | + set -u + + run_it() { + echo "rendering=true" >> "$GITHUB_OUTPUT" + echo "conformance will run: $1" + exit 0 + } + + # Fail open. A filter that cannot see what changed must run the job: + # skipping a correctness check on a bad guess is the costly mistake, + # and a wasted run is only slow. + [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && run_it "manually triggered" + [ -z "$BASE" ] && run_it "no base commit to compare against" + git cat-file -e "$BASE^{commit}" 2>/dev/null || run_it "base commit $BASE is not available" + + changed=$(git diff --name-only "$BASE" "$GITHUB_SHA") || run_it "could not diff against $BASE" + echo "changed files:" + echo "$changed" | sed 's/^/ /' + + # Everything a rendering is built from. The package list is the + # dependency closure of the html5 renderer — html5 pulls in latex2js, + # pstricks, utils and macros; latex2js and pstricks both pull in + # settings; settings pulls in utils — plus the stylesheet they draw + # through. Also the corpus they draw, the harness that captures it, + # the lockfile that pins what any of it runs against, and this + # workflow. Widen this if a package gains an edge into that closure. + if echo "$changed" | grep -qE '^(packages/(pstricks|latex2js|utils|html5|css|settings|macros)/|playground/|tools/pstricks-conformance/|\.github/workflows/|pnpm-lock\.yaml$|package\.json$)'; then + run_it "a rendering path changed" + fi + + echo "rendering=false" >> "$GITHUB_OUTPUT" + echo "conformance skipped: nothing that affects a rendering changed" + conformance: # Compares LaTeX2JS output against genuine PSTricks. Only the "does every # picture still compile" half gates the build; the similarity score is a # heuristic between two different rasterizers and is published for review # rather than enforced. + needs: changes runs-on: ubuntu-latest timeout-minutes: 30 - if: github.event_name != 'pull_request' || !github.event.pull_request.draft + if: >- + needs.changes.outputs.rendering == 'true' + && (github.event_name != 'pull_request' || !github.event.pull_request.draft) steps: - name: Checkout repository From b93d8ed1332cab8187d526c12562876d80d0aea4 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sat, 15 Aug 2026 22:14:05 -0700 Subject: [PATCH 22/22] docs(conformance): describe how the harness runs in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Records what the conformance job gates on and what it only publishes, the artifacts each run produces, and the dependency closure the paths filter is derived from — including the warning to widen it when a package gains an edge into that closure, since the failure mode is a job that silently stops covering something. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBYcp3Go9naDWgFThChaDf --- tools/pstricks-conformance/README.md | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tools/pstricks-conformance/README.md b/tools/pstricks-conformance/README.md index 21d651a7..3b209605 100644 --- a/tools/pstricks-conformance/README.md +++ b/tools/pstricks-conformance/README.md @@ -134,3 +134,33 @@ Plain colour names resolve through CSS, so `green` is CSS green (`#008000`), where LaTeX's `green` is pure `#00FF00`. Only `!` mix expressions are resolved against LaTeX's palette. Changing this would shift colours on every existing page, so it stays a decision rather than a fix. + +## In CI + +`.github/workflows/run-tests.yaml` runs this harness as a `conformance` job on +pull requests, alongside the unit tests and the browser suite. + +**Only half of it gates the build.** A picture that stops compiling under real +PSTricks fails the job — that is deterministic, and a regression by any +reading. The similarity score is a heuristic between two rasterizers that can +never agree pixel for pixel, so it is uploaded as an artifact for review and +never enforced. A threshold there would fail on antialiasing and teach everyone +to ignore the job. + +Two artifacts come out of each run: + +| Artifact | Contents | +|---|---| +| `pstricks-conformance` | the side-by-side comparison page, plus every PSTricks reference PNG | +| `example-renderings` | every LaTeX2JS rendering, plus a self-contained `gallery.html` | + +The job is skipped when nothing that could affect a drawing changed. A +preceding `changes` job diffs against the base commit and matches against the +html5 renderer's dependency closure — `pstricks`, `latex2js`, `utils`, +`settings`, `macros`, `css` — plus the corpus, the playground, this directory, +the lockfile and the workflow. **Widen that list if a package gains an edge +into the closure**, or the job will silently stop covering it. + +The filter fails open: an unknown base commit, a failed diff, or a manual run +all take the job. It also logs the changed files and the reason it decided, so +an unexpected skip can be read from the log rather than reproduced.