From 755f86d744299f260b6f2825cf218ff01e9bcf5b Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sat, 11 Jul 2026 16:16:11 +0700 Subject: [PATCH 01/12] #10: add docs --- docs/.gitignore | 13 + docs/apidoc/index.md | 12 + docs/astro.config.mjs | 47 + docs/docfx.json | 39 + docs/package-lock.json | 6820 ++++++++++++++++++ docs/package.json | 16 + docs/src/content.config.ts | 7 + docs/src/content/docs/concepts/aai.md | 74 + docs/src/content/docs/concepts/vocabulary.md | 122 + docs/src/content/docs/index.mdx | 55 + docs/src/content/docs/installation.mdx | 67 + docs/src/content/docs/introduction.md | 77 + docs/src/content/docs/quickstart.md | 116 + docs/src/content/docs/why-mokkit.md | 82 + docs/tsconfig.json | 5 + 15 files changed, 7552 insertions(+) create mode 100644 docs/.gitignore create mode 100644 docs/apidoc/index.md create mode 100644 docs/astro.config.mjs create mode 100644 docs/docfx.json create mode 100644 docs/package-lock.json create mode 100644 docs/package.json create mode 100644 docs/src/content.config.ts create mode 100644 docs/src/content/docs/concepts/aai.md create mode 100644 docs/src/content/docs/concepts/vocabulary.md create mode 100644 docs/src/content/docs/index.mdx create mode 100644 docs/src/content/docs/installation.mdx create mode 100644 docs/src/content/docs/introduction.md create mode 100644 docs/src/content/docs/quickstart.md create mode 100644 docs/src/content/docs/why-mokkit.md create mode 100644 docs/tsconfig.json diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..fd57218 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,13 @@ +# build output +dist/ +# generated types +.astro/ +# dependencies +node_modules/ +# DocFX-generated API reference (built in CI into public/api) +public/api/ +# environment +.env +.env.production +# DocFX intermediate metadata +_api/ diff --git a/docs/apidoc/index.md b/docs/apidoc/index.md new file mode 100644 index 0000000..e5b7562 --- /dev/null +++ b/docs/apidoc/index.md @@ -0,0 +1,12 @@ +# Mokkit API reference + +Auto-generated from the XML documentation comments in the Mokkit source. Use the sidebar to browse +namespaces and types, or start with the essentials: + +- **Captures** — [`Mokkit.Capture`](Mokkit.Capture-1.html), [`Mokkit.Trapture`](Mokkit.Trapture-1.html), [`Mokkit.MokkitCaptureAttribute`](Mokkit.MokkitCaptureAttribute.html) +- **Arrange** — [`Mokkit.Arrange.ITestArrange`](Mokkit.Arrange.ITestArrange.html), [`Mokkit.Arrange.EnsureArrangeExtensions`](Mokkit.Arrange.EnsureArrangeExtensions.html) +- **Inspect** — [`Mokkit.Inspect.ITestInspect`](Mokkit.Inspect.ITestInspect.html), [`Mokkit.Inspect.EnsureInspectExtensions`](Mokkit.Inspect.EnsureInspectExtensions.html) +- **Stage** — [`Mokkit.Suite.TestStageSetup`](Mokkit.Suite.TestStageSetup.html), [`Mokkit.Suite.TestStage`](Mokkit.Suite.TestStage.html) +- **Containers** — [`Mokkit.Containers.IDependencyContainerBuilder`](Mokkit.Containers.IDependencyContainerBuilder.html), [`Mokkit.Containers.IStageResolve`](Mokkit.Containers.IStageResolve.html) + +For guides and concepts, head back to the [Mokkit documentation](/). diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs new file mode 100644 index 0000000..3958ca0 --- /dev/null +++ b/docs/astro.config.mjs @@ -0,0 +1,47 @@ +// @ts-check +import { defineConfig } from 'astro/config'; +import starlight from '@astrojs/starlight'; + +// NOTE: set `site` to the project's real custom domain before deploying — it drives +// canonical URLs and the sitemap. Placeholder for now. +export default defineConfig({ + site: 'https://mokkit.dev', + integrations: [ + starlight({ + title: 'Mokkit', + description: "Write tests that read like a story in your domain's language — as plain, compilable C#.", + social: [ + { icon: 'github', label: 'GitHub', href: 'https://github.com/GrafGenerator/mokkit' }, + ], + editLink: { + baseUrl: 'https://github.com/GrafGenerator/mokkit/edit/main/docs/', + }, + sidebar: [ + { + label: 'Start here', + items: [ + { label: 'Introduction', slug: 'introduction' }, + { label: 'Why Mokkit?', slug: 'why-mokkit' }, + { label: 'Installation', slug: 'installation' }, + { label: 'Quickstart', slug: 'quickstart' }, + ], + }, + { + label: 'Core concepts', + items: [ + { label: 'Arrange / Act / Inspect', slug: 'concepts/aai' }, + { label: 'Building your test vocabulary', slug: 'concepts/vocabulary' }, + ], + }, + { + label: 'Reference', + items: [ + // DocFX-generated static site under /api (built separately in CI). + { label: 'API reference', link: '/api/' }, + ], + }, + // Guides are added as those pages land. + ], + }), + ], +}); diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 0000000..ed9f728 --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,39 @@ +{ + "metadata": [ + { + "src": [ + { + "src": "../src", + "files": [ + "Mokkit/Mokkit.csproj", + "Mokkit.Containers.Common/Mokkit.Containers.Common.csproj", + "Mokkit.Containers.Bag/Mokkit.Containers.Bag.csproj", + "Mokkit.Containers.Moq/Mokkit.Containers.Moq.csproj", + "Mokkit.Containers.NSubstitute/Mokkit.Containers.NSubstitute.csproj", + "Mokkit.Containers.FakeItEasy/Mokkit.Containers.FakeItEasy.csproj", + "Mokkit.Containers.Autofac/Mokkit.Containers.Autofac.csproj", + "Mokkit.Containers.CastleWindsor/Mokkit.Containers.CastleWindsor.csproj", + "Mokkit.Containers.Microsoft.Extensions.DependencyInjection/Mokkit.Containers.Microsoft.Extensions.DependencyInjection.csproj" + ] + } + ], + "dest": "_api", + "outputFormat": "apiPage" + } + ], + "build": { + "content": [ + { "files": ["**/*.{yml,md}"], "src": "_api", "dest": "." }, + { "files": ["index.md"], "src": "apidoc", "dest": "." } + ], + "output": "public/api", + "template": ["default", "modern"], + "globalMetadata": { + "_appName": "Mokkit", + "_appTitle": "Mokkit API reference", + "_appFooter": "Mokkit — back to docs", + "_disableContribution": true, + "pdf": false + } + } +} diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 0000000..0e05108 --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,6820 @@ +{ + "name": "mokkit-docs", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mokkit-docs", + "version": "0.0.1", + "dependencies": { + "@astrojs/starlight": "^0.41.3", + "astro": "^7.0.7", + "sharp": "^0.35.3" + } + }, + "node_modules/@astrojs/compiler-binding": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding/-/compiler-binding-0.3.1.tgz", + "integrity": "sha512-DaAUj29AIBU2XdJ8uwcab8lW5O2pk9pY8AXkcMw0sw77nVa3oeTYRcO+Dvbbpoexf6ThMc0FMWYCQ/wN1/T7oQ==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@astrojs/compiler-binding-darwin-arm64": "0.3.1", + "@astrojs/compiler-binding-darwin-x64": "0.3.1", + "@astrojs/compiler-binding-linux-arm64-gnu": "0.3.1", + "@astrojs/compiler-binding-linux-arm64-musl": "0.3.1", + "@astrojs/compiler-binding-linux-x64-gnu": "0.3.1", + "@astrojs/compiler-binding-linux-x64-musl": "0.3.1", + "@astrojs/compiler-binding-wasm32-wasi": "0.3.1", + "@astrojs/compiler-binding-win32-arm64-msvc": "0.3.1", + "@astrojs/compiler-binding-win32-x64-msvc": "0.3.1" + } + }, + "node_modules/@astrojs/compiler-binding-darwin-arm64": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-arm64/-/compiler-binding-darwin-arm64-0.3.1.tgz", + "integrity": "sha512-IEmEF2fUIlTHtpeE/isyEGVOB14cEyh/LZOFYt6wn3jNyVpdC8aR5OZ+RzFUR/f+8ZDM1LaMwZKvoA7eMyJeFw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-darwin-x64": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-darwin-x64/-/compiler-binding-darwin-x64-0.3.1.tgz", + "integrity": "sha512-GF2kIxjpPDLsn94zbZNMsxEmkU828QqnmM7kiQJnaooS3jmI+I7kk6+oI6EpwOsK3femCMdcm+wmOsEqtGrmjQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-arm64-gnu": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-gnu/-/compiler-binding-linux-arm64-gnu-0.3.1.tgz", + "integrity": "sha512-XJL3SDmOtVrqFhCirNcHwE91+IesJqlgNo23I4qW9QUYfwzm/TBZuH61fgqsb1ttgR1mMYz6ooPWs0JDhwMqpQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-arm64-musl": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-arm64-musl/-/compiler-binding-linux-arm64-musl-0.3.1.tgz", + "integrity": "sha512-xqE8BVbDoBueK/B47w30PtkVofUWJKGkwoMVE+EOMLf11rnoANxIAdA9FPqY+rng4oNI5ndHGsri1yPj2k8vZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-x64-gnu": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-gnu/-/compiler-binding-linux-x64-gnu-0.3.1.tgz", + "integrity": "sha512-1y0StU1qiCuDFH3rmbRJXcxdfHxFPrES1Rd+RLffosvUR7I2cH5SF5SFnBN9vXpzpkmyElZm3Yr47iJBPN7vVA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-linux-x64-musl": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-linux-x64-musl/-/compiler-binding-linux-x64-musl-0.3.1.tgz", + "integrity": "sha512-16q0fYf7kpbmdObZEeZJEup8hQv/whgNwVjrSvT8umrKwLDSnNIWiQpm09lQQu6bweZB0XyIvHwlPitvJhC+hg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-wasm32-wasi": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-wasm32-wasi/-/compiler-binding-wasm32-wasi-0.3.1.tgz", + "integrity": "sha512-cB456shIwDv/PrVT+2QG7LFndpHkVge5HjqADKZgGaAc9JHVktCtjSrcdkRQ+3tbkPazNKaTLRjXLIiz2NIx9g==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@astrojs/compiler-binding-win32-arm64-msvc": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-arm64-msvc/-/compiler-binding-win32-arm64-msvc-0.3.1.tgz", + "integrity": "sha512-ur/9+If/yTE69mmeX5MqSZndL0HOyx67GeNZUy3N7wVdWpLz9UTJXwyWS4UR2PUQHitghjsM5xoX0Ge56WRVQQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-binding-win32-x64-msvc": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-binding-win32-x64-msvc/-/compiler-binding-win32-x64-msvc-0.3.1.tgz", + "integrity": "sha512-k0W+kDBzDkNZOqu4kElDvCOIbKw5Ut9S1WZ1Krj3KTgNuBERNKXsMMsRLLcbgfdMdbe7bTekQLshZrrvmYpmwA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@astrojs/compiler-rs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@astrojs/compiler-rs/-/compiler-rs-0.3.1.tgz", + "integrity": "sha512-aT7xkgsbNoS6nriY5qKpbihK43slFHO41iqgHCTdOvn1ifaQxLCc5yXy+6GzAtiafoaC1zA7OwVXCXMsvUZOkg==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler-binding": "0.3.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.10.1.tgz", + "integrity": "sha512-5phcroT/vmOOrYuuAxtkbPixy5hePtlz9i8K4OeDv3dNK6/UQRuXPOSRTxIOBbUY5Sonw2UaxjbuVc43Mcir6Q==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "js-yaml": "^4.1.1", + "picomatch": "^4.0.4", + "retext-smartypants": "^6.2.0", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "unified": "^11.0.5" + } + }, + "node_modules/@astrojs/markdown-remark": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.2.1.tgz", + "integrity": "sha512-jPVNIqTvk+yKviikszv/Y1U4jGUSKpp/Nw48QZV4qjWgp70j4Lkq3lhSDRbWwCfgKvEyO9GHuVbV1dM2WYXy1w==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.10.1", + "@astrojs/prism": "4.0.2", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.1.0", + "unist-util-visit-parents": "^6.0.2", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/markdown-satteri": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-satteri/-/markdown-satteri-0.3.3.tgz", + "integrity": "sha512-Lje33Ittd8UQGgbIIWQvhPkj5X5c4b1sZnZWX3JQV/AWpfbuQGxVi2ONt6+ScydcwfR4egilslEWyczMclrJ1g==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.10.1", + "@astrojs/prism": "4.0.2", + "github-slugger": "^2.0.0", + "satteri": "^0.9.1" + } + }, + "node_modules/@astrojs/mdx": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-7.0.2.tgz", + "integrity": "sha512-l+sJY5U1KkGZUdr+bIL4Y6BefeS549qoSHVSkUSs6A9INwdCND+/0+vN0NroPBXwl5Vcg5u78t7VQRsJjePxbw==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.10.1", + "@astrojs/markdown-remark": "7.2.1", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.16.0", + "es-module-lexer": "^2.0.0", + "estree-util-visit": "^2.0.0", + "hast-util-to-html": "^9.0.5", + "piccolore": "^0.1.3", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.6", + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": ">=22.12.0" + }, + "peerDependencies": { + "@astrojs/markdown-satteri": "^0.3.1", + "astro": "^7.0.0" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-satteri": { + "optional": true + } + } + }, + "node_modules/@astrojs/prism": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.2.tgz", + "integrity": "sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.3.tgz", + "integrity": "sha512-f8euLVsyeAmAkSm/1M2Kb8sL8byQmfgbvBNaHFItCheTj/IpiJYSEWVcqDHZ/yEHxiS7+w87mQkzwZaPHmk5GA==", + "license": "MIT", + "dependencies": { + "sitemap": "^9.0.0", + "stream-replace-string": "^2.0.0", + "zod": "^4.3.6" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.41.3", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.41.3.tgz", + "integrity": "sha512-8xsG6UpK581TJmtOc7/pnxHKEbP16rV06VLWaVa7FOyE/VEwnaHOsLtL+miifCEfuiuv0Wn+j8u3JSluRQesMQ==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-satteri": "^0.3.2", + "@astrojs/mdx": "^7.0.0", + "@astrojs/sitemap": "^3.7.2", + "@pagefind/default-ui": "^1.3.0", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.44.0", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.3", + "hast-util-select": "^6.0.4", + "hast-util-to-string": "^3.0.1", + "hastscript": "^9.0.1", + "i18next": "^26.0.7", + "js-yaml": "^4.1.1", + "klona": "^2.0.6", + "magic-string": "^0.30.21", + "mdast-util-directive": "^3.1.0", + "mdast-util-to-markdown": "^2.1.2", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.5.2", + "rehype": "^13.0.2", + "rehype-format": "^5.0.1", + "remark-directive": "^4.0.0", + "satteri": "^0.9.1", + "ultrahtml": "^1.6.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.1.0", + "vfile": "^6.0.3" + }, + "peerDependencies": { + "@astrojs/markdown-remark": "^7.2.0", + "astro": "^7.0.2" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.3.tgz", + "integrity": "sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.4.0", + "dset": "^3.1.4", + "is-docker": "^4.0.0", + "package-manager-detector": "^1.6.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bruits/satteri-darwin-arm64": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-arm64/-/satteri-darwin-arm64-0.9.5.tgz", + "integrity": "sha512-iw4nZgx9v30lWo/MTngQqi1pI78KI0DnkSm+lVJGYdmPLgAyDNJigVhpG42/Iq55A6c1Ll8q66ljyyRiQUxwow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@bruits/satteri-darwin-x64": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-darwin-x64/-/satteri-darwin-x64-0.9.5.tgz", + "integrity": "sha512-6T26Z5Kf3cFW2PSlk9p7zT7yVxvuBSiJvYyz9u8KjYwMTqZyIDOj2wDyNpxKV4+6yUVG7rddq2QwvG/8LJA2+Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@bruits/satteri-linux-arm64-gnu": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-gnu/-/satteri-linux-arm64-gnu-0.9.5.tgz", + "integrity": "sha512-u51id17uJwNEMK9nBlICsq6U31c+XVqQueVBkwRIzZG+gMpS8TOJctt5h5Wz33Z8xnMdTd+adtACVz0yHgGuOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-arm64-musl": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-arm64-musl/-/satteri-linux-arm64-musl-0.9.5.tgz", + "integrity": "sha512-v39HxiwGC5Rqm01HksP6+5Y+xKLPlsuVFgIgpEAo+SiQ22c+mJVhS3u7Z6ePAKdhL5NJoK1xq70kLz3L13AhpQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-x64-gnu": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-gnu/-/satteri-linux-x64-gnu-0.9.5.tgz", + "integrity": "sha512-F3uO8uFp3pAP5ZGXttwvh57GS7s0lL953tnNdyI2gRyP4kOOkp6pyGojNJzCjkDvWI2Cvb9iNrKok3aqQPauAw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-linux-x64-musl": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-linux-x64-musl/-/satteri-linux-x64-musl-0.9.5.tgz", + "integrity": "sha512-bicEqglLlz++mWyADaZoP0JY20s4vDfLjaPYgQqC+NI4zZLTOOg1T4GB8aqtc822Pqji8SQBmSrTb7CrP8i08Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@bruits/satteri-wasm32-wasi": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-wasm32-wasi/-/satteri-wasm32-wasi-0.9.5.tgz", + "integrity": "sha512-zauAuMwfPnKPUkd4AFixRFpXdgKwP2mKgxrIIo2gJzW0/ZneF9dbHnLkojSpaBnCCp7VUL1hIi5WWZvB1CqmAQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@bruits/satteri-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@bruits/satteri-win32-arm64-msvc": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-arm64-msvc/-/satteri-win32-arm64-msvc-0.9.5.tgz", + "integrity": "sha512-SrfE7NEsgZjBvU3c+RR6oQRu0ToXY5uVJEbieXEF0YTctIV2zAVlbaMjWLts074QCgh3a+XHWkR/lWh2VH2LUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@bruits/satteri-win32-x64-msvc": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@bruits/satteri-win32-x64-msvc/-/satteri-win32-x64-msvc-0.9.5.tgz", + "integrity": "sha512-5Kw9ZAtTGS8WHizyn+CJhjjfIQrw+7jcZodpmpXJjefnO15M8UexIi6JR2E5thyvsmHyhL6ZDDMUNR4bKJPd4g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@capsizecss/unpack": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.1.tgz", + "integrity": "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@clack/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.3.tgz", + "integrity": "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==", + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.7.0.tgz", + "integrity": "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==", + "license": "MIT", + "dependencies": { + "@clack/core": "1.4.3", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.44.0.tgz", + "integrity": "sha512-xgiF2P6tYUbrhi3+x0S8xHZWT1t3Bvb3U91tAtRbLb9HLejLvYc5GZUqKICKLaUN4iSGhhNJu2fM/aH8e5yCMg==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.44.0.tgz", + "integrity": "sha512-V6M6+zVc1GzqCvXkQHc2m5rcFOIVzJgMq5gnfrMnVf2gwtj/sg4H93c1f/mGeqHycubwkHFUDyParAOiGeDZeA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.44.0" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.44.0.tgz", + "integrity": "sha512-RZsdaqlbGqyAQKuoX4myQXxjmiE2l5KBpJ/gKPh62tCdIdpWyjbzVqSo8+5XsezZxkfi8AJ/J6EUaBTPROFX/Q==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.44.0", + "shiki": "^4.0.2" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.44.0.tgz", + "integrity": "sha512-0/m3A5b+lz2upyNq+wzZ1S69HRoJmyFs5LsR42lVZ9pmGRlBiSBYQpvqlji4DBj1+Riamxc0AvcCr5kuzOQeWA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.44.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz", + "integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz", + "integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz", + "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz", + "integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz", + "integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz", + "integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz", + "integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz", + "integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz", + "integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz", + "integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz", + "integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz", + "integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz", + "integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz", + "integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz", + "integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz", + "integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz", + "integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.2" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz", + "integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz", + "integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz", + "integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.2" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz", + "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz", + "integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz", + "integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz", + "integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz", + "integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.5.2.tgz", + "integrity": "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.5.2.tgz", + "integrity": "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.5.2.tgz", + "integrity": "sha512-pm1LMnQg8N2B3n2TnjKlhaFihpz6zTiA4HiGQ6/slKO/+8K9CAU5kcjdSSPgpuk1PMuuN4hxLipUIifnrkl3Sg==", + "license": "MIT" + }, + "node_modules/@pagefind/freebsd-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.5.2.tgz", + "integrity": "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.5.2.tgz", + "integrity": "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.5.2.tgz", + "integrity": "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-arm64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-arm64/-/windows-arm64-1.5.2.tgz", + "integrity": "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.5.2.tgz", + "integrity": "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@shikijs/core": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.3.1.tgz", + "integrity": "sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.3.1", + "@shikijs/types": "4.3.1", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.3.1.tgz", + "integrity": "sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.3.1", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.3.1.tgz", + "integrity": "sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.3.1", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.3.1.tgz", + "integrity": "sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.3.1.tgz", + "integrity": "sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.3.1", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.3.1.tgz", + "integrity": "sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.3.1.tgz", + "integrity": "sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", + "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/am-i-vibing": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/am-i-vibing/-/am-i-vibing-0.4.0.tgz", + "integrity": "sha512-MxT4XZL7pzLHpuvhDKdMaQHMGGkJDLluKBLsbstn+8wv9sWcFT6h+0ve9qkml95amVTZtZV83gQe2hY+ojgHLg==", + "license": "MIT", + "dependencies": { + "process-ancestry": "^0.1.0" + }, + "bin": { + "am-i-vibing": "dist/cli.mjs" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/astro/-/astro-7.0.7.tgz", + "integrity": "sha512-swqrKDSI/B83GFroYPZYMFcxqbSe9+tkynu1WDBk3GLgBfV9++qVM4Z+2uFT6uu9A53Q0TROnxLketfMEENuqQ==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler-rs": "^0.3.0", + "@astrojs/internal-helpers": "0.10.1", + "@astrojs/markdown-satteri": "0.3.3", + "@astrojs/telemetry": "3.3.3", + "@capsizecss/unpack": "^4.0.0", + "@clack/prompts": "^1.1.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.3.0", + "am-i-vibing": "^0.4.0", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "ci-info": "^4.4.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^2.0.0", + "cookie": "^1.1.1", + "devalue": "^5.8.1", + "diff": "^8.0.3", + "dset": "^3.1.4", + "es-module-lexer": "^2.0.0", + "esbuild": "^0.28.0", + "flattie": "^1.1.1", + "fontace": "~0.4.1", + "get-tsconfig": "5.0.0-beta.4", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.2.0", + "js-yaml": "^4.1.1", + "jsonc-parser": "^3.3.1", + "magic-string": "^0.30.21", + "magicast": "^0.5.2", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "obug": "^2.1.1", + "p-limit": "^7.3.0", + "p-queue": "^9.1.0", + "package-manager-detector": "^1.6.0", + "piccolore": "^0.1.3", + "picomatch": "^4.0.4", + "semver": "^7.7.4", + "shiki": "^4.0.2", + "smol-toml": "^1.6.0", + "svgo": "^4.0.1", + "tinyclip": "^0.1.12", + "tinyexec": "^1.0.4", + "tinyglobby": "^0.2.15", + "ultrahtml": "^1.6.0", + "unifont": "~0.7.4", + "unstorage": "^1.17.5", + "vite": "^8.0.13", + "vitefu": "^1.1.2", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^22.0.0", + "zod": "^4.3.6" + }, + "bin": { + "astro": "bin/astro.mjs" + }, + "engines": { + "node": ">=22.12.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.34.0 || ^0.35.0" + }, + "peerDependencies": { + "@astrojs/markdown-remark": "7.2.1" + }, + "peerDependenciesMeta": { + "@astrojs/markdown-remark": { + "optional": true + } + } + }, + "node_modules/astro-expressive-code": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.44.0.tgz", + "integrity": "sha512-b1wN/ZvbJprzxlGKIpIes2kQrCY5KRLwys2tWbZAZyjGZcW5ZtgneZnBwzNRiBna9/48d4mQl19KLjcRuhO1hw==", + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.44.0", + "url-extras": "^0.1.0" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta || ^7.0.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.1.tgz", + "integrity": "sha512-KLw+H/gd2p4zly1X7Yh/qziuyae5/w/QFnvTng9eZL5fvszL7Whl3MBoWF8yxL7ksUjBfOD+OxkytiqbBpG+Fw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cookie-es": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", + "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", + "license": "MIT" + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-selector-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", + "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "license": "CC0-1.0" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", + "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", + "license": "MIT" + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/expressive-code": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.44.0.tgz", + "integrity": "sha512-JXVWVNCKlLuZLMQH8cOiDUSosT0Bb+elwE/dbAkpwFwDFmyFyWlECoWZIohh2FkIF1iI67TQJ+Ts9k7oNDh2qA==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.44.0", + "@expressive-code/plugin-frames": "^0.44.0", + "@expressive-code/plugin-shiki": "^0.44.0", + "@expressive-code/plugin-text-markers": "^0.44.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fontace": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", + "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", + "license": "MIT", + "dependencies": { + "fontkitten": "^1.0.2" + } + }, + "node_modules/fontkitten": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", + "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", + "license": "MIT", + "dependencies": { + "tiny-inflate": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.4", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.4.tgz", + "integrity": "sha512-7nF7C9fIPFEMHgEMEfgIlO9wDdZ8CyHw27rWciFZfHvHDReIiPhsYuzPRXsfvBCqFy1l8RRyyWV7QLM+ZhUJsQ==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/h3": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", + "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.3", + "crossws": "^0.3.5", + "defu": "^6.1.6", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.4", + "radix3": "^1.1.2", + "ufo": "^1.6.3", + "uncrypto": "^0.1.3" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "26.3.6", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.3.6.tgz", + "integrity": "sha512-Bu5Z2nAXgfVyM8xvW3jk9EKRIuX37PudsrBViThNFx7CR7aaYTpP01cxNB/E4c4UUzTDiAZRstEhsRfPOL/8xA==", + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], + "license": "MIT", + "peerDependencies": { + "typescript": "^5 || ^6 || ^7" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-4.0.0.tgz", + "integrity": "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "license": "MIT" + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-4.0.0.tgz", + "integrity": "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/ofetch": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", + "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.5", + "node-fetch-native": "^1.6.7", + "ufo": "^1.6.1" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", + "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.2.1" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.1.tgz", + "integrity": "sha512-POWdiIPmsUPGwb4FeQ4OBg46aqmcInSWe45CKDsGHiOBiVQM9chqfQTuqhuTzcg2Vz9faTI65at0KkVyVEiCHw==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.7.0.tgz", + "integrity": "sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.5.2.tgz", + "integrity": "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.5.2", + "@pagefind/darwin-x64": "1.5.2", + "@pagefind/freebsd-x64": "1.5.2", + "@pagefind/linux-arm64": "1.5.2", + "@pagefind/linux-x64": "1.5.2", + "@pagefind/windows-arm64": "1.5.2", + "@pagefind/windows-x64": "1.5.2" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/piccolore": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", + "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-ancestry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/process-ancestry/-/process-ancestry-0.1.0.tgz", + "integrity": "sha512-tGqJW/UnclpYASFcM6Xh8D8l/BMtaQ9+CSG0vlJSJTcdMM4lDRv4c6H0Pdcsfted+bVczdYSfk2fdukg2gQkZg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-expressive-code": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.44.0.tgz", + "integrity": "sha512-5r74C5F2sMR3X+QJH8OKWgZBO/cqRw5W1fLT6GVlSfLqepk+4j8tGFkyPqZYWjwntsBHzKPDH2zI68sZ7ScLfA==", + "license": "MIT", + "dependencies": { + "expressive-code": "^0.44.0" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-4.0.0.tgz", + "integrity": "sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/satteri": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/satteri/-/satteri-0.9.5.tgz", + "integrity": "sha512-ZuWVl+vnM64y+/TtX8Kosv2c00W+hLQiiwnEL6H0UKVVrxFqMw4D2CJHHQaouVd89OAhtBBfjWLqhKi3TVUV4w==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.5", + "@types/hast": "^3.0.4", + "@types/mdast": "^4.0.4", + "@types/unist": "^3.0.3" + }, + "optionalDependencies": { + "@bruits/satteri-darwin-arm64": "0.9.5", + "@bruits/satteri-darwin-x64": "0.9.5", + "@bruits/satteri-linux-arm64-gnu": "0.9.5", + "@bruits/satteri-linux-arm64-musl": "0.9.5", + "@bruits/satteri-linux-x64-gnu": "0.9.5", + "@bruits/satteri-linux-x64-musl": "0.9.5", + "@bruits/satteri-wasm32-wasi": "0.9.5", + "@bruits/satteri-win32-arm64-msvc": "0.9.5", + "@bruits/satteri-win32-x64-msvc": "0.9.5" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.35.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", + "integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==", + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.3", + "@img/sharp-darwin-x64": "0.35.3", + "@img/sharp-freebsd-wasm32": "0.35.3", + "@img/sharp-libvips-darwin-arm64": "1.3.2", + "@img/sharp-libvips-darwin-x64": "1.3.2", + "@img/sharp-libvips-linux-arm": "1.3.2", + "@img/sharp-libvips-linux-arm64": "1.3.2", + "@img/sharp-libvips-linux-ppc64": "1.3.2", + "@img/sharp-libvips-linux-riscv64": "1.3.2", + "@img/sharp-libvips-linux-s390x": "1.3.2", + "@img/sharp-libvips-linux-x64": "1.3.2", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", + "@img/sharp-libvips-linuxmusl-x64": "1.3.2", + "@img/sharp-linux-arm": "0.35.3", + "@img/sharp-linux-arm64": "0.35.3", + "@img/sharp-linux-ppc64": "0.35.3", + "@img/sharp-linux-riscv64": "0.35.3", + "@img/sharp-linux-s390x": "0.35.3", + "@img/sharp-linux-x64": "0.35.3", + "@img/sharp-linuxmusl-arm64": "0.35.3", + "@img/sharp-linuxmusl-x64": "0.35.3", + "@img/sharp-webcontainers-wasm32": "0.35.3", + "@img/sharp-win32-arm64": "0.35.3", + "@img/sharp-win32-ia32": "0.35.3", + "@img/sharp-win32-x64": "0.35.3" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/shiki": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.3.1.tgz", + "integrity": "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.3.1", + "@shikijs/engine-javascript": "4.3.1", + "@shikijs/engine-oniguruma": "4.3.1", + "@shikijs/langs": "4.3.1", + "@shikijs/themes": "4.3.1", + "@shikijs/types": "4.3.1", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz", + "integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==", + "license": "MIT", + "dependencies": { + "@types/node": "^24.9.2", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/esm/cli.js" + }, + "engines": { + "node": ">=20.19.5", + "npm": ">=10.8.2" + } + }, + "node_modules/smol-toml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz", + "integrity": "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/svgo": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", + "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "license": "MIT", + "dependencies": { + "commander": "^11.1.0", + "css-select": "^5.1.0", + "css-tree": "^3.0.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.1.1", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyclip": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.15.tgz", + "integrity": "sha512-uo33abH+Ays0xYaDysoBt494Hb3hsEczMpcC0MwFl773pazORx4fmvKhclhR1wonUbB6vvpRsvVMwnhfqeMc+A==", + "license": "MIT", + "engines": { + "node": "^16.14.0 || >= 17.3.0" + } + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.7.0.tgz", + "integrity": "sha512-2xRd0VHoAQE4M+vF/DvFFB7pUV0ZxTW1TLi7lHQWnF/Sb5TPeEUV/l+hxcNnGO00ZXGnR0voCMmYRKQf+rvJ2g==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz", + "integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.1.0", + "ofetch": "^1.5.1", + "ohash": "^2.0.11" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", + "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^5.0.0", + "destr": "^2.0.5", + "h3": "^1.15.10", + "lru-cache": "^11.2.7", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.5.1", + "ufo": "^1.6.3" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6 || ^7 || ^8", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1 || ^2 || ^3", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/url-extras": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/url-extras/-/url-extras-0.1.0.tgz", + "integrity": "sha512-8tzwTeXFPuX/5PHuCDQE5Dd9Ts4rwoq2t9aIT+HS4iAVpmj5l4Ao7Q+BuuFjvWRqrLswBhQDk8O96ZicgCqQqw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", + "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.16", + "rolldown": "~1.1.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.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 + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..1a275f9 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,16 @@ +{ + "name": "mokkit-docs", + "type": "module", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview" + }, + "dependencies": { + "@astrojs/starlight": "^0.41.3", + "astro": "^7.0.7", + "sharp": "^0.35.3" + } +} diff --git a/docs/src/content.config.ts b/docs/src/content.config.ts new file mode 100644 index 0000000..6a7b7a0 --- /dev/null +++ b/docs/src/content.config.ts @@ -0,0 +1,7 @@ +import { defineCollection } from 'astro:content'; +import { docsLoader } from '@astrojs/starlight/loaders'; +import { docsSchema } from '@astrojs/starlight/schema'; + +export const collections = { + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), +}; diff --git a/docs/src/content/docs/concepts/aai.md b/docs/src/content/docs/concepts/aai.md new file mode 100644 index 0000000..52164c6 --- /dev/null +++ b/docs/src/content/docs/concepts/aai.md @@ -0,0 +1,74 @@ +--- +title: Arrange / Act / Inspect +description: The three-phase shape every Mokkit test follows, and the discipline that keeps tests honest. +--- + +Every Mokkit test tells the same three-part story. It's the classic Arrange-Act-Assert, with the assert +phase renamed **Inspect** to make its one rule explicit: it only *observes*. + +## Arrange + +`stage.Arrange()` starts a fluent chain. Each `.Then(...)` registers a step; **`await`-ing the chain runs +the steps in order.** A step receives the test host, so it can resolve and configure services: + +```csharp +await stage.Arrange() + .Then(host => host.Execute>(clock => + clock.Setup(x => x.UtcNow).Returns(FixedNow))) + .Then(async host => await host.ExecuteAsync(db => db.Seed(...))); +``` + +Arrange is **deferred**: `.Then(...)` only *records* the step; nothing happens until you `await`. That's what +lets an arrange hand back a **capture** — a placeholder for a value that doesn't exist yet: + +```csharp +var init = Capture.Start(out Capture client); // client is empty for now +return arrange.Then(_ => init.Set(new Client(...))); // filled when the chain runs +``` + +After the `await`, `client.Value` holds the created client. Captures are how the artifacts an arrange +creates flow into later steps. See [Capture vs Trapture](/concepts/captures/). + +## Act + +Act is the one thing under test. It isn't a Mokkit type — it's a convention: **resolve a service from the +stage and run it**, capturing whatever it returns. + +```csharp +var result = await stage.ExecuteAsync( + handler => handler.Handle(command)); +``` + +`Execute`/`ExecuteAsync` come in 1-to-4-service arities, so an Act can pull several collaborators at once. +Teams usually wrap this in a small private `Act(...)` helper so the test body reads cleanly. + +## Inspect + +`stage.Inspect()` starts another fluent chain, awaited the same way. Inspect steps **read** the world — +query the database, verify a mock, poll an endpoint — and assert on it. They must not mutate state: + +```csharp +await stage.Inspect() + .SaveResult(result).IsSuccess() // a value scope over the result + .DbClientExists(id) // reads the database + .EventPublished("clients.created", id); // verifies a mock +``` + +Inspect also offers two power tools, covered in the guides: + +- **Value scopes** — `ThenValueScope(value)` opens a focused block of assertions over one value. +- **Parallel inspects** — `ThenAll(...)` runs independent observations concurrently while the chain stays + readable. + +## The discipline: produce vs observe + +One rule keeps tests trustworthy and reads across the whole suite: + +> **Arrange and Act *produce* artifacts. Inspect only *observes* them.** + +If setting something up needs a side effect, it belongs in Arrange (or is the Act itself). Inspect never +creates or changes state — so you can always trust that a failing Inspect is reporting on what Act did, not +on something Inspect itself caused. + +The verbs in these three chains — `SaveResult`, `DbClientExists`, `EventPublished` — are the heart of the +matter. They're your project's **[test vocabulary](/concepts/vocabulary/)**. diff --git a/docs/src/content/docs/concepts/vocabulary.md b/docs/src/content/docs/concepts/vocabulary.md new file mode 100644 index 0000000..fa7ff99 --- /dev/null +++ b/docs/src/content/docs/concepts/vocabulary.md @@ -0,0 +1,122 @@ +--- +title: Building your test vocabulary +description: The idea Mokkit is built around — reusable Arrange/Inspect verbs that become your domain's testing language. +--- + +This is the page that matters most. Everything else in Mokkit exists to support one practice: + +> **You author a vocabulary of verbs in your domain's language, and every test is a short composition of +> them.** + +The verbs are plain C# extension methods on `ITestArrange` and `ITestInspect`. Once you have a handful, a +test stops looking like plumbing and starts reading like the scenario it describes. + +## A test is a composition + +Here's a test written entirely in a client-management vocabulary: + +```csharp +await Arrange + .NewClient(out var clientId, WithName("Acme Corporation"), WithEmail("acme@e2e.test")); + +var result = await Act(clientId, WithName("Renamed Corporation")); + +await Inspect + .WriteResult(result).Updated() + .ApiClient(clientId, c => c.Name.ShouldBe("Renamed Corporation")) + .EventPublished("clients.updated", clientId); +``` + +`NewClient`, `WithName`, `WriteResult`, `Updated`, `ApiClient`, `EventPublished` aren't Mokkit APIs — they're +**your** methods. Mokkit provides `Arrange` / `Act` / `Inspect` and the machinery underneath; you provide the +words. + +## Two kinds of verb + +### Arrange verbs — set up, and capture + +An arrange verb registers a deferred step and usually hands back a **capture** for the artifact it creates, +so later steps can refer to it: + +```csharp +public static ITestArrange NewClient( + this ITestArrange arrange, out Trapture id, params ClientFieldFn[] fields) +{ + var capture = Trapture.Start(out id); + return arrange.Then(async host => + { + await host.ExecuteAsync(async http => + { + var result = await ClientApi.CreateAsync(http, Build(fields)); + result.Status.ShouldBe(HttpStatusCode.Created); // precondition guard + capture.Set(result.ClientId!.Value); + }); + }); +} +``` + +The `out` capture is the trick that lets verbs pass data to each other while everything stays deferred: +`NewClient` returns immediately with an *empty* `clientId`; when the chain is awaited, the step runs and +fills it. See [Capture vs Trapture](/concepts/captures/). + +Small parameter helpers (`WithName`, `WithEmail`, …) let a caller compose exactly the setup they need: + +```csharp +public static ClientFieldFn WithName(string name) => r => r with { Name = name }; +public static ClientFieldFn WithEmail(string email) => r => r with { Email = email }; +``` + +### Inspect verbs — observe + +An inspect verb resolves what it needs from the stage and asserts. It only reads: + +```csharp +public static ITestInspect ApiClient( + this ITestInspect inspect, Guid clientId, Action assert) => + inspect.Then(async host => await host.ExecuteAsync(async http => + { + var response = await http.GetAsync($"/api/v1/clients/{clientId}"); + response.StatusCode.ShouldBe(HttpStatusCode.OK); + assert((await response.Content.ReadFromJsonAsync())!); + })); + +public static ITestInspect EventPublished(this ITestInspect inspect, string topic, Guid clientId) => + inspect.Then(async host => await host.ExecuteAsync(async probe => + (await probe.SawMessageKeyed(topic, clientId.ToString())).ShouldBeTrue())); +``` + +## Why this beats a DSL + +Because your vocabulary is *code*, you get everything a Gherkin step binding gives up (see +[Why Mokkit?](/why-mokkit/)): + +- **Autocomplete.** Type `Inspect.` and your project's assertions are right there. +- **Go-to-definition & debugging.** Step into `EventPublished` — no binding layer in between. +- **Rename & find-usages.** Refactor a verb and every test that uses it updates; the ones that don't fit the + new signature stop compiling. +- **Typed parameters.** `Guid clientId`, not a string parsed out of a sentence. +- **Provably-correct tests.** `dotnet build` is a real check that the vocabulary is wired up — a + nonsensical test can't even compile, let alone reach a runner. + +The vocabulary is an asset that compounds. The first test costs a few verbs; the tenth reuses them and reads +in seconds. + +## Where verbs live + +Keep vocabulary next to what it describes — colocated `Arrange.cs` / `Inspect.cs` files per +feature or system-under-test. The [project structure](/reference/project-structure/) page shows the layout; +the [guides](/quickstart/) build real vocabulary for mocked services, databases, message queues and full +end-to-end flows. + +## Levelling up your verbs + +As scenarios get richer, a few Mokkit features become vocabulary-authoring techniques rather than test-body +noise: + +- **[Value & context scopes](/concepts/aai/)** — group assertions over one value inside a verb. +- **`Ensure`** — derive, guard-as-non-empty, and capture a value in one step, so ids flow cleanly between + verbs. +- **`[MokkitCapture]`** — let the source generator write the boilerplate body of a "build this object" + arrange verb, so your vocabulary file is just declarations. + +Each is covered in its own guide. diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx new file mode 100644 index 0000000..5a2685d --- /dev/null +++ b/docs/src/content/docs/index.mdx @@ -0,0 +1,55 @@ +--- +title: Mokkit +description: Write tests that read like a story in your domain's language — as plain, compilable C#. +template: splash +hero: + tagline: The readability of Cucumber/SpecFlow — without a DSL. Your tests read like a story in your domain's language, but they're ordinary, compilable C# with full IDE and compiler support. + actions: + - text: Get started + link: /introduction/ + icon: right-arrow + - text: Why Mokkit? + link: /why-mokkit/ + variant: minimal + - text: GitHub + link: https://github.com/GrafGenerator/mokkit + icon: external + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +```csharp +await Arrange + .NewClient(out var client, WithName("Acme Corporation")) + .CacheHasClient(client); + +var result = await Act(client); + +await Inspect + .WriteResult(result).Created() + .Ensure(result, r => r.ClientId, out var id) + .ThenAll( + b => b.ApiClientMatches(id, name: "Acme Corporation"), + b => b.DbClientExists(id), + b => b.EventPublished("clients.created", id)); +``` + + + + Tests are composed from **domain verbs** you name yourself — `NewClient`, `CacheHasClient`, + `EventPublished`. The AAA (Arrange / Act / Inspect) shape keeps every test telling the same story. + + + It's just C#. No Gherkin, no step bindings, no generated glue. Refactor a verb and every test that + uses it updates — and still compiles. + + + Framework-agnostic (xUnit / NUnit / MSTest), mock-agnostic (Moq / NSubstitute / FakeItEasy) and + container-agnostic (Microsoft DI / Autofac / Castle Windsor — or the dependency-free Bag). + + + The same Arrange / Act / Inspect vocabulary scales from a mocked unit test to a full + Testcontainers e2e run against real Postgres, Kafka and your API. + + diff --git a/docs/src/content/docs/installation.mdx b/docs/src/content/docs/installation.mdx new file mode 100644 index 0000000..4b7b660 --- /dev/null +++ b/docs/src/content/docs/installation.mdx @@ -0,0 +1,67 @@ +--- +title: Installation +description: The Mokkit packages, how to pick them, and how to install. +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +You install **three things**: the core, one **container adapter** for your DI container (or the trivial +Bag), and — if your tests use mocks — one **mock adapter** for your mocking library. + +:::note[Prerelease] +Mokkit is currently published as a prerelease (`0.3.0-preview.1`). Add `--prerelease` (CLI) or reference the +exact version so NuGet will resolve it. +::: + +## A minimal unit-test setup + +Core + Microsoft DI + NSubstitute is a common starting point: + + + +```bash +dotnet add package Mokkit --prerelease +dotnet add package Mokkit.Containers.Microsoft.Extensions.DependencyInjection --prerelease +dotnet add package Mokkit.Containers.NSubstitute --prerelease +``` + + +```xml + + + + + +``` + + + +The `[MokkitCapture]` source generator ships **inside the `Mokkit` package** (under `analyzers/dotnet/cs`), +so referencing `Mokkit` is all you need to use it — nothing extra to install. + +## Package matrix + +| Package | You need it when… | +| --- | --- | +| **`Mokkit`** | Always. The core: Stage, Arrange/Act/Inspect, captures, the `[MokkitCapture]` generator. | +| **`Mokkit.Containers.Microsoft.Extensions.DependencyInjection`** | Your system-under-test is wired with Microsoft DI. | +| **`Mokkit.Containers.Autofac`** | …wired with Autofac. | +| **`Mokkit.Containers.CastleWindsor`** | …wired with Castle Windsor. | +| **`Mokkit.Containers.Bag`** | You just need to hold a few pre-built instances (e.g. e2e clients) — no DI, no auto-wiring. | +| **`Mokkit.Containers.Moq`** | Your tests mock with Moq. | +| **`Mokkit.Containers.NSubstitute`** | …mock with NSubstitute. | +| **`Mokkit.Containers.FakeItEasy`** | …mock with FakeItEasy. | +| `Mokkit.Containers.Common` | Shared base for the mock adapters — pulled in automatically; you don't reference it directly. | + +## How to choose + +- **Which container adapter?** Match your production wiring. If the SUT is resolved from Microsoft DI, use + the Microsoft DI adapter so the *real* graph is exercised and mocks are bridged into it. If your test only + needs to hand back a couple of pre-built objects (typical for black-box e2e), the **Bag** container is the + zero-dependency choice. +- **Which mock adapter?** Match your mocking library — one per test project. (You can even mix a mock + container with a DI container; see [Containers & the mock→DI bridge](/concepts/containers/).) +- **Multiple SUTs?** One test project can use several containers together; you compose them when you build + the Stage. See [The Stage & lifecycle](/concepts/stage/). + +Ready to write one? Head to the **[Quickstart](/quickstart/)**. diff --git a/docs/src/content/docs/introduction.md b/docs/src/content/docs/introduction.md new file mode 100644 index 0000000..3fb9c0e --- /dev/null +++ b/docs/src/content/docs/introduction.md @@ -0,0 +1,77 @@ +--- +title: Introduction +description: What Mokkit is, and the one idea it's built around — tests that read like a story, as plain compilable C#. +--- + +Mokkit is a **test-orchestration toolkit for .NET**. It doesn't replace your test framework, your mocking +library, or your DI container — it sits on top of them and gives your tests a shape and a language. + +That language is the whole point. + +## The idea + +Good tests describe a scenario. Read one aloud and it should sound like a sentence someone on the team +would say: + +> Given a new client *Acme Corporation* that's already in the cache, when we create it, then the API returns +> **Created**, the row is in the database, and a `clients.created` event is published. + +BDD tools like **Cucumber** and **SpecFlow** chase that readability with a separate language — Gherkin +feature files, plus "step bindings" that glue each English phrase to some C# behind the scenes. You get +prose, but you pay for it: a second syntax, a runtime binding layer, and steps that can drift out of sync +with the code without the compiler noticing. + +Mokkit takes the other road. The same scenario, as a Mokkit test: + +```csharp +await Arrange + .NewClient(out var client, WithName("Acme Corporation")) + .CacheHasClient(client); + +var result = await Act(client); + +await Inspect + .WriteResult(result).Created() + .Ensure(result, r => r.ClientId, out var id) + .ApiClientMatches(id, name: "Acme Corporation") + .DbClientExists(id) + .EventPublished("clients.created", id); +``` + +It reads like the sentence above — but there is **no DSL**. `NewClient`, `CacheHasClient`, `Created`, +`ApiClientMatches`, `EventPublished` are just C# methods *you* wrote: your project's testing vocabulary. +Because it's plain code, you get everything the compiler and IDE give you — autocomplete, go-to-definition, +rename-refactoring, and the guarantee that a test that doesn't make sense **won't compile**. + +## The shape: Arrange / Act / Inspect + +Every Mokkit test follows the same three-phase story (Arrange-Act-Assert, with the assert phase named +*Inspect*): + +- **Arrange** — set up the world and capture the artifacts later steps refer to. +- **Act** — perform the one thing under test; it yields a result. +- **Inspect** — observe the outcome. Inspect only *reads*; it never changes state. + +Each phase is a fluent chain of the verbs you defined. See +[Arrange / Act / Inspect](/concepts/aai/) for the mechanics and +[Building your test vocabulary](/concepts/vocabulary/) for the part that matters most. + +## Agnostic by design + +Mokkit assumes nothing about the rest of your stack: + +- **Test framework** — xUnit, NUnit, MSTest; Mokkit is just calls inside your test methods. +- **Mocking** — first-class container packages for **Moq**, **NSubstitute** and **FakeItEasy** (or bring + your own). +- **DI container** — **Microsoft.Extensions.DependencyInjection**, **Autofac**, **Castle Windsor**, or the + dependency-free **Bag** container for tests that just need to hold a few instances. + +The same vocabulary and the same test shape carry from a fast, fully-mocked **unit** test to an +**integration** test against a real database, up to a black-box **end-to-end** test that boots your whole +system in Docker. The [guides](/quickstart/) walk each of those. + +## Next steps + +- **[Why Mokkit?](/why-mokkit/)** — the honest comparison with BDD/DSL frameworks. +- **[Installation](/installation/)** — the packages and how to pick them. +- **[Quickstart](/quickstart/)** — a complete test in a few minutes. diff --git a/docs/src/content/docs/quickstart.md b/docs/src/content/docs/quickstart.md new file mode 100644 index 0000000..5065c34 --- /dev/null +++ b/docs/src/content/docs/quickstart.md @@ -0,0 +1,116 @@ +--- +title: Quickstart +description: Write a complete Mokkit test — build a Stage, define a verb, and inspect an outcome — in a few minutes. +--- + +This walks through a full (if tiny) Mokkit test with xUnit and NSubstitute. See +[Installation](/installation/) for the packages. + +## 1. The system under test + +A service with one dependency we'll want to substitute: + +```csharp +public interface IEmailSender +{ + Task SendWelcome(string address); +} + +public sealed class SignupService(IEmailSender email) +{ + public async Task Register(string address) + { + await email.SendWelcome(address); + return Guid.NewGuid(); + } +} +``` + +## 2. Build a Stage + +The **Stage** is where your services live during a test. Here we use the dependency-free **Bag** container +to hold a substitute and the service under test — the same substitute instance goes into both, so we can +drive it *and* verify it: + +```csharp +using Mokkit.Containers.Bag; +using Mokkit.Suite; +using NSubstitute; + +public sealed class SignupTests +{ + private static async Task NewStage() + { + var email = Substitute.For(); + + var setup = await TestStageSetup.Create( + new BagContainerBuilder() + .AddInstance(email) + .AddInstance(new SignupService(email))); + + return setup.EnterStage(); + } +} +``` + +:::tip[The real-world setup] +Hand-wiring with Bag is perfect for a first test. In practice you'll usually resolve the *real* service from +your DI container and let a mock container bridge substitutes into it — see +[Wire a real DI container](/concepts/containers/). The test body below doesn't change. +::: + +## 3. Define a verb (your vocabulary) + +An **Inspect verb** is a C# extension method that observes an outcome. This one reads "a welcome email was +sent to…": + +```csharp +using Mokkit.Inspect; + +public static class SignupVocabulary +{ + public static ITestInspect WelcomeEmailSent(this ITestInspect inspect, string toAddress) => + inspect.Then(host => host.Execute(email => + email.Received(1).SendWelcome(toAddress))); +} +``` + +## 4. Write the test + +Now the test reads as Arrange → Act → Inspect. (This one needs no arrange.) + +```csharp +[Fact] +public async Task Registering_a_user_sends_a_welcome_email() +{ + var stage = await NewStage(); + + // ACT — resolve the service from the stage and run the one thing under test. + var id = await stage.ExecuteAsync( + service => service.Register("acme@example.com")); + + // INSPECT — observe the outcome through your vocabulary. + await stage.Inspect() + .WelcomeEmailSent("acme@example.com"); + + Assert.NotEqual(Guid.Empty, id); +} +``` + +## What just happened + +- `TestStageSetup.Create(...)` composed your containers; `EnterStage()` gave you a fresh **Stage** for the + test. +- `stage.ExecuteAsync(...)` **resolved** the service from the stage and ran it — the + **Act**. +- `stage.Inspect().WelcomeEmailSent(...)` ran your **Inspect** verb, which resolved the same `IEmailSender` + from the stage and verified the call. + +The verb `WelcomeEmailSent` is the seed of your project's **vocabulary**. As you add +`Arrange` verbs (to set up state) and more `Inspect` verbs (to observe it), tests become short, readable +compositions of sentences — with full IDE and compile-time support. + +## Next + +- **[Arrange / Act / Inspect](/concepts/aai/)** — the mechanics of each phase. +- **[Building your test vocabulary](/concepts/vocabulary/)** — the idea Mokkit is built around. diff --git a/docs/src/content/docs/why-mokkit.md b/docs/src/content/docs/why-mokkit.md new file mode 100644 index 0000000..83df937 --- /dev/null +++ b/docs/src/content/docs/why-mokkit.md @@ -0,0 +1,82 @@ +--- +title: Why Mokkit? +description: How Mokkit compares to BDD/DSL frameworks like Cucumber and SpecFlow — readable tests, without the DSL tax. +--- + +Mokkit exists for one reason: to get the **readability** of BDD without the **cost** of a DSL. + +## The BDD promise, and its price + +Tools like Cucumber and SpecFlow let you write scenarios in near-English Gherkin: + +```gherkin +Scenario: Create a client + Given a new client "Acme Corporation" + When I create it + Then the API returns Created + And a "clients.created" event is published +``` + +That's genuinely readable. But the prose isn't the program — it's a script that has to be *bound* to code: + +```csharp +[Given(@"a new client ""(.*)""")] +public void GivenANewClient(string name) { /* ... */ } +``` + +So you carry a second language and a runtime binding layer, and you inherit their problems: + +- **Two artifacts to keep in sync.** A `.feature` file and its step definitions live apart. Rename a step's + wording and the binding silently breaks — at *runtime*, not compile time. +- **No compiler, weak IDE.** Steps are matched by regex/string at run time. Go-to-definition, rename, and + "find all usages" are best-effort at most; a typo in a step is a runtime failure. +- **Indirection for its own sake.** Every phrase bounces through a binding before reaching the code you + actually care about. +- **Parameters through strings.** Values arrive as text and get parsed back into types. + +## Mokkit's answer: it's just code + +Mokkit keeps the readable, sentence-like scenario but drops the separate language. The "steps" are C# +extension methods you author — your **domain vocabulary** — and a test simply composes them: + +```csharp +await Arrange.NewClient(out var client, WithName("Acme Corporation")); +var result = await Act(client); +await Inspect.WriteResult(result).Created().EventPublished("clients.created", result); +``` + +Because a Mokkit test is ordinary code, the trade-offs invert: + +| | BDD / DSL (Cucumber, SpecFlow) | Mokkit | +| --- | --- | --- | +| Readability | ✅ Gherkin prose | ✅ Sentence-like C# | +| Separate language to learn | Yes (Gherkin + bindings) | **No** | +| Steps bound at | Runtime (regex/strings) | **Compile time** | +| IDE navigation / rename / find-usages | Limited | **Full** | +| A nonsensical/typo'd step | Fails at runtime | **Fails to compile** | +| Parameters | Strings, re-parsed | **Typed** | +| Debug into a step | Through the binding layer | **Straight into the method** | + +The headline: with Mokkit, **a test that doesn't make sense won't compile**, and the same `dotnet build` +that checks your product code checks your tests' vocabulary. + +## When BDD is still the better fit + +Mokkit is not anti-BDD; it's a different trade-off. Reach for a Gherkin tool when: + +- **Non-developers author or read the scenarios.** Business analysts editing `.feature` files is a workflow + Mokkit doesn't try to replace — Mokkit's audience is engineers who live in an IDE. +- **The living documentation *is* the deliverable**, and plain-English feature files are a contractual + artifact. + +If your scenarios are written and maintained by the same engineers who write the code, Mokkit gives you the +readability with none of the DSL tax. + +## What Mokkit is *not* + +- Not a test framework — it runs inside xUnit / NUnit / MSTest. +- Not a mocking library — it wraps Moq / NSubstitute / FakeItEasy. +- Not a DI container — it wraps yours (or ships a trivial one). + +It's the thin layer that gives those tools a shared shape and a readable language. Next: +**[Installation](/installation/)**. diff --git a/docs/tsconfig.json b/docs/tsconfig.json new file mode 100644 index 0000000..8bf91d3 --- /dev/null +++ b/docs/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +} From 5610fb3f092a68ad33ac81672474e672ad11f48b Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sat, 11 Jul 2026 16:16:17 +0700 Subject: [PATCH 02/12] #10: add readme --- README.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e392c2f..82d4b3b 100644 --- a/README.md +++ b/README.md @@ -1 +1,70 @@ -# mokkit \ No newline at end of file +# Mokkit + +[![NuGet](https://img.shields.io/nuget/vpre/Mokkit.svg)](https://www.nuget.org/packages/Mokkit) +[![CI](https://github.com/GrafGenerator/mokkit/actions/workflows/ci.yml/badge.svg)](https://github.com/GrafGenerator/mokkit/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + +**Write tests that read like a story in your domain's language — as ordinary, compilable C#.** + +Mokkit gives your tests the readability of BDD tools like Cucumber/SpecFlow, but with **no DSL**: no feature +files, no step bindings, no runtime glue. The "steps" are just C# extension methods you author — your +project's testing vocabulary — so you keep full IDE support (autocomplete, go-to-definition, refactoring) and +a test that doesn't make sense simply won't compile. + +It's a thin orchestration layer, not a framework: run it inside xUnit / NUnit / MSTest, mock with +Moq / NSubstitute / FakeItEasy, and wire with Microsoft DI / Autofac / Castle Windsor (or the dependency-free +Bag). The same Arrange / Act / Inspect vocabulary scales from a mocked unit test to a full Testcontainers +end-to-end run. + +```csharp +// A Mokkit test reads like the scenario it describes. +[Fact] +public async Task Suspending_a_client_reflects_everywhere() +{ + await Arrange + .NewClient(out var clientId, WithName("Acme Corporation")) + .CacheHasClient(clientId); + + await Act(clientId, ClientStatus.Suspended); + + await Inspect + .ApiClientEventually(clientId, c => c.Status == Suspended) + .DbClient(clientId, c => c!.Status.ShouldBe(Suspended)) + .EventPublished("clients.updated", clientId); +} +``` + +`NewClient`, `CacheHasClient`, `ApiClientEventually`, `DbClient`, `EventPublished` aren't Mokkit APIs — they're +your verbs. Mokkit provides the Arrange / Act / Inspect shape and the machinery underneath. + +## Install + +Core + a DI adapter + a mock adapter (prerelease for now): + +```bash +dotnet add package Mokkit --prerelease +dotnet add package Mokkit.Containers.Microsoft.Extensions.DependencyInjection --prerelease +dotnet add package Mokkit.Containers.NSubstitute --prerelease +``` + +## Packages + +| Package | Purpose | +| --- | --- | +| `Mokkit` | Core: Stage, Arrange/Act/Inspect, captures, the `[MokkitCapture]` source generator | +| `Mokkit.Containers.Microsoft.Extensions.DependencyInjection` · `.Autofac` · `.CastleWindsor` | DI container adapters | +| `Mokkit.Containers.Moq` · `.NSubstitute` · `.FakeItEasy` | Mock library adapters | +| `Mokkit.Containers.Bag` | Dependency-free "hold a few instances" container | + +## Documentation + +Full guides, concepts and API reference: **[mokkit.dev](https://mokkit.dev)** + +- [Introduction](https://mokkit.dev/introduction/) · [Why Mokkit? (vs BDD/DSL)](https://mokkit.dev/why-mokkit/) · [Quickstart](https://mokkit.dev/quickstart/) +- [Building your test vocabulary](https://mokkit.dev/concepts/vocabulary/) — the idea Mokkit is built around + +A worked, three-tier example (unit / integration / e2e) lives in [`example/Example1`](example/Example1). + +## License + +[MIT](LICENSE) © Nikita Ivanov From a66d832bb16cd2e6bf6b94864e1bce0399949ad6 Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sat, 11 Jul 2026 16:16:34 +0700 Subject: [PATCH 03/12] #10: add docs worfklow --- .github/workflows/docs.yml | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..6875c35 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,45 @@ +name: Docs + +on: + push: + branches: [ main ] + paths: + - 'docs/**' + - 'src/**' # rebuild the API reference when the library changes + - '.github/workflows/docs.yml' + workflow_dispatch: + +jobs: + build-deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # .NET 8 runs the DocFX tool natively; .NET 10 matches global.json for building the projects. + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.0.x + 10.0.x + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Generate API reference (DocFX → docs/public/api) + run: | + dotnet tool install -g docfx + docfx docs/docfx.json + + - name: Build the docs site (Starlight, includes /api) + working-directory: docs + run: | + npm ci + npm run build + + - name: Deploy to Cloudflare Pages + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy docs/dist --project-name=mokkit-docs From 8923e0a725f053a4fb8ddbc06f3cdfa8e8ee13b5 Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sat, 11 Jul 2026 16:45:02 +0700 Subject: [PATCH 04/12] #10: update domain --- README.md | 6 +++--- docs/astro.config.mjs | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 82d4b3b..d15d97f 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,10 @@ dotnet add package Mokkit.Containers.NSubstitute --prerelease ## Documentation -Full guides, concepts and API reference: **[mokkit.dev](https://mokkit.dev)** +Full guides, concepts and API reference: **[mokkit.net](https://mokkit.net)** -- [Introduction](https://mokkit.dev/introduction/) · [Why Mokkit? (vs BDD/DSL)](https://mokkit.dev/why-mokkit/) · [Quickstart](https://mokkit.dev/quickstart/) -- [Building your test vocabulary](https://mokkit.dev/concepts/vocabulary/) — the idea Mokkit is built around +- [Introduction](https://mokkit.net/introduction/) · [Why Mokkit? (vs BDD/DSL)](https://mokkit.net/why-mokkit/) · [Quickstart](https://mokkit.net/quickstart/) +- [Building your test vocabulary](https://mokkit.net/concepts/vocabulary/) — the idea Mokkit is built around A worked, three-tier example (unit / integration / e2e) lives in [`example/Example1`](example/Example1). diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 3958ca0..930ec69 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -2,10 +2,9 @@ import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; -// NOTE: set `site` to the project's real custom domain before deploying — it drives -// canonical URLs and the sitemap. Placeholder for now. +// The project's custom domain — drives canonical URLs and the sitemap. export default defineConfig({ - site: 'https://mokkit.dev', + site: 'https://mokkit.net', integrations: [ starlight({ title: 'Mokkit', From d49a3967f5891dbf002a48fe271fe3dc50b4cad8 Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sun, 12 Jul 2026 01:04:57 +0700 Subject: [PATCH 05/12] #10: update docs for Act changes --- docs/astro.config.mjs | 1 + docs/src/content/docs/concepts/aai.md | 23 +++-- docs/src/content/docs/concepts/scenarios.md | 95 +++++++++++++++++++++ 3 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 docs/src/content/docs/concepts/scenarios.md diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 930ec69..f1a57ce 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -30,6 +30,7 @@ export default defineConfig({ items: [ { label: 'Arrange / Act / Inspect', slug: 'concepts/aai' }, { label: 'Building your test vocabulary', slug: 'concepts/vocabulary' }, + { label: 'Scenario tests', slug: 'concepts/scenarios' }, ], }, { diff --git a/docs/src/content/docs/concepts/aai.md b/docs/src/content/docs/concepts/aai.md index 52164c6..cb81ac4 100644 --- a/docs/src/content/docs/concepts/aai.md +++ b/docs/src/content/docs/concepts/aai.md @@ -31,16 +31,29 @@ creates flow into later steps. See [Capture vs Trapture](/concepts/captures/). ## Act -Act is the one thing under test. It isn't a Mokkit type — it's a convention: **resolve a service from the -stage and run it**, capturing whatever it returns. +Act is the one thing under test — and, like Arrange and Inspect, it's a **first-class phase**. `stage.Act()` +starts a fluent chain whose `.Then(...)` steps resolve services from the stage and run them. Because Act is +symmetric with Arrange, an Act operation is reusable **vocabulary** too, and — like Arrange — it may *produce* +an artifact the later Inspect observes. + +An Act comes in three flavors, depending on what (if anything) the operation hands back: ```csharp -var result = await stage.ExecuteAsync( - handler => handler.Handle(command)); +// Void — the operation's effects are observed downstream (e.g. a message is emitted onto a bus). +await stage.Act().Then(host => host.ExecuteAsync(p => p.ProduceAsync(topic, message))); + +// Return — the operation returns its artifact directly (the familiar `var result = await ...`). +var result = await stage.Act().Returning(host => + host.ExecuteAsync(handler => handler.Handle(command))); + +// Capture — the operation threads its result forward through an out capture, exactly like Arrange. +await stage.Act().SaveClient(out var result, command); ``` `Execute`/`ExecuteAsync` come in 1-to-4-service arities, so an Act can pull several collaborators at once. -Teams usually wrap this in a small private `Act(...)` helper so the test body reads cleanly. +As with Arrange and Inspect, teams give their Acts domain names — `Act.CreateClient(...)`, +`Act.ProduceStatusChanged(...)` — so the test body reads as the story it tells. When a test is a whole +*sequence* of Act steps interleaved with checks, see **[Scenario tests](/concepts/scenarios/)**. ## Inspect diff --git a/docs/src/content/docs/concepts/scenarios.md b/docs/src/content/docs/concepts/scenarios.md new file mode 100644 index 0000000..0ecef45 --- /dev/null +++ b/docs/src/content/docs/concepts/scenarios.md @@ -0,0 +1,95 @@ +--- +title: Scenario tests +description: Tell a whole story as a sequence of Arrange / Act / Inspect blocks — one test, many steps, each verified in turn. +--- + +A single Arrange / Act / Inspect triple describes one moment: set up, do the thing, check the result. But +some behaviour is a **story** — a client is created, then renamed, then suspended — and you want to confirm +the system is correct *after each step*, not just at the end. Because [Act is a first-class +phase](/concepts/aai/#act), you don't need a new construct for that: a scenario is just a **sequence of AAI +blocks**, written as plain awaited statements. + +```csharp +// Born — create the client, and confirm it starts out Active. +await Arrange + .NewClient(out var clientId, WithName("Acme Corporation"), WithStatus(ClientStatus.Active)); + +await Inspect + .ApiClient(clientId, c => c.Status.ShouldBe((int)ClientStatus.Active)); + +// Renamed — update it through the API. This act returns its artifact, which the next Inspect asserts. +var renamed = await Act + .UpdateClient(clientId, WithName("Acme Holdings")); + +await Inspect + .WriteResult(renamed).Updated() + .ApiClient(clientId, c => c.Name.ShouldBe("Acme Holdings")); + +// Suspended — an upstream message carries the change. This act is void; its effects surface downstream. +await Arrange + .ArrangeStatusChanged(out var suspend, clientId, "Acme Holdings", Email, Phone, (int)ClientStatus.Suspended); + +await Act + .ProduceStatusChanged(clientId, suspend); + +await Inspect + .ApiClientEventually(clientId, c => c.Status == (int)ClientStatus.Suspended) + .DbClient(clientId, c => c!.Status.ShouldBe(ClientStatus.Suspended)) + .EventPublished("clients.updated", clientId); +``` + +Read top to bottom, that test *is* its own specification: build → check → act → check → act → check. No +Gherkin, no step-binding file — just compilable C# that happens to read like the scenario it verifies. + +## How it holds together + +**The captured id threads through the whole story.** `NewClient` hands back `clientId` as a +[`Trapture`](/concepts/captures/), which converts transparently wherever a `Guid` is expected — so every +later Act and Inspect refers to the same client without any plumbing. + +**`Arrange`, `Act` and `Inspect` are properties on the fixture**, each starting a fresh chain: + +```csharp +protected ITestArrange Arrange => Stage.Arrange(); +protected ITestAct Act => Stage.Act(); +protected ITestInspect Inspect => Stage.Inspect(); +``` + +**Every step is real vocabulary**, so the story stays in your domain's language. Act verbs live right next to +the Arrange and Inspect verbs for the same feature: + +```csharp +public static class ActClientApi +{ + // Return flavor — the write returns its artifact, asserted by the very next Inspect. + public static ITestAct UpdateClient( + this ITestAct act, Guid clientId, params ClientFieldFn[] fields) => + act.Returning(host => host.ExecuteAsync( + http => ClientApi.UpdateAsync(http, clientId, Build(fields)))); + + // Void flavor — fire the message; its effects are observed downstream in Inspect. + public static ITestAct ProduceStatusChanged(this ITestAct act, Guid clientId, StatusChangedMessage message) => + act.Then(host => host.ExecuteAsync>(async producer => + { + await producer.ProduceAsync("clients.status-changed", Serialize(clientId, message)); + producer.Flush(TimeSpan.FromSeconds(5)); + })); +} +``` + +See [Building your test vocabulary](/concepts/vocabulary/) for how Arrange, Act and Inspect verbs are authored. + +## When to reach for a scenario + +Scenarios shine for **end-to-end and integration** tests, where a feature is a lifecycle and each transition +has observable consequences across several systems (an API, a database, a message bus). A scenario lets one +test walk that lifecycle and pin down every transition, while still reading like prose. + +For a **unit** test — one method, one mocked collaborator — a single AAI triple is usually clearer; reach for +a scenario only when the behaviour genuinely spans several steps. + +:::tip[Keep each block honest] +The [produce-vs-observe rule](/concepts/aai/#the-discipline-produce-vs-observe) still holds at every step: +Arrange and Act *produce*, Inspect only *observes*. That's what lets you trust each intermediate check — a +failing Inspect is always reporting on what the preceding Act did, never on something the Inspect caused. +::: From 46537e123df7dc7b5cf54cd46ed01a69aa2c99c6 Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sun, 12 Jul 2026 02:13:42 +0700 Subject: [PATCH 06/12] #10: remove duplicating "arrange" in example --- .../src/Mokkit.Example1.E2E.Tests/Clients/ArrangeMessages.cs | 2 +- .../Clients/ClientLifecycleScenarioTests.cs | 2 +- .../Clients/StatusChangeFlowTests.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/ArrangeMessages.cs b/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/ArrangeMessages.cs index 41d86e1..c5bf18a 100644 --- a/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/ArrangeMessages.cs +++ b/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/ArrangeMessages.cs @@ -12,7 +12,7 @@ namespace Mokkit.Example1.E2E.Tests.Clients; public static partial class ArrangeMessages { [MokkitCapture] - public static partial ITestArrange ArrangeStatusChanged( + public static partial ITestArrange StatusChanged( this ITestArrange arrange, out Trapture message, Guid clientId, diff --git a/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/ClientLifecycleScenarioTests.cs b/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/ClientLifecycleScenarioTests.cs index 10f0212..6428e00 100644 --- a/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/ClientLifecycleScenarioTests.cs +++ b/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/ClientLifecycleScenarioTests.cs @@ -51,7 +51,7 @@ await Inspect // ── Suspended ── an upstream system emits a status-changed message carrying the (renamed) record. This // act is void — its effects surface eventually via the API, the DB row, and a confirmation event. await Arrange - .ArrangeStatusChanged(out var suspend, clientId, RenamedName, Email, Phone, (int)ClientStatus.Suspended); + .StatusChanged(out var suspend, clientId, RenamedName, Email, Phone, (int)ClientStatus.Suspended); await Act .ProduceStatusChanged(clientId, suspend); diff --git a/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/StatusChangeFlowTests.cs b/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/StatusChangeFlowTests.cs index ec7f3a4..0f01f76 100644 --- a/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/StatusChangeFlowTests.cs +++ b/example/Example1/src/Mokkit.Example1.E2E.Tests/Clients/StatusChangeFlowTests.cs @@ -18,12 +18,12 @@ public StatusChangeFlowTests(E2EStack stack) : base(stack) public async Task StatusChangedMessage_IsConsumed_AndReflectedEverywhere() { // ARRANGE — a real client created through the public API, then the status-changed message that will be - // fed to it. ArrangeStatusChanged's body is source-generated from [MokkitCapture] (see ArrangeMessages). + // fed to it. StatusChanged's body is source-generated from [MokkitCapture] (see ArrangeMessages). await Arrange .NewClient(out var clientId, WithName(Name), WithEmail(Email), WithPhone(Phone), WithStatus(ClientStatus.Active)); await Arrange - .ArrangeStatusChanged(out var message, clientId, Name, Email, Phone, (int)ClientStatus.Suspended); + .StatusChanged(out var message, clientId, Name, Email, Phone, (int)ClientStatus.Suspended); // ACT — an upstream system emits the status-changed message onto Kafka (carrying the full record, // which the consumer validates before applying) From 8aad0ed4edb2f81c6638b088ea855502754e4a8c Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sun, 12 Jul 2026 02:14:00 +0700 Subject: [PATCH 07/12] #10: implement core concepts doc pages --- docs/astro.config.mjs | 3 + docs/src/content/docs/concepts/captures.md | 93 +++++++++++++++++ docs/src/content/docs/concepts/containers.md | 103 +++++++++++++++++++ docs/src/content/docs/concepts/scenarios.md | 2 +- docs/src/content/docs/concepts/stage.md | 97 +++++++++++++++++ docs/src/content/docs/concepts/vocabulary.md | 32 +++++- docs/src/content/docs/quickstart.md | 17 ++- docs/src/content/docs/why-mokkit.md | 24 ++--- 8 files changed, 348 insertions(+), 23 deletions(-) create mode 100644 docs/src/content/docs/concepts/captures.md create mode 100644 docs/src/content/docs/concepts/containers.md create mode 100644 docs/src/content/docs/concepts/stage.md diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index f1a57ce..28ee2b8 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -31,6 +31,9 @@ export default defineConfig({ { label: 'Arrange / Act / Inspect', slug: 'concepts/aai' }, { label: 'Building your test vocabulary', slug: 'concepts/vocabulary' }, { label: 'Scenario tests', slug: 'concepts/scenarios' }, + { label: 'The Stage & lifecycle', slug: 'concepts/stage' }, + { label: 'Captures: Capture vs Trapture', slug: 'concepts/captures' }, + { label: 'Containers & the mock→DI bridge', slug: 'concepts/containers' }, ], }, { diff --git a/docs/src/content/docs/concepts/captures.md b/docs/src/content/docs/concepts/captures.md new file mode 100644 index 0000000..091d570 --- /dev/null +++ b/docs/src/content/docs/concepts/captures.md @@ -0,0 +1,93 @@ +--- +title: "Captures: Capture vs Trapture" +description: How an artifact created in one phase threads into the next while the whole chain stays deferred. +--- + +Arrange and Act are **deferred**: `.Then(...)` only *records* a step; nothing runs until you `await`. That +raises a question — if the client isn't created until the chain runs, how does a later step refer to it? The +answer is a **capture**: a typed placeholder handed back immediately and filled when the step runs. + +```csharp +var init = Capture.Start(out Capture client); // client is empty for now +return arrange.Then(_ => init.Set(new Client(...))); // filled when the chain runs +// after `await`: client.Value holds the created Client +``` + +`Start` hands you two things: the **capture** (via `out`, for the caller to read later) and an +**initializer** (returned, for the step to `Set`). This split is what makes deferral work — the caller gets a +reference to a value that doesn't exist yet. + +## Two flavors + +Mokkit ships two capture types with the same job but different ergonomics at the **use site**: + +| | Read as | Use when | +| --- | --- | --- | +| **`Capture`** | `capture.Value` (explicit) | You want the read to be visible — a deliberate `.Value`. | +| **`Trapture`** | converts to `T` implicitly | The value flows transparently into later steps, and ceremony would just be noise. | + +```csharp +Capture a = ...; +DoSomething(a.Value); // Capture — explicit + +Trapture b = ...; +DoSomething(b); // Trapture — implicit conversion (TRansparent cAPTURE) +``` + +`Trapture` is the workhorse inside vocabulary, because ids and entities usually just need to flow from an +Arrange into the next Act or Inspect without anyone thinking about it: + +```csharp +// NewClient hands back the id as a Trapture ... +await Arrange.NewClient(out var clientId, WithName("Acme Corporation")); + +// ... which the later Act and Inspect consume as a plain Guid, no .Value in sight. +await Act.UpdateClient(clientId, WithName("Acme Holdings")); +await Inspect.ApiClient(clientId, c => c.Name.ShouldBe("Acme Holdings")); +``` + +Reach for `Capture` when you *want* the read to stand out — for instance a result whose `.Value` you unpack +and assert on deliberately. + +## Producing a capture from a verb + +An Arrange (or Act) verb that creates an artifact starts a capture, then sets it inside the deferred step: + +```csharp +public static ITestArrange NewClient( + this ITestArrange arrange, out Trapture id, params ClientFieldFn[] fields) +{ + var capture = Trapture.Start(out id); // hand the caller an empty capture + return arrange.Then(async host => + { + await host.ExecuteAsync(async http => + { + var result = await ClientApi.CreateAsync(http, Build(fields)); + capture.Set(result.ClientId!.Value); // fill it when the chain runs + }); + }); +} +``` + +The interfaces behind this are small: `ICapture` exposes `Value`; `ICaptureInitializer` exposes +`Set(T)`. A verb takes the *initializer* to write and hands back the *capture* to read. + +## Deriving one value from another: `Ensure` + +Often you don't want to capture the whole artifact, just something off it — an id — and you want it guarded as +non-empty before it threads onward. `Ensure` does derive-guard-capture in one step: + +```csharp +await Inspect + .WriteResult(result).Created() + .Ensure(result, r => r.ClientId, out var clientId) // non-empty Guid, captured + .ApiClient(clientId, c => c.ShouldNotBeNull()); +``` + +`Ensure` has its own [guide](/guides/ensure/); it's the idiomatic way to turn "the result's id" into a clean +capture the rest of the chain can use. + +## Next + +- **[Building your test vocabulary](/concepts/vocabulary/)** — where captures are produced and consumed. +- **[Scenario tests](/concepts/scenarios/)** — a captured id threading through a whole multi-step story. diff --git a/docs/src/content/docs/concepts/containers.md b/docs/src/content/docs/concepts/containers.md new file mode 100644 index 0000000..cb84281 --- /dev/null +++ b/docs/src/content/docs/concepts/containers.md @@ -0,0 +1,103 @@ +--- +title: Containers & the mock→DI bridge +description: How Mokkit composes real services with test doubles so the code under test and your test share the same mock. +--- + +A [Stage](/concepts/stage/) is composed from **containers**. Mokkit doesn't ship its own DI or mocking +framework — it *adapts* the ones you already use, and its one real trick is making them cooperate: the mock a +test arranges is the very same instance the real service under test calls. + +## Two roles a container plays + +- **Mock containers** hold your test doubles — Moq mocks, NSubstitute substitutes, FakeItEasy fakes. +- **DI containers** compose the *real* application — Microsoft DI, Autofac, Castle Windsor. +- The dependency-free **Bag** is a third option: it just holds a few instances you hand it, no framework at + all (great for a first test or a small SUT). + +You pass the builders for these to `TestStageSetup.Create(...)`, and they're composed together into one Stage. + +## The mock→DI bridge + +The problem: your real `SaveClientHandler` asks its DI container for an `IClientCacheService`. Your test wants +that to be *its* mock — the one it sets up and later verifies. The bridge connects the two. + +On the DI side you register the dependency with **`ResolveFromStage()`** instead of a real implementation: + +```csharp +services.ResolveFromStage(); +``` + +That registers a factory that, when the DI container is asked for `IClientCacheService`, reaches into the +Stage and returns the mock the mock-container deposited there (via `IStageResolve`, backed by the Stage's +shared bag). So the real handler and the test resolve the **same** object. + +Usually you don't list dependencies one by one — you bridge *every* registered mock in a loop: + +```csharp +// From the unit suite's BaseStageFixture: a substitute container + a Microsoft DI container. +var substitutes = new SubstituteContainerBuilder() + .UseInit(s => { ConfigureSubstitutes(s); return Task.CompletedTask; }); + +var services = new ServiceProviderContainerBuilder() + .UseInit(s => + { + s.AddScoped(); + ConfigureServices(s); // register the real SUT + return Task.CompletedTask; + }) + .UsePreBuild(InjectSubstitutes); // ← bridge, see below + +var setup = await TestStageSetup.Create(substitutes, services); + +// Bridge every substitute into DI so the real SUT gets the test's doubles. +static Task InjectSubstitutes(IServiceCollection services, ISubstituteCollection substitutes) +{ + foreach (var registration in substitutes.Registrations) + services.ResolveFromStage(registration.InnerType); + return Task.CompletedTask; +} +``` + +The `UsePreBuild(...)` hook is the one moment the four-phase build matters: **PreBuild** +is where the DI builder gets to *see* the mock container's registrations, so it can wire a `ResolveFromStage` +for each. After that, the composition is built and every stage entered from it shares the arrangement. + +The result reads exactly the way you'd want: + +```csharp +// ARRANGE the mock ... +await Arrange.Then(host => host.Execute>(m => + m.Value.GetClient(id).Returns(cached))); + +// ... ACT runs the REAL handler, which resolves that same mock from DI ... +var result = await Act.GetClient(query); + +// ... INSPECT verifies against it. +await Inspect.CacheNotUpdated(); +``` + +## The adapters + +Everything above works with whichever pair you prefer. Each adapter is a small package: + +| Role | Packages | +| --- | --- | +| Mock library | `Mokkit.Containers.Moq` · `.NSubstitute` · `.FakeItEasy` | +| DI container | `Mokkit.Containers.Microsoft.Extensions.DependencyInjection` · `.Autofac` · `.CastleWindsor` | +| Dependency-free | `Mokkit.Containers.Bag` | +| Shared contracts | `Mokkit.Containers.Common` (referenced transitively) | + +The suites in the [example](https://github.com/GrafGenerator/mokkit/tree/main/example/Example1) deliberately +use *different* stacks — NSubstitute + MS-DI for units, Moq + MS-DI for integration, Bag for E2E — to prove +the test body never depends on the choice. + +:::tip[Rolling your own] +The adapter contract (`IDependencyContainerBuilder` → `IDependencyContainer`) is small. If your stack isn't +covered, you can write an adapter — see [Write a custom container adapter](/guides/custom-container-adapter/), +which walks through `SubstituteContainerBuilder`. +::: + +## Next + +- **[The Stage & lifecycle](/concepts/stage/)** — where these containers are composed and entered. +- **[Guides](/quickstart/)** — pick a mock library, wire a real DI container, bridge mocks into it. diff --git a/docs/src/content/docs/concepts/scenarios.md b/docs/src/content/docs/concepts/scenarios.md index 0ecef45..c075f9b 100644 --- a/docs/src/content/docs/concepts/scenarios.md +++ b/docs/src/content/docs/concepts/scenarios.md @@ -27,7 +27,7 @@ await Inspect // Suspended — an upstream message carries the change. This act is void; its effects surface downstream. await Arrange - .ArrangeStatusChanged(out var suspend, clientId, "Acme Holdings", Email, Phone, (int)ClientStatus.Suspended); + .StatusChanged(out var suspend, clientId, "Acme Holdings", Email, Phone, (int)ClientStatus.Suspended); await Act .ProduceStatusChanged(clientId, suspend); diff --git a/docs/src/content/docs/concepts/stage.md b/docs/src/content/docs/concepts/stage.md new file mode 100644 index 0000000..7d7315a --- /dev/null +++ b/docs/src/content/docs/concepts/stage.md @@ -0,0 +1,97 @@ +--- +title: The Stage & lifecycle +description: Where your services live during a test — how a Stage is composed once and entered fresh for every test. +--- + +The **Stage** is the runtime a test runs against. It holds the services a test resolves — the real +system-under-test plus its (real or mocked) collaborators — and it's what `Arrange`, `Act` and `Inspect` +pull from. Everything else in Mokkit sits on top of it. + +## Compose once, enter per test + +A Stage comes in two steps: + +1. **`TestStageSetup.Create(...builders)`** composes your [containers](/concepts/containers/) — this is the + expensive part, and you do it **once**. +2. **`setup.EnterStage()`** returns a fresh, isolated `TestStage` — you do this **once per test**, and dispose + it afterwards. + +```csharp +// Once — usually in a class/collection fixture. +var setup = await TestStageSetup.Create( + new BagContainerBuilder() + .AddInstance(email) + .AddInstance(new SignupService(email))); + +// Per test. +var stage = setup.EnterStage(); +// ... arrange / act / inspect ... +stage.Dispose(); +``` + +Each `EnterStage()` opens its own scope, so tests are isolated: scoped services are created per stage and +disposed when the stage is disposed. Nothing leaks between tests. + +## What a Stage gives you + +```csharp +stage.Arrange(); // → ITestArrange — start the setup chain +stage.Act(); // → ITestAct — start the act chain +stage.Inspect(); // → ITestInspect — start the observe chain + +stage.Execute(svc => ...); // resolve one service and run it +stage.ExecuteAsync(svc => ...); // resolve, run, return a result +``` + +`Execute`/`ExecuteAsync` come in 1-to-4-service arities, so a step can pull several collaborators at once. +Your [vocabulary](/concepts/vocabulary/) verbs are thin wrappers over exactly these calls. + +## Wiring it to your test framework + +Mokkit is framework-agnostic — the Stage is composed in whatever "run once" hook your runner offers and +entered in its "per test" hook. The pattern is identical across xUnit, NUnit and MSTest; only the fixture +attributes differ. + +```csharp +// xUnit — the composition is an IClassFixture (built once); each test enters a fresh stage. +public abstract class BaseUnitTest : IClassFixture, IDisposable + where TFixture : BaseStageFixture +{ + protected BaseUnitTest(TFixture fixture) => Stage = fixture.EnterStage(); + + protected TestStage Stage { get; } + + protected ITestArrange Arrange => Stage.Arrange(); + protected ITestAct Act => Stage.Act(); + protected ITestInspect Inspect => Stage.Inspect(); + + public void Dispose() => Stage.Dispose(); +} +``` + +Exposing `Arrange` / `Act` / `Inspect` as properties on a base fixture is what lets a test body read as +`await Arrange.…` / `await Act.…` / `await Inspect.…` with no ceremony. + +## One composition per system-under-test + +Because containers are built **once** per composition, a service is either the *real* thing or a *mock* +within a given Stage — not both. So a type that is the system-under-test in one test but a **dependency** in +another needs **its own fixture**: + +> One fixture per SUT: its real type, plus mocks for that type's direct dependencies. + +This is a feature, not a limitation — it keeps each test's composition small and obvious. The +[project structure](/reference/project-structure/) page shows how to organise fixtures per feature. + +:::note[The 4-phase container build] +`Create` runs each builder through four phases — **PreInit → Init → PreBuild → Build** — before any stage is +entered. The only phase you normally notice is `PreBuild`, where a DI container gets to see the mock +container's registrations so it can [bridge them in](/concepts/containers/#the-mockdi-bridge). It's incidental +machinery; you configure builders, not phases. +::: + +## Next + +- **[Containers & the mock→DI bridge](/concepts/containers/)** — what goes into `Create`, and how mocks reach + the real service. +- **[Captures: Capture vs Trapture](/concepts/captures/)** — how artifacts thread from one phase to the next. diff --git a/docs/src/content/docs/concepts/vocabulary.md b/docs/src/content/docs/concepts/vocabulary.md index fa7ff99..aaa4293 100644 --- a/docs/src/content/docs/concepts/vocabulary.md +++ b/docs/src/content/docs/concepts/vocabulary.md @@ -19,7 +19,8 @@ Here's a test written entirely in a client-management vocabulary: await Arrange .NewClient(out var clientId, WithName("Acme Corporation"), WithEmail("acme@e2e.test")); -var result = await Act(clientId, WithName("Renamed Corporation")); +var result = await Act + .UpdateClient(clientId, WithName("Renamed Corporation")); await Inspect .WriteResult(result).Updated() @@ -27,11 +28,11 @@ await Inspect .EventPublished("clients.updated", clientId); ``` -`NewClient`, `WithName`, `WriteResult`, `Updated`, `ApiClient`, `EventPublished` aren't Mokkit APIs — they're -**your** methods. Mokkit provides `Arrange` / `Act` / `Inspect` and the machinery underneath; you provide the -words. +`NewClient`, `WithName`, `UpdateClient`, `WriteResult`, `Updated`, `ApiClient`, `EventPublished` aren't Mokkit +APIs — they're **your** methods. Mokkit provides `Arrange` / `Act` / `Inspect` and the machinery underneath; +you provide the words. -## Two kinds of verb +## Three kinds of verb ### Arrange verbs — set up, and capture @@ -66,6 +67,27 @@ public static ClientFieldFn WithName(string name) => r => r with { Name = name public static ClientFieldFn WithEmail(string email) => r => r with { Email = email }; ``` +### Act verbs — do the thing, and maybe return a result + +An act verb performs the operation under test. Like an arrange, it can hand an artifact back — either by +**returning** it (`Returning`) or, for a void act, leaving its effects to be observed later in Inspect: + +```csharp +// Return flavor — `var result = await Act.UpdateClient(...)`. +public static ITestAct UpdateClient( + this ITestAct act, Guid clientId, params ClientFieldFn[] fields) => + act.Returning(host => host.ExecuteAsync( + http => ClientApi.UpdateAsync(http, clientId, Build(fields)))); + +// Void flavor — fire the operation; its effects surface downstream in Inspect. +public static ITestAct ProduceStatusChanged(this ITestAct act, Guid clientId, StatusChangedMessage message) => + act.Then(host => host.ExecuteAsync>( + producer => producer.ProduceAsync("clients.status-changed", Serialize(clientId, message)))); +``` + +Act verbs are what let a test grow from a single triple into a [scenario](/concepts/scenarios/) — a sequence +of Arrange / Act / Inspect blocks that walks a whole lifecycle. + ### Inspect verbs — observe An inspect verb resolves what it needs from the stage and asserts. It only reads: diff --git a/docs/src/content/docs/quickstart.md b/docs/src/content/docs/quickstart.md index 5065c34..02995e0 100644 --- a/docs/src/content/docs/quickstart.md +++ b/docs/src/content/docs/quickstart.md @@ -85,9 +85,9 @@ public async Task Registering_a_user_sends_a_welcome_email() { var stage = await NewStage(); - // ACT — resolve the service from the stage and run the one thing under test. - var id = await stage.ExecuteAsync( - service => service.Register("acme@example.com")); + // ACT — the Act phase resolves the service, runs the one thing under test, and returns its result. + var id = await stage.Act().Returning(host => + host.ExecuteAsync(service => service.Register("acme@example.com"))); // INSPECT — observe the outcome through your vocabulary. await stage.Inspect() @@ -97,12 +97,19 @@ public async Task Registering_a_user_sends_a_welcome_email() } ``` +:::note[Act is a phase too] +`stage.Act()` starts the Act phase, symmetric with `Arrange` and `Inspect`. `.Returning(...)` is the flavor +that hands a result back; there are also void and capture flavors. In a real project you'd wrap this in an +**Act verb** — `await Act.RegisterUser("acme@example.com")` — exactly like the Inspect verb above. See +[Arrange / Act / Inspect](/concepts/aai/#act). +::: + ## What just happened - `TestStageSetup.Create(...)` composed your containers; `EnterStage()` gave you a fresh **Stage** for the test. -- `stage.ExecuteAsync(...)` **resolved** the service from the stage and ran it — the - **Act**. +- `stage.Act().Returning(...)` started the **Act** phase, **resolved** the service from the stage, ran it, and + returned its result. - `stage.Inspect().WelcomeEmailSent(...)` ran your **Inspect** verb, which resolved the same `IEmailSender` from the stage and verified the call. diff --git a/docs/src/content/docs/why-mokkit.md b/docs/src/content/docs/why-mokkit.md index 83df937..fe024a0 100644 --- a/docs/src/content/docs/why-mokkit.md +++ b/docs/src/content/docs/why-mokkit.md @@ -40,9 +40,10 @@ Mokkit keeps the readable, sentence-like scenario but drops the separate languag extension methods you author — your **domain vocabulary** — and a test simply composes them: ```csharp -await Arrange.NewClient(out var client, WithName("Acme Corporation")); -var result = await Act(client); -await Inspect.WriteResult(result).Created().EventPublished("clients.created", result); +var result = await Act.CreateClient(WithName("Acme Corporation")); +await Inspect + .WriteResult(result).Created() + .EventPublished("clients.created", result.ClientId!.Value); ``` Because a Mokkit test is ordinary code, the trade-offs invert: @@ -60,17 +61,16 @@ Because a Mokkit test is ordinary code, the trade-offs invert: The headline: with Mokkit, **a test that doesn't make sense won't compile**, and the same `dotnet build` that checks your product code checks your tests' vocabulary. -## When BDD is still the better fit +## Who Mokkit is for -Mokkit is not anti-BDD; it's a different trade-off. Reach for a Gherkin tool when: +Mokkit makes one assumption: the people who write and read your tests are **engineers who live in an IDE**. +Everything it offers — autocomplete, go-to-definition, refactoring, compile-time checking — is aimed squarely +at them. If your tests are written and maintained by the same engineers who write the code, Mokkit gives you +the readability with none of the DSL tax. -- **Non-developers author or read the scenarios.** Business analysts editing `.feature` files is a workflow - Mokkit doesn't try to replace — Mokkit's audience is engineers who live in an IDE. -- **The living documentation *is* the deliverable**, and plain-English feature files are a contractual - artifact. - -If your scenarios are written and maintained by the same engineers who write the code, Mokkit gives you the -readability with none of the DSL tax. +The one workflow it deliberately doesn't target is a **plain-English scenario document as a contractual +deliverable, authored or read by non-developers**. A Mokkit test reads like sentences, but it lives in your +test project as code — not as a separate artifact a non-engineer edits. ## What Mokkit is *not* From eb08abe47487ba63f8fc9dd33edfae78e6c4c337 Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sun, 12 Jul 2026 13:28:06 +0700 Subject: [PATCH 08/12] #10: first batch of guides --- docs/astro.config.mjs | 12 +- docs/src/content/docs/guides/end-to-end.md | 128 +++++++++++++++++ .../docs/guides/eventually-consistent.md | 91 ++++++++++++ .../docs/guides/integration-database.md | 135 ++++++++++++++++++ .../content/docs/guides/real-di-container.md | 105 ++++++++++++++ .../docs/guides/unit-mocked-dependency.md | 113 +++++++++++++++ 6 files changed, 583 insertions(+), 1 deletion(-) create mode 100644 docs/src/content/docs/guides/end-to-end.md create mode 100644 docs/src/content/docs/guides/eventually-consistent.md create mode 100644 docs/src/content/docs/guides/integration-database.md create mode 100644 docs/src/content/docs/guides/real-di-container.md create mode 100644 docs/src/content/docs/guides/unit-mocked-dependency.md diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 28ee2b8..7ea2bd5 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -36,6 +36,17 @@ export default defineConfig({ { label: 'Containers & the mock→DI bridge', slug: 'concepts/containers' }, ], }, + { + label: 'Guides', + items: [ + { label: 'Unit-test a service with a mock', slug: 'guides/unit-mocked-dependency' }, + { label: 'Wire a real DI container', slug: 'guides/real-di-container' }, + { label: 'Integration-test a database', slug: 'guides/integration-database' }, + { label: 'Full black-box E2E', slug: 'guides/end-to-end' }, + { label: 'Async / eventually-consistent assertions', slug: 'guides/eventually-consistent' }, + // More guides land here batch by batch. + ], + }, { label: 'Reference', items: [ @@ -43,7 +54,6 @@ export default defineConfig({ { label: 'API reference', link: '/api/' }, ], }, - // Guides are added as those pages land. ], }), ], diff --git a/docs/src/content/docs/guides/end-to-end.md b/docs/src/content/docs/guides/end-to-end.md new file mode 100644 index 0000000..6a9cd53 --- /dev/null +++ b/docs/src/content/docs/guides/end-to-end.md @@ -0,0 +1,128 @@ +--- +title: Full black-box E2E with Testcontainers +description: Boot the whole system in Docker — API, Postgres, Redis, Kafka — and drive it through its public surface with the same Arrange/Act/Inspect. +--- + +An end-to-end test uses **no mocks at all**: the real API, database, cache and message bus all run in Docker, +and the test drives the system through its public HTTP/Kafka surface. The remarkable part is that the test +*body* is identical to a [unit test](/guides/unit-mocked-dependency/) — only the vocabulary underneath resolves +real clients instead of doubles. + +## The stack + +A collection fixture boots everything once on a shared Docker network — Postgres, Redis, Kafka, and the **real +API built from its Dockerfile**: + +```csharp +_apiImage = new ImageFromDockerfileBuilder() + .WithDockerfileDirectory(CommonDirectoryPath.GetSolutionDirectory(), string.Empty) + .WithDockerfile("src/Mokkit.Example1.Api/Dockerfile") + .WithName($"mokkit-example1-api-e2e-{Guid.NewGuid():N}") + .Build(); +await _apiImage.CreateAsync(); + +_api = new ContainerBuilder() + .WithImage(_apiImage) + .WithNetwork(_network) + .WithEnvironment("Database__Primary", $"Host=postgres;Port=5432;Database={PgDatabase};...") + .WithEnvironment("Kafka__BootstrapServers", InNetworkKafkaListener) + .WithPortBinding(8080, true) + .WithWaitStrategy(Wait.ForUnixContainer() + .UntilHttpRequestIsSucceeded(r => r.ForPath("/health").ForPort(8080))) + .Build(); +await _api.StartAsync(); +``` + +The Mokkit stage then holds **only external clients** pointed at the running stack — this is a perfect fit for +the dependency-free [Bag container](/guides/bag-container/): no DI, no mocks, just pre-built instances. + +```csharp +var external = new BagContainerBuilder().UseInit(bag => +{ + bag.AddInstance(new HttpClient { BaseAddress = apiBaseAddress }); + bag.AddInstance>(BuildKafkaProducer(kafkaBootstrap)); + bag.AddInstance(new KafkaProbe(kafkaBootstrap)); // reads topics, to assert on events + bag.AddFactory(() => new ExampleContext(NpgsqlOptions(_pgConnectionString))); // read the DB directly + return Task.CompletedTask; +}); + +_setup = await TestStageSetup.Create(external); +``` + +## Per-test isolation + +Each test enters a fresh stage; the database is Respawn-reset afterwards. The base fixture keeps the test class +clean and exposes the phase properties: + +```csharp +public abstract class BaseE2ETest : IAsyncLifetime +{ + private readonly E2EStack _stack; + protected BaseE2ETest(E2EStack stack) => _stack = stack; + + protected TestStage Stage { get; private set; } = null!; + protected ITestArrange Arrange => Stage.Arrange(); + protected ITestAct Act => Stage.Act(); + protected ITestInspect Inspect => Stage.Inspect(); + + public Task InitializeAsync() { Stage = _stack.EnterStage(); return Task.CompletedTask; } + public async Task DisposeAsync() { await _stack.ResetDatabaseAsync(); Stage.Dispose(); } +} +``` + +## Vocabulary: real operations, real observations + +Act verbs perform real HTTP calls; inspect verbs read the API, the database and Kafka: + +```csharp +// Act — a real POST, returning the write-result artifact. +public static ITestAct CreateClient(this ITestAct act, params ClientFieldFn[] fields) => + act.Returning(host => host.ExecuteAsync( + http => ClientApi.CreateAsync(http, ArrangeClientApi.Build(fields)))); + +// Inspect — read the row straight from Postgres. +public static ITestInspect DbClient(this ITestInspect inspect, Guid clientId, Action assert) => + inspect.Then(async host => await host.ExecuteAsync(async db => + assert(await db.Clients.AsNoTracking().FirstOrDefaultAsync(c => c.Id == clientId)))); + +// Inspect — confirm the service published an event keyed by the client id. +public static ITestInspect EventPublished(this ITestInspect inspect, string topic, Guid clientId) => + inspect.Then(async host => await host.ExecuteAsync(async probe => + (await probe.SawMessageKeyed(topic, clientId.ToString(), Timeout)).ShouldBeTrue())); +``` + +## The test + +```csharp +[Fact] +public async Task Create_ViaApi_IsRetrievable_Persisted_AndAnnounced() +{ + // ACT — create the client through the public API. + var result = await Act.CreateClient(WithName("Acme Corporation"), WithEmail("acme@e2e.test")); + + // INSPECT — assert the result, capture its id, then observe the three downstream effects concurrently. + await Inspect + .WriteResult(result).Created() + .Ensure(result, r => r.ClientId, out var clientId) + .ThenAll( + b => b.ApiClient(clientId, c => c.Name.ShouldBe("Acme Corporation")), + b => b.DbClient(clientId, c => c.ShouldNotBeNull()), + b => b.EventPublished("clients.created", clientId)); +} +``` + +No part of the system is faked: the assertion passes only if the HTTP endpoint, the persistence layer and the +Kafka publisher all did their jobs. Because effects are asynchronous, E2E leans on +[eventually-consistent assertions](/guides/eventually-consistent/) — and a whole lifecycle can be walked as a +[scenario](/concepts/scenarios/). + +:::note[The Dockerfile's runtime user] +The API image runs as the non-root user the .NET base image provides — `USER $APP_UID`. (Older templates ran +`adduser`, which the .NET 10 base image no longer ships.) +::: + +## Next + +- **[Async / eventually-consistent assertions](/guides/eventually-consistent/)** — `ApiClientEventually`, polling. +- **[Scenario tests](/concepts/scenarios/)** — walk a create → update → suspend lifecycle in one test. +- **[The Bag container](/guides/bag-container/)** — the dependency-free holder used here. diff --git a/docs/src/content/docs/guides/eventually-consistent.md b/docs/src/content/docs/guides/eventually-consistent.md new file mode 100644 index 0000000..9d7e0fd --- /dev/null +++ b/docs/src/content/docs/guides/eventually-consistent.md @@ -0,0 +1,91 @@ +--- +title: Async / eventually-consistent assertions +description: Assert on outcomes that arrive asynchronously — a consumer applying a change, an event landing on a topic — by polling until they're true. +--- + +Some effects don't happen synchronously with the act. A Kafka message is consumed on a background loop; a +projection updates a moment later; an event lands on a topic when the broker gets around to it. Asserting +*immediately* would be flaky. The fix isn't `Thread.Sleep` — it's an inspect verb that **polls until the +expected state, with a timeout**. + +## An "eventually" inspect verb + +Compare the immediate read with the polling one. The immediate verb expects the outcome *now*: + +```csharp +public static ITestInspect ApiClient(this ITestInspect inspect, Guid clientId, Action assert) => + inspect.Then(async host => await host.ExecuteAsync(async http => + { + var response = await http.GetAsync($"/api/v1/clients/{clientId}"); + response.StatusCode.ShouldBe(HttpStatusCode.OK); + assert((await response.Content.ReadFromJsonAsync())!); + })); +``` + +The eventually verb re-reads until a predicate holds, and fails with the last thing it saw: + +```csharp +private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30); + +public static ITestInspect ApiClientEventually( + this ITestInspect inspect, Guid clientId, Func until) => + inspect.Then(async host => await host.ExecuteAsync(async http => + { + ClientResponse? last = null; + var reached = await Poll.Until(async () => + { + var response = await http.GetAsync($"/api/v1/clients/{clientId}"); + if (response.StatusCode != HttpStatusCode.OK) return false; + last = await response.Content.ReadFromJsonAsync(); + return last is not null && until(last); + }, Timeout); + + reached.ShouldBeTrue( + $"client {clientId} did not reach the expected state within {Timeout.TotalSeconds:0}s (last seen: {last})"); + })); +``` + +`Poll.Until(predicate, timeout)` is a tiny helper: it calls the async predicate on a short interval and returns +`true` as soon as it succeeds, or `false` at the deadline. Capturing `last` means a failure tells you *what the +system actually looked like*, not just "timed out" — the difference between a five-minute debug and a five-second +one. + +## Waiting for an event + +The same idea applies to messages. The `EventPublished` verb waits up to a timeout for the probe to see a +message keyed by the client id, rather than assuming it's already there: + +```csharp +public static ITestInspect EventPublished(this ITestInspect inspect, string topic, Guid clientId) => + inspect.Then(async host => await host.ExecuteAsync(async probe => + (await probe.SawMessageKeyed(topic, clientId.ToString(), Timeout)) + .ShouldBeTrue($"expected a message on '{topic}' keyed by {clientId}"))); +``` + +## Using it + +Pick the eventually verb for anything the system does *after* the act returns — here, a status change applied by +a Kafka consumer: + +```csharp +// ACT — emit the status-changed message (a void act; the consumer picks it up asynchronously). +await Act.ProduceStatusChanged(clientId, message); + +// INSPECT — the change shows up via the API eventually, is persisted, and a confirmation is published. +await Inspect + .ApiClientEventually(clientId, c => c.Status == (int)ClientStatus.Suspended) + .DbClient(clientId, c => c!.Status.ShouldBe(ClientStatus.Suspended)) + .EventPublished("clients.updated", clientId); +``` + +:::tip[Keep synchronous checks synchronous] +Only reach for polling where the outcome is genuinely asynchronous. A value the act already produced (a returned +result, a row the handler wrote inline) should be asserted immediately — polling there just hides bugs behind a +timeout. Match the verb to the timing. +::: + +## Next + +- **[Full black-box E2E with Testcontainers](/guides/end-to-end/)** — where async effects are everywhere. +- **[Scenario tests](/concepts/scenarios/)** — interleave eventually-checks between steps of a lifecycle. +- **[Parallel inspects with ThenAll](/guides/thenall/)** — observe several async effects concurrently. diff --git a/docs/src/content/docs/guides/integration-database.md b/docs/src/content/docs/guides/integration-database.md new file mode 100644 index 0000000..9a3c099 --- /dev/null +++ b/docs/src/content/docs/guides/integration-database.md @@ -0,0 +1,135 @@ +--- +title: Integration-test against a real database +description: Exercise the real application layer against a real Postgres, with only the outward-facing edges mocked and Respawn keeping tests isolated. +--- + +An integration test runs the **real** application — real EF Core against a real database — and mocks only the +outward-facing edges (cache, message bus, clock, id generation). The example's integration suite does exactly +this with **Moq + Microsoft DI + Postgres + Respawn**. + +## The composition + +The base fixture composes a Moq container (the mocked edges) with a Microsoft DI container that wires the real +EF Core context and application layer, then bridges the mocks in: + +```csharp +var mocks = new MoqContainerBuilder().UseInit(BuildMocks); + +var services = new ServiceProviderContainerBuilder() + .UseInit(s => BuildServices(s, sessionGuid)) + .UsePreBuild>(InjectMocks); // bridge every mock into DI + +_setup = await TestStageSetup.Create(mocks, services); + +static Task BuildMocks(IMockCollection mocks) +{ + mocks.AddMock(() => new Mock()); + mocks.AddMock(() => new Mock()); + mocks.AddMock(() => new Mock()); + mocks.AddMock(() => new Mock()); + return Task.CompletedTask; +} + +static Task BuildServices(IServiceCollection services, Guid sessionGuid) +{ + services.Configure(o => o.Primary = ResolveConnectionString(sessionGuid)); + services.AddPostgresDbContext(); // the REAL EF Core context + services.AddApplicationLayer(); // the REAL handlers/validators + return Task.CompletedTask; +} +``` + +The database is a real Postgres reachable by connection string — from `docker-compose up postgres` locally, or a +service container in CI (the suite reads `TEST_DATABASE` if set, else defaults to `localhost:5432`). This is +the one difference from the [E2E suite](/guides/end-to-end/), which spins Postgres up in-process with +Testcontainers. + +## Isolation with Respawn + +Build the schema once and create a Respawner in a one-time setup, then reset between every test so each starts +clean: + +```csharp +// One-time: create schema + a Respawner scoped to the service schema. +using var dbStage = _setup.EnterStage(); +await dbStage.ExecuteAsync(async context => +{ + await context.Database.EnsureCreatedAsync(); + await context.Database.OpenConnectionAsync(); + _respawner = await Respawner.CreateAsync(context.Database.GetDbConnection(), new RespawnerOptions + { + SchemasToInclude = [DatabaseConstants.ServiceSchemaName], + DbAdapter = DbAdapter.Postgres + }); +}); + +// After each test: reset, then dispose the stage. +await dbStage.ExecuteAsync(async context => +{ + await context.Database.OpenConnectionAsync(); + await _respawner.ResetAsync(context.Database.GetDbConnection()); +}); +``` + +## Vocabulary: deterministic edges + real rows + +Arrange the mocked clock and id generator so timestamps and ids are assertable, and seed real rows straight +into the database: + +```csharp +public static ITestArrange Clock(this ITestArrange arrange, DateTime? utcNow = null) => + arrange.Then(host => host.Execute>(mock => + mock.SetupGet(x => x.UtcNow).Returns(utcNow ?? FixedUtcNow))); + +// Seed an existing client through the REAL context, and capture it for assertions. +public static ITestArrange DbClient( + this ITestArrange arrange, out Capture client, Action? mutate = null) +{ + var capture = Capture.Start(out client); + return arrange.Then(async host => await host.ExecuteAsync(async context => + { + var entity = NewDefaultClient(mutate); + context.Clients.Add(entity); + await context.SaveChangesAsync(); + capture.Set(entity); + })); +} +``` + +## The test + +Deterministic clock + id, a real create command, and assertions that span the result, the row, the cache mock +and the event mock: + +```csharp +[Test] +public async Task Create_PersistsClient_UpdatesCache_AndPublishesCreatedEvent() +{ + // ARRANGE — pin the clock and id, then build a create command over the defaults. + await Arrange + .Clock(ArrangeClient.FixedUtcNow) + .Ids(ArrangeClient.FixedClientId) + .CreateClientCommand(out var command, WithName("Acme Corporation")); + + // ACT — dispatch through the real handler. + var result = await Act.SaveClient(command); + + // INSPECT — result, then the persisted row, the cache and the published event. + await Inspect + .SaveResult(result).IsSuccess(ArrangeClient.FixedClientId) + .Ensure(result, r => r.ClientId, out var clientId) + .DbClientById(clientId, out var saved, c => Assert.That(c, Is.Not.Null)) + .CacheUpdated(clientId) + .EventPublished(clientId, "created"); +} +``` + +Because the handler ran for real, this test proves the whole slice — validation, EF mapping, the SQL that +lands the row — not a mock of it. Only the *edges* Mokkit can't run in-process (cache, Kafka, wall-clock, +`Guid.NewGuid()`) are doubles. + +## Next + +- **[Deterministic time & ids](/guides/deterministic-time-ids/)** — the `Clock`/`Ids` pattern in depth. +- **[Full black-box E2E with Testcontainers](/guides/end-to-end/)** — no mocks at all, the whole system in Docker. +- **[Snapshot assertions with Verify](/guides/verify-snapshots/)** — `.Verify(saved)` above. diff --git a/docs/src/content/docs/guides/real-di-container.md b/docs/src/content/docs/guides/real-di-container.md new file mode 100644 index 0000000..601d851 --- /dev/null +++ b/docs/src/content/docs/guides/real-di-container.md @@ -0,0 +1,105 @@ +--- +title: Wire a real DI container + bridge mocks +description: Compose the real application from your DI container while a mock container feeds test doubles into it. +--- + +Hand-wiring a service with the [Bag](/guides/bag-container/) is fine for a small SUT, but real code is usually +assembled by a DI container — and you want to test the *real* composition, with only the outermost +collaborators faked. Mokkit does this by running two containers side by side and **bridging** them: a mock +container holds your doubles, your real DI container composes the app, and each mocked dependency is registered +to resolve *from the stage*. This page shows the wiring; for the concept, see +[Containers & the mock→DI bridge](/concepts/containers/). + +## The composition + +The example's unit `BaseStageFixture` is the reusable pattern — a substitute container plus a Microsoft DI +container: + +```csharp +public abstract class BaseStageFixture : IAsyncLifetime +{ + private TestStageSetup _setup = null!; + + public async Task InitializeAsync() + { + var substitutes = new SubstituteContainerBuilder() + .UseInit(s => { ConfigureSubstitutes(s); return Task.CompletedTask; }); + + var services = new ServiceProviderContainerBuilder() + .UseInit(s => + { + s.AddScoped(); + ConfigureServices(s); // register the REAL system-under-test + return Task.CompletedTask; + }) + .UsePreBuild(InjectSubstitutes); // ← the bridge + + _setup = await TestStageSetup.Create(substitutes, services); + } + + public TestStage EnterStage() => _setup.EnterStage(); + + protected abstract void ConfigureSubstitutes(ISubstituteCollection substitutes); + protected abstract void ConfigureServices(IServiceCollection services); + + // Register every substitute so DI resolves it FROM THE STAGE instead of building a real one. + private static Task InjectSubstitutes(IServiceCollection services, ISubstituteCollection substitutes) + { + foreach (var registration in substitutes.Registrations) + services.ResolveFromStage(registration.InnerType); + return Task.CompletedTask; + } +} +``` + +Concrete fixtures then declare only *what differs* — the SUT and which of its dependencies are doubles: + +```csharp +public sealed class ProcessorFixture : BaseStageFixture +{ + protected override void ConfigureSubstitutes(ISubstituteCollection s) + { + s.AddSubstitute>(); + s.AddSubstitute(); + } + + protected override void ConfigureServices(IServiceCollection services) => + services.AddScoped(); +} +``` + +## How the bridge works + +`ResolveFromStage()` (or the loop over `substitutes.Registrations`) registers a DI factory that, when asked +for `T`, reaches into the stage and returns the double the mock container deposited there: + +```csharp +// Effectively what ResolveFromStage registers: +services.AddTransient(serviceType, sp => + sp.GetRequiredService().Resolve(serviceType) + ?? throw new InvalidOperationException($"Cannot resolve {serviceType} from the stage")); +``` + +So the real `ClientStatusChangedProcessor`, resolved from DI, asks for `IKafkaEventPublisher` and gets **the +test's substitute** — the one arranged in `HandlerSucceedsFor` and checked in `ConfirmationPublishedFor`. + +The `UsePreBuild(...)` hook is the one moment ordering matters: **PreBuild** is when the +DI builder can see the mock container's registrations, so it can wire a bridge for each. (This is the only +phase of the [four-phase build](/concepts/stage/#the-4-phase-container-build) you interact with.) + +## Swapping the stack + +Nothing above is Microsoft-DI- or NSubstitute-specific. Substitute the builders and the rest is identical: + +| Swap | For | +| --- | --- | +| `SubstituteContainerBuilder` (NSubstitute) | `MoqContainerBuilder` · `FakeItEasyContainerBuilder` | +| `ServiceProviderContainerBuilder` (MS-DI) | `AutofacContainerBuilder` · `CastleWindsorContainerBuilder` | + +The integration suite, for instance, uses **Moq + Microsoft DI** with the exact same `ResolveFromStage` loop — +see [Integration-test against a real database](/guides/integration-database/). + +## Next + +- **[Containers & the mock→DI bridge](/concepts/containers/)** — the concept in full. +- **[Write a custom container adapter](/guides/custom-container-adapter/)** — if your stack isn't covered. diff --git a/docs/src/content/docs/guides/unit-mocked-dependency.md b/docs/src/content/docs/guides/unit-mocked-dependency.md new file mode 100644 index 0000000..a4da36e --- /dev/null +++ b/docs/src/content/docs/guides/unit-mocked-dependency.md @@ -0,0 +1,113 @@ +--- +title: Unit-test a service with a mocked dependency +description: The bread-and-butter Mokkit test — a real service, its collaborators substituted, driven and verified through your own vocabulary. +--- + +The most common test: one real service, its dependencies replaced with test doubles you drive and verify. This +guide builds one end to end using **NSubstitute**, lifted from the example's unit suite. + +The system under test is a `ClientStatusChangedProcessor` that maps an incoming message to a save command, +dispatches it through a handler, and publishes a confirmation on success. Both collaborators — the handler and +the publisher — are substituted. + +## 1. A fixture: the real SUT + substituted dependencies + +A fixture declares one composition: the real service, plus substitutes for *its* direct dependencies. (See +[the Stage](/concepts/stage/) for why it's one fixture per system-under-test.) + +```csharp +public sealed class ProcessorFixture : BaseStageFixture +{ + protected override void ConfigureSubstitutes(ISubstituteCollection substitutes) + { + substitutes.AddSubstitute>(); + substitutes.AddSubstitute(); + } + + protected override void ConfigureServices(IServiceCollection services) => + services.AddScoped(); +} +``` + +The `BaseStageFixture` behind this composes a substitute container with a real Microsoft DI container and +**bridges** every substitute into DI, so the real processor resolves the *same* doubles the test arranges. That +bridge is the subject of its own guide — [Wire a real DI container](/guides/real-di-container/); here we just +use it. + +## 2. Vocabulary: arrange the doubles, inspect the calls + +**Arrange verbs** build the incoming message and configure the substituted handler: + +```csharp +public static ITestArrange HandlerSucceedsFor( + this ITestArrange arrange, Capture message) => + arrange.Then(host => host.Execute>(handler => + handler.Handle(Arg.Any(), Arg.Any()) + .Returns(new SaveClientCommandResult(true, message.Value!.ClientId)))); +``` + +**Inspect verbs** verify how the SUT drove those doubles — they only *read*: + +```csharp +public static ITestInspect HandledUpdate( + this ITestInspect inspect, Capture message) => + inspect.Then(host => host.Execute>(handler => + handler.Received(1).Handle( + Arg.Is(c => + c.Operation == SaveOperationKind.Update && + c.ClientData.Id == message.Value!.ClientId), + Arg.Any()))); + +public static ITestInspect ConfirmationPublishedFor( + this ITestInspect inspect, Capture message) => + inspect.Then(host => host.Execute(publisher => + publisher.Received(1).PublishClientEventAsync(message.Value!.ClientId, "updated", Arg.Any()))); +``` + +`host.Execute(...)` resolves `T` from the stage. When `T` is a substituted type, you get the substitute — the +very one the real processor will also resolve. See [Building your test vocabulary](/concepts/vocabulary/) for +the verb-authoring patterns. + +## 3. The test + +With the vocabulary in place, the test is three lines of your own language: + +```csharp +public sealed class ClientStatusChangedProcessorTests : BaseUnitTest +{ + public ClientStatusChangedProcessorTests(ProcessorFixture fixture) : base(fixture) { } + + [Fact] + public async Task ValidMessage_UpdatesClient_AndPublishesConfirmation() + { + // ARRANGE — an incoming message, and a handler set to succeed for it. + await Arrange + .IncomingStatusChange(out var message, status: (int)ClientStatus.Suspended) + .HandlerSucceedsFor(message); + + // ACT — run the real processor over the message. + await Stage.Act().Then(host => + host.ExecuteAsync(p => p.ProcessAsync(KafkaMessageFaker.ToJson(message.Value!)))); + + // INSPECT — it dispatched an Update and published the confirmation. + await Inspect + .HandledUpdate(message) + .ConfirmationPublishedFor(message); + } +} +``` + +The negative cases read just as clearly — swap `HandlerSucceedsFor` → `HandlerFailsFor` and assert +`.NoConfirmationPublished()`, or feed `Process("{ not-valid-json")` and assert `.NotHandled()`. + +:::note[Which mock library?] +This suite uses NSubstitute (`AddSubstitute`, `Received`, `Arg`). Mokkit adapts Moq and FakeItEasy just as +well — only the double's API changes, never the test shape. See +[Pick a mock library](/guides/mock-libraries/). +::: + +## Next + +- **[Wire a real DI container](/guides/real-di-container/)** — how the substitutes reach the real service. +- **[Deterministic time & ids](/guides/deterministic-time-ids/)** — substitute the clock and id generator. +- **[Integration-test against a real database](/guides/integration-database/)** — the same shape, real infra. From 805596ad3a0e8e4fbf3af3ab97ccdf50cd1a49e2 Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sun, 12 Jul 2026 13:44:36 +0700 Subject: [PATCH 09/12] #10: second batch of guides --- docs/astro.config.mjs | 24 +++- .../docs/guides/advanced-vocabulary.md | 53 ++++++++ docs/src/content/docs/guides/bag-container.md | 62 ++++++++++ .../docs/guides/custom-container-adapter.md | 113 ++++++++++++++++++ .../docs/guides/deterministic-time-ids.md | 67 +++++++++++ docs/src/content/docs/guides/ensure.md | 60 ++++++++++ .../src/content/docs/guides/inspect-scopes.md | 74 ++++++++++++ docs/src/content/docs/guides/kafka.md | 113 ++++++++++++++++++ .../src/content/docs/guides/mock-libraries.md | 79 ++++++++++++ .../src/content/docs/guides/mokkit-capture.md | 81 +++++++++++++ docs/src/content/docs/guides/thenall.md | 63 ++++++++++ .../content/docs/guides/verify-snapshots.md | 69 +++++++++++ .../src/content/docs/reference/conventions.md | 62 ++++++++++ .../docs/reference/project-structure.md | 108 +++++++++++++++++ 14 files changed, 1027 insertions(+), 1 deletion(-) create mode 100644 docs/src/content/docs/guides/advanced-vocabulary.md create mode 100644 docs/src/content/docs/guides/bag-container.md create mode 100644 docs/src/content/docs/guides/custom-container-adapter.md create mode 100644 docs/src/content/docs/guides/deterministic-time-ids.md create mode 100644 docs/src/content/docs/guides/ensure.md create mode 100644 docs/src/content/docs/guides/inspect-scopes.md create mode 100644 docs/src/content/docs/guides/kafka.md create mode 100644 docs/src/content/docs/guides/mock-libraries.md create mode 100644 docs/src/content/docs/guides/mokkit-capture.md create mode 100644 docs/src/content/docs/guides/thenall.md create mode 100644 docs/src/content/docs/guides/verify-snapshots.md create mode 100644 docs/src/content/docs/reference/conventions.md create mode 100644 docs/src/content/docs/reference/project-structure.md diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 7ea2bd5..dfdb5a3 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -40,16 +40,38 @@ export default defineConfig({ label: 'Guides', items: [ { label: 'Unit-test a service with a mock', slug: 'guides/unit-mocked-dependency' }, + { label: 'Pick a mock library', slug: 'guides/mock-libraries' }, { label: 'Wire a real DI container', slug: 'guides/real-di-container' }, + { label: 'The Bag container', slug: 'guides/bag-container' }, { label: 'Integration-test a database', slug: 'guides/integration-database' }, { label: 'Full black-box E2E', slug: 'guides/end-to-end' }, + { label: 'Test a Kafka consumer / producer', slug: 'guides/kafka' }, { label: 'Async / eventually-consistent assertions', slug: 'guides/eventually-consistent' }, - // More guides land here batch by batch. + { label: 'Deterministic time & ids', slug: 'guides/deterministic-time-ids' }, + ], + }, + { + label: 'Techniques', + items: [ + { label: 'Value & context scopes', slug: 'guides/inspect-scopes' }, + { label: 'Parallel inspects with ThenAll', slug: 'guides/thenall' }, + { label: 'Ensure: derive, guard, capture', slug: 'guides/ensure' }, + { label: 'Snapshot assertions with Verify', slug: 'guides/verify-snapshots' }, + { label: 'Source-generated arranges', slug: 'guides/mokkit-capture' }, + ], + }, + { + label: 'Extending', + items: [ + { label: 'Advanced vocabulary techniques', slug: 'guides/advanced-vocabulary' }, + { label: 'Write a custom container adapter', slug: 'guides/custom-container-adapter' }, ], }, { label: 'Reference', items: [ + { label: 'How to structure a test project', slug: 'reference/project-structure' }, + { label: 'Conventions cheat-sheet', slug: 'reference/conventions' }, // DocFX-generated static site under /api (built separately in CI). { label: 'API reference', link: '/api/' }, ], diff --git a/docs/src/content/docs/guides/advanced-vocabulary.md b/docs/src/content/docs/guides/advanced-vocabulary.md new file mode 100644 index 0000000..ebadfc4 --- /dev/null +++ b/docs/src/content/docs/guides/advanced-vocabulary.md @@ -0,0 +1,53 @@ +--- +title: Advanced vocabulary techniques +description: Level up your Arrange/Inspect verbs with scopes, Ensure, and source-generated arranges — the tools that keep a growing vocabulary clean. +--- + +The [core idea](/concepts/vocabulary/) is simple: verbs are extension methods on `ITestArrange` / `ITestAct` / +`ITestInspect`. As a suite grows, a few Mokkit features become *vocabulary-authoring* techniques — ways to keep +those verbs terse and expressive rather than repetitive. Each has its own guide; this page is the map. + +## Group assertions with scopes + +When several checks concern one value, open a **[value scope](/guides/inspect-scopes/)** so the verbs read as +`.Thing(x).IsA().HasB()` — and a **context scope** when those checks must also run *inside* something (a single +`Assert.Multiple`, a transaction). Scopes turn a cluster of raw asserts into fluent, named steps. + +```csharp +.WriteResult(result).Created() // value scope over the result +.SaveResult(result).IsSuccess() // context scope: runs inside Assert.Multiple +``` + +## Thread ids cleanly with `Ensure` + +Deriving an id off an artifact and guarding it non-empty is a per-test ritual. **[`Ensure`](/guides/ensure/)** +collapses derive-guard-capture into one step, so ids flow between phases without `!.Value` and null-checks: + +```csharp +.Ensure(result, r => r.ClientId, out var clientId) // non-empty Guid, captured +``` + +## Generate the boring arranges + +A verb whose whole job is "build this DTO from these fields and capture it" is pure boilerplate. Let +**[`[MokkitCapture]`](/guides/mokkit-capture/)** write the body — you declare a partial method, the generator +constructs the object (by constructor or object-initializer) and captures it: + +```csharp +[MokkitCapture] +public static partial ITestArrange StatusChanged( + this ITestArrange arrange, out Trapture message, + Guid clientId, string? name, string? email, string? phone, int status); +``` + +## The pattern behind all three + +Each of these keeps *intent* in the test body and *mechanism* in the vocabulary. A scope hides how assertions +are grouped; `Ensure` hides the guard; `[MokkitCapture]` hides the constructor call. The test keeps reading like +a sentence while the plumbing lives — and is reused — in the verb. That's the whole game: as the vocabulary +compounds, tests get *shorter*, not longer. + +## Next + +- **[Value & context scopes](/guides/inspect-scopes/)** · **[Ensure](/guides/ensure/)** · **[`[MokkitCapture]`](/guides/mokkit-capture/)** +- **[Write a custom container adapter](/guides/custom-container-adapter/)** — extend Mokkit itself. diff --git a/docs/src/content/docs/guides/bag-container.md b/docs/src/content/docs/guides/bag-container.md new file mode 100644 index 0000000..63afb26 --- /dev/null +++ b/docs/src/content/docs/guides/bag-container.md @@ -0,0 +1,62 @@ +--- +title: The Bag container +description: A dependency-free container that just holds the instances you give it — perfect for a first test or a stage of pre-built external clients. +--- + +Not every test needs a DI framework. The **Bag** (`Mokkit.Containers.Bag`) is a trivial container that holds +instances you hand it — no auto-wiring, no options, no dependency on Microsoft DI. It's the right tool for two +situations: your very first test, and a stage that just needs to hold some pre-built clients. + +## Two ways to register + +```csharp +var setup = await TestStageSetup.Create( + new BagContainerBuilder() + .AddInstance(email) // a singleton you own; reused across resolves + .AddInstance(new SignupService(email)) + .AddFactory(() => new DbContext(options))); // built fresh per stage, disposed with the stage + +var stage = setup.EnterStage(); +``` + +- **`AddInstance(x)`** — stores the object as-is. The *same* instance is returned on every `Execute`, and + Mokkit does **not** dispose it (you own its lifetime). Ideal for a shared mock or HTTP client. +- **`AddFactory(() => ...)`** — one instance per stage, created on first resolve and **disposed when the stage + is disposed**. Ideal for a per-test `DbContext`. + +Resolving is the same `Execute`/`ExecuteAsync` as any container: + +```csharp +stage.Execute(email => email.Received(1).SendWelcome(address)); +``` + +## When it shines + +**A first test.** Hand-wire a substitute and the service under test — the same substitute goes into both, so you +can drive it and verify it, with zero container ceremony (this is the [quickstart](/quickstart/) setup). + +**A stage of external clients.** In the [E2E suite](/guides/end-to-end/), the whole system runs in Docker and the +stage only needs to *hold clients pointed at it* — no mocks, no DI graph. Bag is a perfect fit: + +```csharp +var external = new BagContainerBuilder().UseInit(bag => +{ + bag.AddInstance(new HttpClient { BaseAddress = apiBaseAddress }); + bag.AddInstance>(BuildKafkaProducer(bootstrap)); + bag.AddInstance(new KafkaProbe(bootstrap)); + bag.AddFactory(() => new ExampleContext(NpgsqlOptions(connectionString))); // fresh + disposed per stage + return Task.CompletedTask; +}); +``` + +## When to reach for more + +The moment you want to test a *real* object graph — a service whose dependencies are themselves resolved and +constructed by a container — move up to a real DI adapter and bridge your mocks in. That's the +[real DI container](/guides/real-di-container/) guide. + +## Next + +- **[Quickstart](/quickstart/)** — a first test built on the Bag. +- **[Full black-box E2E](/guides/end-to-end/)** — the Bag holding external clients. +- **[Wire a real DI container](/guides/real-di-container/)** — when hand-wiring isn't enough. diff --git a/docs/src/content/docs/guides/custom-container-adapter.md b/docs/src/content/docs/guides/custom-container-adapter.md new file mode 100644 index 0000000..89e1125 --- /dev/null +++ b/docs/src/content/docs/guides/custom-container-adapter.md @@ -0,0 +1,113 @@ +--- +title: Write a custom container adapter +description: Not using Moq, NSubstitute, FakeItEasy, or Microsoft DI? The adapter contract is tiny — this walks through a real one end to end. +--- + +Mokkit ships adapters for Moq, NSubstitute, FakeItEasy, Microsoft DI, Autofac and Castle Windsor. If your stack +isn't in that list, you write an adapter — and the contract is small. This page walks the example's own +`SubstituteContainerBuilder` (a from-scratch NSubstitute container) to show the whole thing. + +## The contract + +A container is two interfaces: + +```csharp +public interface IDependencyContainerBuilder +{ + Task PreInit(); // early setup + Task Init(); // register your doubles/services + Task PreBuild(IDependencyContainerBuilder[] builders); // coordinate with sibling containers + TCollection? TryGetCollection() where TCollection : class; // expose your registrations + IDependencyContainer Build(ITestHostBagAccessor bagAccessor); +} + +public interface IDependencyContainer // Build() returns this +{ + IDependencyContainerScope BeginScope(TestHostContext context); // one scope per stage +} +``` + +The four-phase lifecycle (**PreInit → Init → PreBuild → Build**) runs once for all builders at +`TestStageSetup.Create`. Most adapters only need `Init` (register) and `Build` (produce the container). + +## The builder + +Collect registrations in `Init`, hand them to the container in `Build`: + +```csharp +public sealed class SubstituteContainerBuilder : IDependencyContainerBuilder +{ + private Func? _initFn; + public SubstituteCollection SubstituteCollection { get; } = new(); + + public SubstituteContainerBuilder UseInit(Func fn) { _initFn = fn; return this; } + + Task IDependencyContainerBuilder.PreInit() => Task.CompletedTask; + Task IDependencyContainerBuilder.Init() => _initFn?.Invoke(SubstituteCollection) ?? Task.CompletedTask; + Task IDependencyContainerBuilder.PreBuild(IDependencyContainerBuilder[] builders) => Task.CompletedTask; + + // Let a sibling container (e.g. DI) discover our registrations during its PreBuild. + public TCollection? TryGetCollection() where TCollection : class => + SubstituteCollection as TCollection; + + IDependencyContainer IDependencyContainerBuilder.Build(ITestHostBagAccessor bagAccessor) + { + SubstituteCollection.MakeReadOnly(); + return new SubstituteContainer(SubstituteCollection, bagAccessor); + } +} +``` + +## The container: one line is the whole contract + +The container creates a scope per stage; the scope's job is to **publish each double into the test-host bag, +keyed by the service interface**. That single `bag.TryAdd` is what makes the mock→DI +[bridge](/concepts/containers/) work — `ResolveFromStage`, already in Mokkit, does the rest. + +```csharp +public sealed class SubstituteContainer : BaseDependencyContainer, IDependencyContainer +{ + // ...ctor stores the collection + bag accessor... + public IDependencyContainerScope BeginScope(TestHostContext context) => + new SubstituteScope(_collection, _bagAccessor); + + private sealed class SubstituteScope : IDependencyContainerScope + { + private readonly Dictionary _substitutes = new(); + + public SubstituteScope(SubstituteCollection collection, ITestHostBagAccessor bagAccessor) + { + _bagAccessor = bagAccessor; + foreach (var reg in collection.Registrations) + _substitutes[reg.InnerType] = reg.Factory(); // fresh doubles per scope + } + + public void OnAsyncScopeEnter() + { + var bag = _bagAccessor.Bag ?? throw new InvalidOperationException("bag is missing."); + foreach (var (innerType, substitute) in _substitutes) + bag.TryAdd(innerType, substitute); // ← the whole contract + } + + public T? TryResolve() where T : class => + _substitutes.TryGetValue(typeof(T), out var s) ? (T)s : null; + + public void Dispose() { } + } +} +``` + +That's it. Because an NSubstitute fake *is* the interface, the object in the bag, the dependency injected into +the real service, and the handle the test resolves are all the **same instance**. (A Moq adapter differs by one +line — it deposits `mock.Object` for injection while letting tests resolve the `Mock` wrapper.) + +## Shortcut: the mock-container base + +If you're adapting a *mocking* library specifically, `BaseMockContainerBuilder` in +`Mokkit.Containers.Common` already implements the four-phase lifecycle and the shared collection — the built-in +Moq/NSubstitute/FakeItEasy adapters are ~15 lines each on top of it. Start there unless you need full control. + +## Next + +- **[Containers & the mock→DI bridge](/concepts/containers/)** — the concept your adapter plugs into. +- **[Wire a real DI container](/guides/real-di-container/)** — the other half of the bridge. diff --git a/docs/src/content/docs/guides/deterministic-time-ids.md b/docs/src/content/docs/guides/deterministic-time-ids.md new file mode 100644 index 0000000..582d918 --- /dev/null +++ b/docs/src/content/docs/guides/deterministic-time-ids.md @@ -0,0 +1,67 @@ +--- +title: Deterministic time & ids +description: Make timestamps and generated ids assertable by mocking the clock and id generator behind a small arrange verb. +--- + +`DateTime.UtcNow` and `Guid.NewGuid()` make a test unrepeatable — you can't assert on a value you don't control. +The fix is a two-part discipline: production code depends on **abstractions** (`IDateTimeProvider`, +`IIdGenerator`), and tests **arrange those abstractions to fixed values** through a verb. + +## Fixed values + two arrange verbs + +Keep the fixed constants in one place, then wrap the mock setup in `Clock` and `Ids` verbs: + +```csharp +public static readonly DateTime FixedUtcNow = new(2026, 1, 15, 9, 30, 0, DateTimeKind.Utc); +public static readonly Guid FixedClientId = Guid.Parse("11111111-1111-1111-1111-111111111111"); + +public static ITestArrange Clock(this ITestArrange arrange, DateTime? utcNow = null) => + arrange.Then(host => host.Execute>(mock => + mock.SetupGet(x => x.UtcNow).Returns(utcNow ?? FixedUtcNow))); + +public static ITestArrange Ids(this ITestArrange arrange, params Guid[] ids) +{ + var sequence = ids.Length == 0 ? new[] { FixedClientId } : ids; + return arrange.Then(host => host.Execute>(mock => + { + var queue = new Queue(sequence); + // Hand out each id in turn; repeat the last once the queue runs dry. + mock.Setup(x => x.NewId()).Returns(() => queue.Count > 1 ? queue.Dequeue() : queue.Peek()); + })); +} +``` + +`Ids(...)` taking `params Guid[]` means a test that creates several entities can pin each id in order — +`.Ids(firstId, secondId)` — while the common case, `.Ids()`, just uses the fixed default. + +## Using them + +The clock and id are arranged first, so the handler runs against known values and the result is exactly +assertable: + +```csharp +await Arrange + .Clock(ArrangeClient.FixedUtcNow) + .Ids(ArrangeClient.FixedClientId) + .CreateClientCommand(out var command, WithName("Acme Corporation")); + +var result = await Act.SaveClient(command); + +await Inspect + .SaveResult(result).IsSuccess(ArrangeClient.FixedClientId) // the id we pinned + .DbClientById(ArrangeClient.FixedClientId, out var saved, c => Assert.That(c!.CreatedAt, Is.EqualTo(FixedUtcNow))); +``` + +Because both the created-at timestamp and the id are values *you* chose, the assertions are exact equalities — +no "roughly now", no "some Guid". That determinism is also what makes [Verify snapshots](/guides/verify-snapshots/) +stable across runs. + +:::tip[The verbs read like preconditions] +`Clock` and `Ids` are cross-feature arrange vocabulary — they belong in a shared `ArrangeClient` / +`ArrangeCommon` file, not per test. See [Building your test vocabulary](/concepts/vocabulary/). +::: + +## Next + +- **[Snapshot assertions with Verify](/guides/verify-snapshots/)** — deterministic values make snapshots stable. +- **[Integration-test against a real database](/guides/integration-database/)** — where `Clock`/`Ids` shine. diff --git a/docs/src/content/docs/guides/ensure.md b/docs/src/content/docs/guides/ensure.md new file mode 100644 index 0000000..015c4d3 --- /dev/null +++ b/docs/src/content/docs/guides/ensure.md @@ -0,0 +1,60 @@ +--- +title: "Ensure: derive, guard, capture" +description: Turn "the result's id" into a clean, non-empty capture in one step — so ids flow between phases without null-checks littering the test. +--- + +A recurring need: take a value off an artifact (an entity's id), make sure it isn't empty, and thread it into +later steps. Done by hand that's `result.ClientId!.Value` plus a guard plus a capture — noise that repeats in +every test. **`Ensure`** does all three in one call: **derive → guard-as-non-empty → capture**. + +## In Inspect (eager) + +The inspect overloads run immediately, so the capture is filled the moment you call them — perfect for pulling +an id off a result you just asserted, then observing by it: + +```csharp +await Inspect + .WriteResult(result).Created() + .Ensure(result, r => r.ClientId, out var clientId) // non-empty Guid, captured now + .ApiClient(clientId, c => c.Name.ShouldBe("Acme Corporation")) + .EventPublished("clients.created", clientId); +``` + +`Ensure` is type-aware about "empty": it rejects `Guid.Empty`, `null`, `""`, `0`, and empty collections, throwing +a clear failure instead of letting a bogus value flow onward. There's also a direct form when you already hold +the value — `.Ensure(someValue, out var captured)`. + +## In Arrange (deferred) + +The arrange overloads are **deferred** — they capture a `Trapture` that's filled when the chain runs, so you +can derive from a value another arrange step produces: + +```csharp +public static ITestArrange Ensure( + this ITestArrange arrange, ICapture source, Func selector, + out Trapture captured, string? because = null) where TSource : class +{ + var initializer = Trapture.Start(out captured); + return arrange.Then(_ => + { + var value = source.Value ?? throw new InvalidOperationException("Ensure: source capture is not initialized."); + initializer.Set(EnsureGuard.NotEmpty(selector(value), because)); + }); +} +``` + +Use it to guard a derived id before later arranges consume it. There's also a thunk form — +`.Ensure(() => client.Value!.Id, out var id)` — for values built from more than one capture. Both hand back a +[`Trapture`](/concepts/captures/), so the id flows transparently. + +## Why it's worth a verb + +Without `Ensure`, ids arrive as `result.ClientId!.Value` — a null-forgiving operator and an unchecked +assumption in every test. `Ensure` replaces that with a single, self-documenting step that fails loudly and +early if the precondition ("there *is* an id") doesn't hold. It's the idiomatic bridge between an artifact and +the [captures](/concepts/captures/) that carry it forward. + +## Next + +- **[Captures: Capture vs Trapture](/concepts/captures/)** — what `Ensure` produces. +- **[Building your test vocabulary](/concepts/vocabulary/)** — `Ensure` as a vocabulary-authoring technique. diff --git a/docs/src/content/docs/guides/inspect-scopes.md b/docs/src/content/docs/guides/inspect-scopes.md new file mode 100644 index 0000000..db11818 --- /dev/null +++ b/docs/src/content/docs/guides/inspect-scopes.md @@ -0,0 +1,74 @@ +--- +title: Value & context scopes in Inspect +description: Focus a run of assertions on one value, and wrap them in a context — a single Assert.Multiple, a transaction — with inspect scopes. +--- + +Most inspect verbs read the world and assert. Sometimes, though, several assertions all concern **one value** — +a result object, a fetched entity — and you want them grouped. That's an **inspect scope**: a focused block of +checks over a single value, opened with `ThenValueScope`. + +## A value scope + +`ThenValueScope(value)` returns an `ITestInspectScope`, and verbs on that scope receive the value directly. +The write-result vocabulary is a clean example — `WriteResult` opens the scope, `Created`/`Updated`/`Rejected` +assert within it: + +```csharp +public static ITestInspectScope WriteResult(this ITestInspect inspect, ClientWriteResult result) => + inspect.ThenValueScope(result); + +public static ITestInspectScope Created(this ITestInspectScope inspect) => + inspect.Then((result, _) => + { + result.Status.ShouldBe(HttpStatusCode.Created); + result.ClientId.ShouldNotBeNull(); + }); +``` + +Reads naturally at the call site, and flows back into the normal chain afterwards: + +```csharp +await Inspect + .WriteResult(result).Created() // scope over the result + .ApiClient(clientId, c => ...); // back to ordinary inspects +``` + +## A context scope + +A scope can also wrap *how* its assertions run by passing a context callback to `ThenValueScope`. The +integration suite uses this to run every check inside a single NUnit `Assert.Multiple`, so one failing field +doesn't hide the others: + +```csharp +public static ITestInspectScope SaveResult( + this ITestInspect inspect, SaveClientCommandResult result) => + inspect.ThenValueScope(result, async (_, execute) => + { + await Assert.MultipleAsync(async () => await execute()); // wrap the scope's steps + }); + +public static ITestInspectScope IsSuccess( + this ITestInspectScope inspect, Guid? expectedClientId = null) => + inspect.Then((result, _) => + { + Assert.That(result.Success, Is.True); + Assert.That(result.Exception, Is.Null); + if (expectedClientId.HasValue) Assert.That(result.ClientId, Is.EqualTo(expectedClientId.Value)); + }); +``` + +The `execute` delegate runs the scope's chained steps; wrapping it lets you impose a context — `Assert.Multiple` +here, but equally a transaction, a timing block, or a retry. The value and the context travel together, so every +verb on the scope gets both. + +## When to use which + +- **Plain inspect verb** — an observation that stands alone (`ApiClient`, `EventPublished`). +- **Value scope** — several assertions about the *same* value read cleanly as `.Thing(x).IsA().HasB()`. +- **Context scope** — those grouped assertions also need to run *inside* something (multi-assert, transaction). + +## Next + +- **[Building your test vocabulary](/concepts/vocabulary/)** — scopes are a verb-authoring technique. +- **[Snapshot assertions with Verify](/guides/verify-snapshots/)** — often used right after a value scope. +- **[Parallel inspects with ThenAll](/guides/thenall/)** — the other Inspect power tool. diff --git a/docs/src/content/docs/guides/kafka.md b/docs/src/content/docs/guides/kafka.md new file mode 100644 index 0000000..34df221 --- /dev/null +++ b/docs/src/content/docs/guides/kafka.md @@ -0,0 +1,113 @@ +--- +title: Test a Kafka consumer / producer +description: Cover message-driven code at every level — the consumer's logic as a unit, and the real produce/consume round-trip end-to-end. +--- + +Message-driven code has two things worth testing: the **logic** that runs when a message arrives (map it, +dispatch it, publish a confirmation), and the **round-trip** through a real broker. Mokkit handles both — the +first as a fast unit test, the second end-to-end — with the same vocabulary style. + +## Unit: the consumer's logic + +The processing logic doesn't need a real broker — feed it a JSON payload and verify what it did. The +[unit test](/guides/unit-mocked-dependency/) resolves the real processor and substitutes its collaborators: + +```csharp +[Fact] +public async Task ValidMessage_UpdatesClient_AndPublishesConfirmation() +{ + // ARRANGE — an incoming message + a handler set to succeed. + await Arrange + .IncomingStatusChange(out var message, status: (int)ClientStatus.Suspended) + .HandlerSucceedsFor(message); + + // ACT — run the real processor over the raw payload. + await Stage.Act().Then(host => + host.ExecuteAsync(p => p.ProcessAsync(KafkaMessageFaker.ToJson(message.Value!)))); + + // INSPECT — it dispatched an Update and published the confirmation. + await Inspect + .HandledUpdate(message) + .ConfirmationPublishedFor(message); +} +``` + +Malformed input is just another test — `Process("{ not-valid-json")` then `.NotHandled().NoConfirmationPublished()`. + +## Producing: a void Act verb + +For the real round-trip, producing a message is a **void** [Act](/concepts/aai/#act) — its effect is observed +downstream, so it returns nothing: + +```csharp +public static ITestAct ProduceStatusChanged(this ITestAct act, Guid clientId, StatusChangedMessage message) => + act.Then(host => host.ExecuteAsync>(async producer => + { + await producer.ProduceAsync("clients.status-changed", new Message + { + Key = clientId.ToString(), + Value = JsonSerializer.Serialize(message) + }); + producer.Flush(TimeSpan.FromSeconds(5)); + })); +``` + +## Consuming to assert: a probe + +To prove the service *published* an event, read the topic back with a small host-side consumer — a "probe" — +and expose it as an inspect verb. The probe reads from the beginning with a throwaway group and polls up to a +timeout for a message by key: + +```csharp +public sealed class KafkaProbe(string bootstrapServers) +{ + public Task SawMessageKeyed(string topic, string key, TimeSpan timeout) => Task.Run(() => + { + var config = new ConsumerConfig + { + BootstrapServers = bootstrapServers, + GroupId = $"e2e-probe-{Guid.NewGuid():N}", // throwaway group → read from the start + AutoOffsetReset = AutoOffsetReset.Earliest + }; + using var consumer = new ConsumerBuilder(config).Build(); + consumer.Subscribe(topic); + + var deadline = DateTime.UtcNow + timeout; + while (DateTime.UtcNow < deadline) + if (consumer.Consume(TimeSpan.FromMilliseconds(500))?.Message?.Key == key) + return true; + return false; + }); +} + +// The inspect verb over it: +public static ITestInspect EventPublished(this ITestInspect inspect, string topic, Guid clientId) => + inspect.Then(async host => await host.ExecuteAsync(async probe => + (await probe.SawMessageKeyed(topic, clientId.ToString(), Timeout)).ShouldBeTrue())); +``` + +## End-to-end: the whole round-trip + +Put them together and one test proves a message flows in, is consumed, applied, and confirmed — all against a +real broker (see [Full E2E](/guides/end-to-end/)): + +```csharp +await Arrange.StatusChanged(out var message, clientId, name, email, phone, (int)ClientStatus.Suspended); + +await Act.ProduceStatusChanged(clientId, message); // produce onto the topic + +await Inspect + .ApiClientEventually(clientId, c => c.Status == (int)ClientStatus.Suspended) // consumed & applied + .EventPublished("clients.updated", clientId); // confirmation published +``` + +:::note[Pre-create topics; auto-create is racy] +The E2E stack creates topics before the API starts so the consumer binds immediately, and the probe polls +rather than assuming the message is already there — message effects are +[eventually consistent](/guides/eventually-consistent/). +::: + +## Next + +- **[Async / eventually-consistent assertions](/guides/eventually-consistent/)** — why the probe polls. +- **[Full black-box E2E](/guides/end-to-end/)** — the broker running in Docker. diff --git a/docs/src/content/docs/guides/mock-libraries.md b/docs/src/content/docs/guides/mock-libraries.md new file mode 100644 index 0000000..9771115 --- /dev/null +++ b/docs/src/content/docs/guides/mock-libraries.md @@ -0,0 +1,79 @@ +--- +title: Pick a mock library +description: Moq, NSubstitute, or FakeItEasy — choose the mock container that matches your library; the test shape never changes. +--- + +Mokkit doesn't ship a mocking framework — it *adapts* one. Three adapters come in the box, and swapping between +them changes only how you configure and read a double, never the shape of a test. + +| Library | Package | Register with | Resolve in a verb as | +| --- | --- | --- | --- | +| **Moq** | `Mokkit.Containers.Moq` | `AddMock(() => new Mock())` | `Mock` (inject `.Object`) | +| **NSubstitute** | `Mokkit.Containers.NSubstitute` | `AddSubstitute()` | `T` directly | +| **FakeItEasy** | `Mokkit.Containers.FakeItEasy` | `AddFake()` | `T` directly | + +## Registering the doubles + +Each adapter is a container builder you add to `TestStageSetup.Create(...)`, configured through `UseInit`: + +```csharp +// Moq +var mocks = new MoqContainerBuilder().UseInit(m => +{ + m.AddMock(() => new Mock()); + m.AddMock(() => new Mock()); + return Task.CompletedTask; +}); + +// NSubstitute +var subs = new NSubstituteContainerBuilder().UseInit(s => +{ + s.AddSubstitute(); + s.AddSubstitute(); + return Task.CompletedTask; +}); + +// FakeItEasy +var fakes = new FakeItEasyContainerBuilder().UseInit(f => +{ + f.AddFake(); + f.AddFake(); + return Task.CompletedTask; +}); +``` + +## The one real difference: `Mock` vs `T` + +With **Moq**, a double is a `Mock` wrapper — so your verbs resolve `Mock` to configure it, and Mokkit +injects `mock.Object` into the real graph: + +```csharp +public static ITestArrange Clock(this ITestArrange arrange, DateTime utcNow) => + arrange.Then(host => host.Execute>(mock => + mock.SetupGet(x => x.UtcNow).Returns(utcNow))); +``` + +With **NSubstitute** and **FakeItEasy**, the double *is* the interface — no wrapper. Verbs resolve `T` +directly, and the same object is what the real service receives: + +```csharp +public static ITestArrange HandlerSucceedsFor(this ITestArrange arrange, ...) => + arrange.Then(host => host.Execute>(handler => + handler.Handle(Arg.Any(), Arg.Any()) + .Returns(new SaveClientCommandResult(true, id)))); +``` + +Everything else — the [bridge into DI](/guides/real-di-container/), the test body, the Arrange/Act/Inspect +shape — is identical. The example proves it: its unit suite uses NSubstitute and its integration suite uses +Moq, over the very same Mokkit primitives. + +:::note[Not just these three] +If your library or stack isn't covered, the adapter contract is tiny — see +[Write a custom container adapter](/guides/custom-container-adapter/). (The example's unit suite actually rolls +its own NSubstitute container as a demonstration.) +::: + +## Next + +- **[Wire a real DI container](/guides/real-di-container/)** — how the doubles reach the real service. +- **[Unit-test a service with a mock](/guides/unit-mocked-dependency/)** — a full worked test. diff --git a/docs/src/content/docs/guides/mokkit-capture.md b/docs/src/content/docs/guides/mokkit-capture.md new file mode 100644 index 0000000..fce32e6 --- /dev/null +++ b/docs/src/content/docs/guides/mokkit-capture.md @@ -0,0 +1,81 @@ +--- +title: "Source-generated arranges with [MokkitCapture]" +description: Let the generator write the boilerplate body of a "build this object and capture it" arrange verb, so your vocabulary file is just declarations. +--- + +Many arrange verbs are pure boilerplate: start a capture, build an object from the parameters, set the capture +when the chain runs. `[MokkitCapture]` writes that body for you. You declare a **partial** arrange method; the +source generator (bundled in the `Mokkit` package) fills it in. + +## Declare, don't implement + +Mark a partial extension method with `[MokkitCapture]`. Its parameters describe the object to build; the `out` +capture receives it: + +```csharp +public static partial class ArrangeMessages +{ + [MokkitCapture] + public static partial ITestArrange StatusChanged( + this ITestArrange arrange, + out Trapture message, + Guid clientId, string? name, string? email, string? phone, int status); +} +``` + +That's the whole file — no body. The generator produces one equivalent to a hand-written +`Trapture.Start(out message)` + `arrange.Then(_ => set.Set(new StatusChangedMessage { ... }))`. Call it like any +verb: + +```csharp +await Arrange + .StatusChanged(out var message, clientId, name, email, phone, (int)ClientStatus.Suspended); +``` + +## Two construction strategies + +The generator inspects the captured type and builds it the right way: + +```csharp +public sealed record Foo(int Value, string Name); // positional → constructor +public sealed record Baz { public int Code { get; init; } public string Label { get; init; } = ""; } // init props → object initializer + +[MokkitCapture] +public static partial ITestArrange ArrangeFoo(this ITestArrange a, out Trapture foo, int value, string name); +// generated: new Foo(value, name) + +[MokkitCapture] +public static partial ITestArrange ArrangeBaz(this ITestArrange a, out Trapture baz, int code, string label); +// generated: new Baz { Code = code, Label = label } +``` + +- **Positional records / constructor types** → the generator calls the **constructor**, matching parameters by + name. +- **Types with `init` properties** → it uses an **object initializer**. + +Either `Capture` or `Trapture` works as the out parameter — pick per how the value flows (see +[Capture vs Trapture](/concepts/captures/)). Unmentioned fields are simply left at their defaults — handy for +testing "what happens when the contact fields are null": + +```csharp +[MokkitCapture] +public static partial ITestArrange IncomingStatusChangeMissingContact( + this ITestArrange arrange, out Capture message, Guid clientId, int status); +// builds the message from ClientId + Status; leaves Name/Email/Phone null +``` + +## When to use it + +Reach for `[MokkitCapture]` when an arrange verb is *only* "construct this DTO/command/message from these +fields and capture it". When the verb needs real behaviour — call an API, seed a database, set up a mock — +write it by hand with `.Then(...)`. The two mix freely in the same vocabulary file. + +:::note[It's in the box] +The generator ships inside the `Mokkit` package as an analyzer — no extra reference. The declaring class and +method must be `partial`. +::: + +## Next + +- **[Captures: Capture vs Trapture](/concepts/captures/)** — what the generated body produces. +- **[Building your test vocabulary](/concepts/vocabulary/)** — where generated arranges fit. diff --git a/docs/src/content/docs/guides/thenall.md b/docs/src/content/docs/guides/thenall.md new file mode 100644 index 0000000..f65f3dd --- /dev/null +++ b/docs/src/content/docs/guides/thenall.md @@ -0,0 +1,63 @@ +--- +title: Parallel inspects with ThenAll +description: Run independent observations concurrently — three downstream effects at once — while the chain stays ordered and readable. +--- + +After an act, you often need to check several *independent* effects: the API read, the database row, the +published event. Checked one after another they serialise — and each may involve a +[poll with a timeout](/guides/eventually-consistent/), so the waits add up. **`ThenAll`** runs a group of +inspects **concurrently**, then the chain continues in order. + +## The branch-builder form + +The most readable form takes branch builders — each branch is its own little inspect sub-chain: + +```csharp +await Inspect + .WriteResult(result).Created() + .Ensure(result, r => r.ClientId, out var clientId) + .ThenAll( + b => b.ApiClient(clientId, c => c.Name.ShouldBe("Acme Corporation")), + b => b.DbClient(clientId, c => c.ShouldNotBeNull()), + b => b.EventPublished("clients.created", clientId)); +``` + +The three observations run at once; the slowest one determines how long the group takes, not the sum. A branch +can chain multiple steps — `b => b.Then(...).Then(...)` — and they run within that branch in order. + +## Ordering guarantees + +`ThenAll` is a concurrent *group inside* an otherwise ordered chain. Steps before it complete first; steps after +it run only once every branch has finished: + +```csharp +await Inspect + .Then(_ => Add(1)) + .Then(_ => Add(2)) + .ThenAll(b => b.Then(_ => Add(3)), b => b.Then(_ => Add(4))) // 3 and 4 in either order + .Then(_ => Add(5)); // strictly after both +// order: 1, 2, {3,4 in some order}, 5 +``` + +Concurrent resolves from the stage are safe — the container hands every branch the same cached instance — so +even a high fan-out (dozens of branches all resolving the same service) is race-free. + +## Forms + +`ThenAll` also accepts plain inspect functions when you don't need sub-chains: + +```csharp +ITestInspect ThenAll(params Func[] branches); // branch builders (shown above) +ITestInspect ThenAll(params InspectAsyncFn[] inspectFns); // async funcs +ITestInspect ThenAll(params InspectFn[] inspectFns); // sync funcs +``` + +:::tip[Use it for independent effects] +`ThenAll` is for observations that don't depend on each other. If one check must happen before another — read a +value, then assert on it — keep those in sequence; only parallelise what's genuinely independent. +::: + +## Next + +- **[Async / eventually-consistent assertions](/guides/eventually-consistent/)** — why parallelising the waits matters. +- **[Full black-box E2E](/guides/end-to-end/)** — where several downstream effects are checked at once. diff --git a/docs/src/content/docs/guides/verify-snapshots.md b/docs/src/content/docs/guides/verify-snapshots.md new file mode 100644 index 0000000..ac6f379 --- /dev/null +++ b/docs/src/content/docs/guides/verify-snapshots.md @@ -0,0 +1,69 @@ +--- +title: Snapshot assertions with Verify +description: Assert a whole object at once by snapshotting it — wrapped in a reusable inspect verb with project-wide settings. +--- + +Sometimes the clearest assertion is "the whole thing looks like *this*" — every field of a persisted entity, not +a dozen hand-written `Assert.That`s. [Verify](https://github.com/VerifyTests/Verify) snapshots the object to a +committed `.verified.txt` file and fails when it drifts. Mokkit wraps it in a normal inspect verb, so a snapshot +is just another word in your vocabulary. + +## A `Verify` inspect verb + +Centralise the Verify settings once, then expose a verb that snapshots any value (with `[CallerFilePath]` so the +snapshot lands next to the test): + +```csharp +public static class VerifierSetup +{ + public static VerifySettings Default(Action? configure = null) + { + var settings = new VerifySettings(); + settings.DontScrubGuids(); // our ids are deterministic — show them literally + settings.DontIgnoreEmptyCollections(); + configure?.Invoke(settings); + return settings; + } +} + +public static ITestInspect Verify( + this ITestInspect inspect, Capture capture, + Action? configure = null, + [CallerFilePath] string sourceFile = "") => + inspect.Then(async _ => await Verifier.Verify(capture.Value, VerifierSetup.Default(configure), sourceFile)); +``` + +## Using it in a chain + +Read the row into a capture, then hand that capture to `Verify` — the snapshot covers every field at once: + +```csharp +await Inspect + .SaveResult(result).IsSuccess(ArrangeClient.FixedClientId) + .DbClientById(clientId, out var saved, c => Assert.That(c, Is.Not.Null)) + .Verify(saved) // snapshot the whole persisted entity + .CacheUpdated(clientId) + .EventPublished(clientId, "created"); +``` + +Because the [clock and ids are deterministic](/guides/deterministic-time-ids/), the snapshot is stable run to +run — `CreatedAt` is always the fixed time, the id is always the fixed Guid — so a diff means a *real* change, +not noise. + +## The workflow + +1. First run writes `..received.txt` and the test "fails". +2. Review it. If it's right, accept it — rename to `.verified.txt` (or use a clipboard/accept tool) and + **commit** it. +3. From then on, any drift from the committed snapshot fails the test with a diff. + +:::note[Scrub what you can't control] +`DontScrubGuids()` is only safe *because* ids are pinned. For values you genuinely can't fix (a real +timestamp, a random token), let Verify scrub them to ordered placeholders (`Guid_1`, `DateTime_1`) instead — the +snapshot then asserts *shape*, not the volatile value. +::: + +## Next + +- **[Deterministic time & ids](/guides/deterministic-time-ids/)** — the prerequisite for stable snapshots. +- **[Value & context scopes](/guides/inspect-scopes/)** — the `SaveResult(...).IsSuccess()` scope above. diff --git a/docs/src/content/docs/reference/conventions.md b/docs/src/content/docs/reference/conventions.md new file mode 100644 index 0000000..5f24653 --- /dev/null +++ b/docs/src/content/docs/reference/conventions.md @@ -0,0 +1,62 @@ +--- +title: Conventions cheat-sheet +description: The one-screen summary of Mokkit conventions — pin it up while writing tests. +--- + +The short version of the [project structure](/reference/project-structure/) and +[vocabulary](/concepts/vocabulary/) guides. Distilled from the example's three convention docs. + +## The cardinal rule + +> **Arrange and Act *produce* artifacts. Inspect only *observes*** — reads OK, mutations not. + +- If an **inspect** performs the thing under test → lift it into **Act** and pass the artifact on. +- If an **act** asserts → move the assertion into **Inspect**; Act should just return/capture the artifact. + +## Test bodies + +- **No raw setup or assertions in a test body.** Every value is a business-named `Arrange`; every check a + business-named `Inspect`. +- **Three blocks, in order:** `await Arrange…` → `await Act…` → `await Inspect…`, nothing else interleaved. +- The **what** lives in the test body; the **how** lives in reusable verbs. + +## Vocabulary + +- Verbs are **extension methods** on `ITestArrange` / `ITestAct` / `ITestInspect`, returning the same, ending + in `.Then(host => …)`. +- **Colocate** per feature/SUT: `Arrange.cs` · `Act.cs` · `Inspect.cs` next to the + tests, same namespace. +- **Cross-feature** verbs live in a **root-namespace** `Helpers/` folder (in scope everywhere). +- **Defaults at the top, variation via mutators** — build from one default + `params …Fn[]`; tests list only + what differs (`WithName`, `WithEmail`, …). +- Set up mocks inside `host.Execute>(…)`; seed the DB inside `host.ExecuteAsync(…)`. **Await** + async work — return the `Task` from `.Then(async host => await …)`. + +## Captures + +- **Capture, don't return.** Surface values built in Arrange via an `out Capture` / `out Trapture`. +- **Captures fill when the chain is awaited** — don't read `.Value` while *building* a later step in the same + chain; `await` the producer first. +- `Trapture` converts implicitly (transparent flow); `Capture` needs `.Value` (explicit read). +- Use **[`Ensure`](/guides/ensure/)** to derive-guard-capture an id in one step. + +## Stage & fixtures + +- Compose containers **once** (`TestStageSetup.Create`); **`EnterStage()`** per test; dispose after. +- **One fixture per SUT** — a type can't be both the real SUT and a mock in one composition. +- Base fixture exposes `Arrange` / `Act` / `Inspect` properties over the current stage. + +## Assertions & effects + +- **Result assertions → a [value scope](/guides/inspect-scopes/)** (`ThenValueScope`); a **context scope** wraps + them (e.g. `Assert.Multiple`). +- **State assertions read** the real DB/API; **interaction assertions** verify mocks (`Received`, `Verify`). +- **Async effects → [poll](/guides/eventually-consistent/)** (`ApiClientEventually`, a probe). Never + `Task.Delay` a fixed guess. +- **[Snapshot](/guides/verify-snapshots/)** whole objects with `Verify(capture)` — keep values deterministic + ([`Clock`/`Ids`](/guides/deterministic-time-ids/)) so snapshots are stable. + +## Determinism + +- Depend on `IDateTimeProvider` / `IIdGenerator`, not `DateTime.UtcNow` / `Guid.NewGuid()`. +- Arrange them with `Clock(…)` / `Ids(…)` and shared fixed constants. diff --git a/docs/src/content/docs/reference/project-structure.md b/docs/src/content/docs/reference/project-structure.md new file mode 100644 index 0000000..8b079a5 --- /dev/null +++ b/docs/src/content/docs/reference/project-structure.md @@ -0,0 +1,108 @@ +--- +title: How to structure a Mokkit test project +description: The layout that makes vocabulary discoverable and reusable — colocated verbs, one fixture per SUT, and the discipline that keeps AAA honest. +--- + +Mokkit doesn't prescribe a folder layout, but a good one makes your [vocabulary](/concepts/vocabulary/) +discoverable and your fixtures obvious. This is the structure the example uses across three suites. + +## Colocate vocabulary with what it describes + +Keep the verbs for a feature (or system-under-test) next to that feature's tests, in the same namespace — so +they're in scope with no extra `using`s. The canonical unit of organisation is a folder holding four files: + +``` +Features/Client/SaveClient/ +├── ArrangeSaveClient.cs # arrange verbs (build commands, seed state) +├── ActSaveClient.cs # act verbs (the operation under test) +├── InspectSaveClient.cs # inspect verbs (observe results & effects) +└── SaveClientCommandHandlerTests.cs +``` + +`Arrange.cs` / `Act.cs` / `Inspect.cs` mirror the three phases. A test in this folder +reads as `await Arrange.…`, `await Act.…`, `await Inspect.…` using words defined right beside it. + +Genuinely cross-feature verbs (a `Clock`/`Ids` arrange, a shared `DbClient` seed) go in a **root-namespace** +`Helpers/` folder, so enclosing-namespace lookup makes them visible to every feature test. + +## A base fixture builds the Stage once + +Put the [Stage](/concepts/stage/) composition in a base fixture that builds **once** and exposes the three phases +as properties: + +```csharp +protected TestStage Stage { get; } +protected ITestArrange Arrange => Stage.Arrange(); +protected ITestAct Act => Stage.Act(); +protected ITestInspect Inspect => Stage.Inspect(); +``` + +Each test enters a fresh stage over that fixed composition and disposes it afterwards. This is identical across +xUnit / NUnit / MSTest — only the fixture attributes differ. + +## One container build → one fixture per SUT + +Because containers are built once, a type is either the *real* SUT or a *mock* in a given composition — not +both. So each system-under-test gets **its own small fixture**, declaring its real type plus mocks for its +direct dependencies: + +```csharp +public sealed class ProcessorFixture : BaseStageFixture +{ + protected override void ConfigureSubstitutes(ISubstituteCollection s) + { + s.AddSubstitute>(); + s.AddSubstitute(); + } + protected override void ConfigureServices(IServiceCollection services) => + services.AddScoped(); +} +``` + +## The discipline that keeps it honest + +One rule underpins the whole layout: + +> **Arrange and Act *produce* artifacts. Inspect only *observes* them** — reads are fine, mutations are not. + +No raw setup or assertions in a test body: every value is a business-named arrange, every check a business-named +inspect, and the act returns (or captures) the artifact the inspects observe. If an inspect is doing the thing +under test, lift it into Act; if an act asserts, move that into Inspect. + +## Three real layouts + +The example organises each suite by whatever axis fits it — all following the rules above: + +``` +Unit.Tests/ # organised BY SUT/concern (portability proof: xUnit + NSubstitute) +├── BaseStageFixture.cs BaseUnitTest.cs +├── Containers/ # a bring-your-own NSubstitute adapter +├── Cache/ { CacheServiceFixture, ArrangeCache, InspectCache, …Tests } +├── Validation/ { ValidatorFixture, ArrangeCommand, InspectValidation, …Tests } +└── Messaging/ # one concern, several SUTs → subfolder per SUT + ├── Processor/ { ProcessorFixture, ArrangeProcessor, InspectProcessor, …Tests } + ├── Publisher/ { … } + └── Consumer/ { … } + +Integration.Tests/ # organised BY FEATURE (NUnit + Moq + Postgres/Respawn) +├── BaseIntegrationTest.cs +├── Helpers/ { ArrangeClient, InspectClient, VerifierSetup } # cross-feature, root namespace +└── Features/Client/ + ├── GetClient/ { ArrangeGetClient, ActGetClient, InspectGetClient, …Tests } + └── SaveClient/ { ArrangeSaveClient, ActSaveClient, InspectSaveClient, …Tests, *.verified.txt } + +E2E.Tests/ # organised BY EXTERNAL SURFACE (xUnit + Testcontainers, nothing mocked) +├── BaseE2ETest.cs E2EStack.cs E2ECollection.cs KafkaProbe.cs Poll.cs +├── Clients/ { ClientApi, ArrangeClientApi, ArrangeMessages, ActClientApi, +│ InspectClientApi, *FlowTests, ClientLifecycleScenarioTests } +└── Contracts/ # suite-owned wire DTOs — not the service's internal types +``` + +Unit organises **by SUT** (one fixture each), integration **by feature** (cross-feature helpers hoisted to the +root), E2E **by external surface** (infra plumbing at the root, its own black-box contracts). Same Mokkit +primitives throughout — only the surrounding stack changes. + +## Next + +- **[Conventions cheat-sheet](/reference/conventions/)** — the one-screen version of this. +- **[Building your test vocabulary](/concepts/vocabulary/)** — what fills the `Arrange*`/`Act*`/`Inspect*` files. From fb3afa951c7be34ba96b0f93ae21b977d9fbd779 Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sun, 12 Jul 2026 13:54:59 +0700 Subject: [PATCH 10/12] #10: update convention files --- .../E2E-CONVENTIONS.md | 34 +++++++++++-------- .../CONVENTIONS.md | 20 ++++++----- .../UNIT-CONVENTIONS.md | 9 +++-- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/example/Example1/src/Mokkit.Example1.E2E.Tests/E2E-CONVENTIONS.md b/example/Example1/src/Mokkit.Example1.E2E.Tests/E2E-CONVENTIONS.md index 346014b..b12052a 100644 --- a/example/Example1/src/Mokkit.Example1.E2E.Tests/E2E-CONVENTIONS.md +++ b/example/Example1/src/Mokkit.Example1.E2E.Tests/E2E-CONVENTIONS.md @@ -22,10 +22,10 @@ An xUnit collection fixture (built once per run) starts, on a shared Docker netw others (`Database__Primary`, `ConnectionStrings__Redis`, `Kafka__BootstrapServers`), with readiness gated on `GET /health`. -It then builds the Mokkit stage via a `ServiceProviderContainerBuilder` registering **external clients** -pointed at the running stack (`HttpClient`, `IProducer`, `KafkaProbe`, `ExampleContext`, -`IConnectionMultiplexer`). [`BaseE2ETest`](BaseE2ETest.cs) enters a fresh stage per test and -Respawn-resets the database afterwards (ignoring `__EFMigrationsHistory`). +It then builds the Mokkit stage via a dependency-free `BagContainerBuilder` that **holds external clients** +pointed at the running stack (`HttpClient`, `IProducer`, `KafkaProbe`, and a per-stage +`ExampleContext` factory) — no DI framework needed, since nothing here is auto-wired. [`BaseE2ETest`](BaseE2ETest.cs) +enters a fresh stage per test and Respawn-resets the database afterwards (ignoring `__EFMigrationsHistory`). ### Kafka dual-listener The broker advertises **two** listeners: an in-network one (`kafka:19092`, used by the API container) via @@ -33,23 +33,29 @@ The broker advertises **two** listeners: an in-network one (`kafka:19092`, used A single advertised address can't serve both vantage points — see the plan/notes for the full rationale. ## 2. Everything is arrange / act / inspect — each step a real operation -No raw `HttpClient`/`ProduceAsync`/SQL or asserts in test bodies. Helpers live in [Clients/](Clients/): +No raw `HttpClient`/`ProduceAsync`/SQL or asserts in test bodies. Helpers live in [Clients/](Clients/), one +vocabulary file per phase: - **Arranges** ([ArrangeClientApi](Clients/ArrangeClientApi.cs)) perform real operations: `NewClient(out id, …)` → `POST`. Field mutators (`WithName`/`WithEmail`/`WithStatus`) compose a request over valid defaults. -- **Act** is the operation under test — a private `Act(...)` method that resolves the relevant external - client from the stage (`Stage.ExecuteAsync(…)` for a `PUT`, - `Stage.ExecuteAsync>(…)` to emit a message). +- **Acts** ([ActClientApi](Clients/ActClientApi.cs)) are the operations under test, expressed as first-class + Act vocabulary on `ITestAct` (`Act` is a phase, symmetric with Arrange/Inspect, exposed as a property on + `BaseE2ETest`): `Act.CreateClient(…)` / `Act.UpdateClient(id, …)` return a `ClientWriteResult`; + `Act.ProduceStatusChanged(id, message)` is a void act that emits a Kafka message. - **Inspects** ([InspectClientApi](Clients/InspectClientApi.cs)) read outcomes back: `ApiClient(id, …)` / `ApiClientEventually(id, until)` (GET, with polling for async outcomes), `DbClient(id, …)` (direct DB), `EventPublished(topic, id)` (Kafka probe). +A test can also be a whole **scenario** — a sequence of Arrange/Act/Inspect blocks walking a lifecycle +(create → rename → suspend), see [ClientLifecycleScenarioTests](Clients/ClientLifecycleScenarioTests.cs). + ## 3. Arrange/Act produce artifacts; Inspect only observes (reads OK, mutations not) This is the load-bearing rule that keeps AAA honest — it applies to **all three suites**. -- **Act** performs the single action under test and **returns an artifact** — an id, a result/response - object, or a captured exception: `var result = await Act(...)`. An action that is a natural - *precondition* may instead live in **Arrange**, which hands off its artifact via `out Capture`. +- **Act** performs the single action under test and produces an artifact — either **returning** it + (`var result = await Act.CreateClient(...)`) or, for a void act, leaving its effect to be observed + downstream (`await Act.ProduceStatusChanged(id, message)`). An action that is a natural *precondition* may + instead live in **Arrange**, which hands off its artifact via `out Capture`. - **Inspect only observes.** It may **read** — a `GET`, a DB query, a Kafka peek, a mock `Received` — and assert on the handed-over artifact. It must **never perform the mutating action under test** (POST/PUT/ DELETE, produce a message, invoke the SUT command). *Reads in an inspect are fine; mutations are not.* @@ -60,7 +66,7 @@ Worked examples here: - **Create is the Act** → returns a `ClientWriteResult` ([CreateClientFlowTests](Clients/CreateClientFlowTests.cs)): ```csharp - var result = await Act(WithName("Acme"), WithEmail("acme@e2e.test")); // ACT → artifact + var result = await Act.CreateClient(WithName("Acme"), WithEmail("acme@e2e.test")); // ACT → artifact await Inspect .WriteResult(result).Created() // assert the result .ApiClient(result.ClientId!.Value, c => …) // observe downstream by its id @@ -68,7 +74,7 @@ Worked examples here: ``` - **Create as a precondition** → `Arrange.NewClient(out var id, …)` (the update flow needs an existing client first); its status check is a *setup guard*, not the assertion under test. -- **A rejected write is still an Act**: `var result = await Act(WithEmail("bad")); Inspect.WriteResult(result).Rejected();` +- **A rejected write is still an Act**: `var result = await Act.CreateClient(WithEmail("bad")); Inspect.WriteResult(result).Rejected();` — never a POST buried inside an inspect. - **`ApiClientNotFound` is a legitimate inspect** — a 404 `GET` is a *read*, not a mutation. @@ -98,4 +104,4 @@ export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE=/var/run/docker.sock ``` Images are pulled only when missing (`PullPolicy.Missing`); pre-pull `postgres:15-alpine`, `redis:7-alpine`, -`confluentinc/cp-kafka:7.6.0`, and `mcr.microsoft.com/dotnet/{sdk,aspnet}:8.0` to avoid slow first-run pulls. +`confluentinc/cp-kafka:7.6.0`, and `mcr.microsoft.com/dotnet/{sdk,aspnet}:10.0` to avoid slow first-run pulls. diff --git a/example/Example1/src/Mokkit.Example1.Integration.Tests/CONVENTIONS.md b/example/Example1/src/Mokkit.Example1.Integration.Tests/CONVENTIONS.md index 0a7ad39..a53d04c 100644 --- a/example/Example1/src/Mokkit.Example1.Integration.Tests/CONVENTIONS.md +++ b/example/Example1/src/Mokkit.Example1.Integration.Tests/CONVENTIONS.md @@ -24,8 +24,8 @@ public async Task Create_PersistsClient_UpdatesCache_AndPublishesCreatedEvent() .Ids(ArrangeClient.FixedClientId) .CreateClientCommand(out var command, WithName("Acme Corporation")); - // ACT — a private helper resolves the handler from the stage and calls it - var result = await Act(command); + // ACT — an Act verb dispatches the command through the real handler and returns the result + var result = await Act.SaveClient(command); // INSPECT — fluent chain on ITestInspect / ITestInspectScope await Inspect @@ -37,7 +37,7 @@ public async Task Create_PersistsClient_UpdatesCache_AndPublishesCreatedEvent() } ``` -`Arrange` and `Inspect` are properties on [`BaseIntegrationTest`](BaseIntegrationTest.cs); each +`Arrange`, `Act` and `Inspect` are properties on [`BaseIntegrationTest`](BaseIntegrationTest.cs); each starts a fresh chain on the current test stage. `await` runs the chain. --- @@ -91,12 +91,16 @@ Rules: ## 4. Act -One private `Act(...)` per test class. Resolve the component from the stage and call it — nothing else: +Act is a first-class phase (like Arrange/Inspect), expressed as **vocabulary** — one `Act.cs` file +per feature, colocated with the Arrange/Inspect files. Each verb resolves the component from the stage and +calls it — nothing else: ```csharp -private Task Act(SaveClientCommand command) - => Stage.ExecuteAsync, SaveClientCommandResult>( - handler => handler.Handle(command).AsTask()); +// Features/Client/SaveClient/ActSaveClient.cs +public static ITestAct SaveClient(this ITestAct act, SaveClientCommand command) => + act.Returning(host => + host.ExecuteAsync, SaveClientCommandResult>( + handler => handler.Handle(command).AsTask())); ``` The `Act` **returns an artifact** (`result`) that the inspects observe — it never asserts, and the inspects @@ -158,7 +162,7 @@ Helpers/ ArrangeClient.cs / InspectClient.cs # cross-feature building blocks (root namespace) InspectValidation.cs # FluentValidation result inspects Features/Client// - Arrange.cs Inspect.cs + Arrange.cs Act.cs Inspect.cs Tests.cs Tests.cs *.verified.txt ``` diff --git a/example/Example1/src/Mokkit.Example1.Unit.Tests/UNIT-CONVENTIONS.md b/example/Example1/src/Mokkit.Example1.Unit.Tests/UNIT-CONVENTIONS.md index f37b01c..d1c11dc 100644 --- a/example/Example1/src/Mokkit.Example1.Unit.Tests/UNIT-CONVENTIONS.md +++ b/example/Example1/src/Mokkit.Example1.Unit.Tests/UNIT-CONVENTIONS.md @@ -19,8 +19,9 @@ integration suite — only the libraries around them changed. ## 1. Bring-your-own mock library: the custom NSubstitute container -Mokkit ships only a Moq container. Swapping in NSubstitute is just implementing -`IDependencyContainerBuilder` — see [Containers/](Containers/): +Mokkit ships Moq, NSubstitute and FakeItEasy container adapters out of the box. This suite deliberately +**rolls its own** NSubstitute container instead — to demonstrate that the adapter contract is tiny, so you +can plug in *any* mocking (or DI) library by implementing `IDependencyContainerBuilder` — see [Containers/](Containers/): - `SubstituteContainerBuilder` — the builder (`UseInit` registers substitutes). - `SubstituteCollection` / `SubstituteRegistration` — `AddSubstitute()` records `typeof(T)` and a @@ -95,7 +96,9 @@ await Inspect .OffsetCommitted(message); // consumer.Commit(result) called ``` -**Two Act styles — the dependencies always come from the stage either way:** +**Two Act styles — the dependencies always come from the stage either way.** (Act is a first-class Mokkit +phase, used by the integration and E2E suites as `Act.Verb(...)` vocabulary; here every act is a trivial +one-liner, so this suite keeps them as inline `Stage.Execute` helpers rather than a separate `Act` file.) - *Resolve the SUT from the stage* (Cache, Validator, Processor): the real SUT is registered in the fixture and `Act` resolves it, so Mokkit constructs it with the substitutes injected — `Stage.ExecuteAsync(svc => svc.GetClientAsync(id))`. From 49e6ce0b48507de234c6f21a9506335c4e8348aa Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sun, 12 Jul 2026 14:21:43 +0700 Subject: [PATCH 11/12] add local test commands --- docs/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/package.json b/docs/package.json index 1a275f9..4d3568a 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,7 +5,9 @@ "private": true, "scripts": { "dev": "astro dev", + "api": "docfx docfx.json", "build": "astro build", + "build:all": "npm run api && astro build", "preview": "astro preview" }, "dependencies": { From 17cc8ea3f8f67c73cb74cf3eac8e2360a0a9fd24 Mon Sep 17 00:00:00 2001 From: Nikita Ivanov Date: Sun, 12 Jul 2026 14:49:30 +0700 Subject: [PATCH 12/12] update gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 995acd0..da3e098 100644 --- a/.gitignore +++ b/.gitignore @@ -402,3 +402,6 @@ FodyWeavers.xsd # Visual Studio Code .vscode + +# MacOS +.DS_Store \ No newline at end of file