From 7eadcf7599313a473931a92dca70774b8df61573 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sat, 4 Jul 2026 16:28:50 +0900 Subject: [PATCH 01/15] docs: add Astro Starlight documentation site deployed to Cloudflare Pages Modeled on the upstream vercel-labs/emulate docs app (apps/web): guides (getting started, configuration, programmatic API, authentication), reference (architecture, emulator conventions), and 13 service pages generated from skills/*/SKILL.md via website/scripts/sync-services.ts. Deploys to the emulate-docs Cloudflare Pages project from CI. --- .github/workflows/docs.yml | 35 + website/.gitignore | 4 + website/README.md | 39 + website/astro.config.mjs | 42 + website/bun.lock | 1076 +++++++++++++++++ website/package.json | 21 + website/public/favicon.svg | 4 + website/scripts/sync-services.ts | 68 ++ website/src/content.config.ts | 7 + .../src/content/docs/guides/authentication.md | 79 ++ .../src/content/docs/guides/configuration.md | 87 ++ .../content/docs/guides/getting-started.md | 104 ++ .../content/docs/guides/programmatic-api.md | 87 ++ website/src/content/docs/index.mdx | 52 + .../content/docs/reference/architecture.md | 76 ++ .../src/content/docs/reference/conventions.md | 182 +++ website/src/content/docs/services/asana.md | 117 ++ website/src/content/docs/services/autumn.md | 91 ++ website/src/content/docs/services/firebase.md | 92 ++ website/src/content/docs/services/gitlab.md | 53 + website/src/content/docs/services/kakao.md | 81 ++ website/src/content/docs/services/linear.md | 96 ++ website/src/content/docs/services/naver.md | 86 ++ website/src/content/docs/services/posthog.md | 71 ++ website/src/content/docs/services/spotify.md | 71 ++ website/src/content/docs/services/supabase.md | 110 ++ .../src/content/docs/services/tosspayments.md | 87 ++ website/src/content/docs/services/workos.md | 70 ++ website/src/content/docs/services/x.md | 73 ++ website/tsconfig.json | 5 + website/wrangler.jsonc | 6 + 31 files changed, 3072 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 website/.gitignore create mode 100644 website/README.md create mode 100644 website/astro.config.mjs create mode 100644 website/bun.lock create mode 100644 website/package.json create mode 100644 website/public/favicon.svg create mode 100644 website/scripts/sync-services.ts create mode 100644 website/src/content.config.ts create mode 100644 website/src/content/docs/guides/authentication.md create mode 100644 website/src/content/docs/guides/configuration.md create mode 100644 website/src/content/docs/guides/getting-started.md create mode 100644 website/src/content/docs/guides/programmatic-api.md create mode 100644 website/src/content/docs/index.mdx create mode 100644 website/src/content/docs/reference/architecture.md create mode 100644 website/src/content/docs/reference/conventions.md create mode 100644 website/src/content/docs/services/asana.md create mode 100644 website/src/content/docs/services/autumn.md create mode 100644 website/src/content/docs/services/firebase.md create mode 100644 website/src/content/docs/services/gitlab.md create mode 100644 website/src/content/docs/services/kakao.md create mode 100644 website/src/content/docs/services/linear.md create mode 100644 website/src/content/docs/services/naver.md create mode 100644 website/src/content/docs/services/posthog.md create mode 100644 website/src/content/docs/services/spotify.md create mode 100644 website/src/content/docs/services/supabase.md create mode 100644 website/src/content/docs/services/tosspayments.md create mode 100644 website/src/content/docs/services/workos.md create mode 100644 website/src/content/docs/services/x.md create mode 100644 website/tsconfig.json create mode 100644 website/wrangler.jsonc diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..3df0da5 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,35 @@ +name: Docs + +on: + push: + branches: [main] + paths: ['website/**', 'skills/**', 'docs/**'] + pull_request: + paths: ['website/**', 'skills/**', 'docs/**'] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + deployments: write + defaults: + run: + working-directory: website + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version-file: package.json + - run: bun install --frozen-lockfile + - run: bun scripts/sync-services.ts + - run: bun run build + - name: Deploy to Cloudflare Pages + if: github.event_name != 'pull_request' + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + workingDirectory: website + command: pages deploy dist --project-name=emulate-docs diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..4e69439 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,4 @@ +dist/ +node_modules/ +.astro/ +.wrangler/ diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..fdb76ca --- /dev/null +++ b/website/README.md @@ -0,0 +1,39 @@ +# emulate docs + +Documentation site for [pleaseai/emulate](https://github.com/pleaseai/emulate), +built with [Astro Starlight](https://starlight.astro.build) and deployed to +Cloudflare Pages. + +## Development + +```bash +cd website +bun install +bun run dev # http://localhost:4321 +``` + +## Content sources + +- `src/content/docs/guides/` and `reference/` — hand-written pages +- `src/content/docs/services/*.md` — **generated** from `../skills//SKILL.md`. + Do not edit them directly; update the skill and regenerate: + +```bash +bun run sync:services +``` + +## Build & deploy + +```bash +bun run build # static output in dist/ +bun run preview # serve the build locally +``` + +Deployment targets a Cloudflare Pages project named `emulate-docs`: + +- **CI**: `.github/workflows/docs.yml` builds and deploys on pushes to `main` + that touch `website/`, `skills/`, or `docs/`. Requires the + `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` repository secrets. +- **Manual**: `bun run deploy` (wrangler login required). +- **Git-integrated Pages** (alternative): set root directory `website`, + build command `bun run sync:services && bun run build`, output `dist`. diff --git a/website/astro.config.mjs b/website/astro.config.mjs new file mode 100644 index 0000000..84aa5a6 --- /dev/null +++ b/website/astro.config.mjs @@ -0,0 +1,42 @@ +// @ts-check +import starlight from '@astrojs/starlight' +import { defineConfig } from 'astro/config' + +export default defineConfig({ + site: 'https://emulate.pleaseai.dev', + integrations: [ + starlight({ + title: 'emulate', + description: + 'Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation.', + social: [ + { icon: 'github', label: 'GitHub', href: 'https://github.com/pleaseai/emulate' }, + ], + editLink: { + baseUrl: 'https://github.com/pleaseai/emulate/edit/main/website/', + }, + sidebar: [ + { + label: 'Guides', + items: [ + { label: 'Getting Started', slug: 'guides/getting-started' }, + { label: 'Configuration', slug: 'guides/configuration' }, + { label: 'Programmatic API', slug: 'guides/programmatic-api' }, + { label: 'Authentication', slug: 'guides/authentication' }, + ], + }, + { + label: 'Services', + autogenerate: { directory: 'services' }, + }, + { + label: 'Reference', + items: [ + { label: 'Architecture', slug: 'reference/architecture' }, + { label: 'Emulator Conventions', slug: 'reference/conventions' }, + ], + }, + ], + }), + ], +}) diff --git a/website/bun.lock b/website/bun.lock new file mode 100644 index 0000000..8e813f0 --- /dev/null +++ b/website/bun.lock @@ -0,0 +1,1076 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "emulate-docs", + "dependencies": { + "@astrojs/starlight": "^0.36.0", + "astro": "^5.6.0", + "sharp": "^0.34.0", + }, + "devDependencies": { + "wrangler": "^4.0.0", + }, + }, + }, + "packages": { + "@astrojs/compiler": ["@astrojs/compiler@2.13.1", "", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="], + + "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.6", "", {}, "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q=="], + + "@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.11", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.6", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "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", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ=="], + + "@astrojs/mdx": ["@astrojs/mdx@4.3.14", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.11", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.15.0", "es-module-lexer": "^1.7.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.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-FBrqJQORVm+rkRa2TS5CjU9PBA6hkhrwLVBSS9A77gN2+iehvjq1w6yya/d0YKC7osiVorKkr3Qd9wNbl0ZkGA=="], + + "@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="], + + "@astrojs/sitemap": ["@astrojs/sitemap@3.7.3", "", { "dependencies": { "sitemap": "^9.0.0", "stream-replace-string": "^2.0.0", "zod": "^4.3.6" } }, "sha512-f8euLVsyeAmAkSm/1M2Kb8sL8byQmfgbvBNaHFItCheTj/IpiJYSEWVcqDHZ/yEHxiS7+w87mQkzwZaPHmk5GA=="], + + "@astrojs/starlight": ["@astrojs/starlight@0.36.3", "", { "dependencies": { "@astrojs/markdown-remark": "^6.3.1", "@astrojs/mdx": "^4.2.3", "@astrojs/sitemap": "^3.3.0", "@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.41.1", "bcp-47": "^2.1.0", "hast-util-from-html": "^2.0.1", "hast-util-select": "^6.0.2", "hast-util-to-string": "^3.0.0", "hastscript": "^9.0.0", "i18next": "^23.11.5", "js-yaml": "^4.1.0", "klona": "^2.0.6", "mdast-util-directive": "^3.0.0", "mdast-util-to-markdown": "^2.1.0", "mdast-util-to-string": "^4.0.0", "pagefind": "^1.3.0", "rehype": "^13.0.1", "rehype-format": "^5.0.0", "remark-directive": "^3.0.0", "ultrahtml": "^1.6.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "vfile": "^6.0.2" }, "peerDependencies": { "astro": "^5.5.0" } }, "sha512-5cm4QVQHUP6ZE52O43TtUpsTvLKdZa9XEs4l3suzuY7Ymsbz4ojtoL9NhistbMqM+/qk6fm6SmxbOL6hQ/LfNA=="], + + "@astrojs/telemetry": ["@astrojs/telemetry@3.3.0", "", { "dependencies": { "ci-info": "^4.2.0", "debug": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" } }, "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="], + + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], + + "@capsizecss/unpack": ["@capsizecss/unpack@4.0.1", "", { "dependencies": { "fontkitten": "^1.0.3" } }, "sha512-CuNiSqg7+e1cO/GjffyMOm5Tt2jUF9CWHHnvQ/UkqvtkGfHdgwEC0wpmq7fkN3gxwpRnrAN0WzO3vREKmNolMQ=="], + + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="], + + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": ">1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw=="], + + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260701.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Zd9Y1bah6DwwBN2RW8vJohffQrIUazb8UXnqSNecOxM+jJLhUuvv5IOG8dbHcV83TyZAubea6gsQXo2yH1lDdw=="], + + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260701.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-yBLsjS1qCWqFyCY37qRUrYfzHHvMGvjh8zRKJ6MvUivYDhkZTzqduppK38FoqYvayLJ5KbcxH7zo5rkxGqbsaA=="], + + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260701.1", "", { "os": "linux", "cpu": "x64" }, "sha512-vMfqSIMfoo4xmZXEuUVqLpSFS921YKjiR9q7kDXPi6Vld1PK74UHg9LZuBavT2KSyemHUCTpj9y/4JSYOEyQbQ=="], + + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260701.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-HRfwbKU2pK44V2NhoM0+iH0JJSj7nQ9Wv13ifIiGYCmTtDL8/zKtEhX7kQ3D4Vy/Cpjhttl0FkfqXj1aqLDPPg=="], + + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260701.1", "", { "os": "win32", "cpu": "x64" }, "sha512-ngxCiIN9s/fM2o1IBMD0o1/mcXrv2NJVdyznh51UH8sQuvrTrXvV2nM0Uj/qU2wMwF6prgNBcdcd7AZeZGiBQA=="], + + "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], + + "@ctrl/tinycolor": ["@ctrl/tinycolor@4.2.0", "", {}, "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + + "@expressive-code/core": ["@expressive-code/core@0.41.7", "", { "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" } }, "sha512-ck92uZYZ9Wba2zxkiZLsZGi9N54pMSAVdrI9uW3Oo9AtLglD5RmrdTwbYPCT2S/jC36JGB2i+pnQtBm/Ib2+dg=="], + + "@expressive-code/plugin-frames": ["@expressive-code/plugin-frames@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7" } }, "sha512-diKtxjQw/979cTglRFaMCY/sR6hWF0kSMg8jsKLXaZBSfGS0I/Hoe7Qds3vVEgeoW+GHHQzMcwvgx/MOIXhrTA=="], + + "@expressive-code/plugin-shiki": ["@expressive-code/plugin-shiki@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7", "shiki": "^3.2.2" } }, "sha512-DL605bLrUOgqTdZ0Ot5MlTaWzppRkzzqzeGEu7ODnHF39IkEBbFdsC7pbl3LbUQ1DFtnfx6rD54k/cdofbW6KQ=="], + + "@expressive-code/plugin-text-markers": ["@expressive-code/plugin-text-markers@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7" } }, "sha512-Ewpwuc5t6eFdZmWlFyeuy3e1PTQC0jFvw2Q+2bpcWXbOZhPLsT7+h8lsSIJxb5mS7wZko7cKyQ2RLYDyK6Fpmw=="], + + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], + + "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "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" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], + + "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="], + + "@pagefind/darwin-arm64": ["@pagefind/darwin-arm64@1.5.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ=="], + + "@pagefind/darwin-x64": ["@pagefind/darwin-x64@1.5.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw=="], + + "@pagefind/default-ui": ["@pagefind/default-ui@1.5.2", "", {}, "sha512-pm1LMnQg8N2B3n2TnjKlhaFihpz6zTiA4HiGQ6/slKO/+8K9CAU5kcjdSSPgpuk1PMuuN4hxLipUIifnrkl3Sg=="], + + "@pagefind/freebsd-x64": ["@pagefind/freebsd-x64@1.5.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA=="], + + "@pagefind/linux-arm64": ["@pagefind/linux-arm64@1.5.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw=="], + + "@pagefind/linux-x64": ["@pagefind/linux-x64@1.5.2", "", { "os": "linux", "cpu": "x64" }, "sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA=="], + + "@pagefind/windows-arm64": ["@pagefind/windows-arm64@1.5.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g=="], + + "@pagefind/windows-x64": ["@pagefind/windows-x64@1.5.2", "", { "os": "win32", "cpu": "x64" }, "sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg=="], + + "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], + + "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], + + "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], + + "@rollup/pluginutils": ["@rollup/pluginutils@5.4.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.2", "", { "os": "android", "cpu": "arm" }, "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.62.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.62.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.62.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.62.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.62.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.62.2", "", { "os": "linux", "cpu": "arm" }, "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.62.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.62.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.62.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.62.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.62.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.2", "", { "os": "linux", "cpu": "x64" }, "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.62.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.62.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.62.2", "", { "os": "none", "cpu": "arm64" }, "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.62.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.62.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.62.2", "", { "os": "win32", "cpu": "x64" }, "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.2", "", { "os": "win32", "cpu": "x64" }, "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA=="], + + "@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g=="], + + "@shikijs/langs": ["@shikijs/langs@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg=="], + + "@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="], + + "@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + + "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], + + "@speed-highlight/core": ["@speed-highlight/core@1.2.17", "", {}, "sha512-Z92FwKpCtfaW1V0jTU/fh3QzYEZN8wDwrzRIBoADCJfn4mJCNcJN/XegifX7BDrQ8/h9Xh/JnbyMchL0FqXrkg=="], + + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], + + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + + "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/mdx": ["@types/mdx@2.0.14", "", {}, "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="], + + "@types/node": ["@types/node@24.13.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA=="], + + "@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.2", "", {}, "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA=="], + + "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + + "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "array-iterate": ["array-iterate@2.0.1", "", {}, "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="], + + "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], + + "astro": ["astro@5.18.2", "", { "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.6", "@astrojs/markdown-remark": "6.3.11", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.3.1", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.1.1", "cssesc": "^3.0.0", "debug": "^4.4.3", "deterministic-object-hash": "^2.0.2", "devalue": "^5.6.2", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.27.3", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.4.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.1", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.1", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.3", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.3", "unist-util-visit": "^5.0.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^6.4.1", "vitefu": "^1.1.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.3", "zod": "^3.25.76", "zod-to-json-schema": "^3.25.1", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "astro.js" } }, "sha512-TnFwLnAXty5MXKPDGuKXqK4AMBXG+FH6RUdK7Oyc3gyfNoFIthT+4eRbzOK43bdRlLaZuxgciDSjgtggZ3OtGQ=="], + + "astro-expressive-code": ["astro-expressive-code@0.41.7", "", { "dependencies": { "rehype-expressive-code": "^0.41.7" }, "peerDependencies": { "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" } }, "sha512-hUpogGc6DdAd+I7pPXsctyYPRBJDK7Q7d06s4cyP0Vz3OcbziP3FNzN0jZci1BpCvLn9675DvS7B9ctKKX64JQ=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="], + + "bcp-47": ["bcp-47@2.1.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-KLw+H/gd2p4zly1X7Yh/qziuyae5/w/QFnvTng9eZL5fvszL7Whl3MBoWF8yxL7ksUjBfOD+OxkytiqbBpG+Fw=="], + + "bcp-47-match": ["bcp-47-match@2.0.3", "", {}, "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="], + + "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], + + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + + "boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="], + + "camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + + "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], + + "cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], + + "common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], + + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "cookie-es": ["cookie-es@1.2.3", "", {}, "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw=="], + + "crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="], + + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], + + "css-selector-parser": ["css-selector-parser@3.3.0", "", {}, "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g=="], + + "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], + + "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + + "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="], + + "devalue": ["devalue@5.8.1", "", {}, "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="], + + "direction": ["direction@2.0.1", "", { "bin": { "direction": "cli.js" } }, "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="], + + "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], + + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + + "dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="], + + "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "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" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="], + + "esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="], + + "esbuild": ["esbuild@0.28.1", "", { "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" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + + "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="], + + "estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="], + + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + + "estree-util-scope": ["estree-util-scope@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0" } }, "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ=="], + + "estree-util-to-js": ["estree-util-to-js@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "astring": "^1.8.0", "source-map": "^0.7.0" } }, "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg=="], + + "estree-util-visit": ["estree-util-visit@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/unist": "^3.0.0" } }, "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + + "expressive-code": ["expressive-code@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7", "@expressive-code/plugin-frames": "^0.41.7", "@expressive-code/plugin-shiki": "^0.41.7", "@expressive-code/plugin-text-markers": "^0.41.7" } }, "sha512-2wZjC8OQ3TaVEMcBtYY4Va3lo6J+Ai9jf3d4dbhURMJcU4Pbqe6EcHe424MIZI0VHUA1bR6xdpoHYi3yxokWqA=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="], + + "fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="], + + "fontkitten": ["fontkitten@1.0.3", "", { "dependencies": { "tiny-inflate": "^1.0.3" } }, "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="], + + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], + + "h3": ["h3@1.15.11", "", { "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" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="], + + "hast-util-embedded": ["hast-util-embedded@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-is-element": "^3.0.0" } }, "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA=="], + + "hast-util-format": ["hast-util-format@1.1.0", "", { "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" } }, "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA=="], + + "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "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" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], + + "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "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" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], + + "hast-util-has-property": ["hast-util-has-property@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA=="], + + "hast-util-is-body-ok-link": ["hast-util-is-body-ok-link@3.0.1", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ=="], + + "hast-util-is-element": ["hast-util-is-element@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="], + + "hast-util-minify-whitespace": ["hast-util-minify-whitespace@1.0.1", "", { "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" } }, "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw=="], + + "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], + + "hast-util-phrasing": ["hast-util-phrasing@3.0.1", "", { "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" } }, "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ=="], + + "hast-util-raw": ["hast-util-raw@9.1.0", "", { "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" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], + + "hast-util-select": ["hast-util-select@6.0.4", "", { "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" } }, "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw=="], + + "hast-util-to-estree": ["hast-util-to-estree@3.1.3", "", { "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" } }, "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "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" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "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" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + + "hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "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" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="], + + "hast-util-to-string": ["hast-util-to-string@3.0.1", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A=="], + + "hast-util-to-text": ["hast-util-to-text@4.0.2", "", { "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" } }, "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hastscript": ["hastscript@9.0.1", "", { "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" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], + + "html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "html-whitespace-sensitive-tag-names": ["html-whitespace-sensitive-tag-names@3.0.1", "", {}, "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA=="], + + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + + "i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="], + + "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], + + "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], + + "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + + "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + + "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], + + "js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="], + + "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + + "klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + + "lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "magicast": ["magicast@0.5.3", "", { "dependencies": { "@babel/parser": "^7.29.3", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw=="], + + "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], + + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="], + + "mdast-util-directive": ["mdast-util-directive@3.1.0", "", { "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" } }, "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "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" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "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" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "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" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "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" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "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" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "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" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-mdx": ["mdast-util-mdx@3.0.0", "", { "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" } }, "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w=="], + + "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "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" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], + + "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "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" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], + + "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "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" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "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" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "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" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], + + "micromark": ["micromark@4.0.2", "", { "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" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "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" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-directive": ["micromark-extension-directive@3.0.2", "", { "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" } }, "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "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" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "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" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "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" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "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" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "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" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "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" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-extension-mdx-expression": ["micromark-extension-mdx-expression@3.0.1", "", { "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" } }, "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q=="], + + "micromark-extension-mdx-jsx": ["micromark-extension-mdx-jsx@3.0.2", "", { "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" } }, "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ=="], + + "micromark-extension-mdx-md": ["micromark-extension-mdx-md@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ=="], + + "micromark-extension-mdxjs": ["micromark-extension-mdxjs@3.0.0", "", { "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" } }, "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ=="], + + "micromark-extension-mdxjs-esm": ["micromark-extension-mdxjs-esm@3.0.0", "", { "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" } }, "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-mdx-expression": ["micromark-factory-mdx-expression@2.0.3", "", { "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" } }, "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "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" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "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" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "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" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-events-to-acorn": ["micromark-util-events-to-acorn@2.0.3", "", { "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" } }, "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "miniflare": ["miniflare@4.20260701.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "0.34.5", "undici": "7.28.0", "workerd": "1.20260701.1", "ws": "8.21.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-L6eAAi6IKtyb/7J6L+YsH2vb1yBrJWKRXI293JYDiMl70+6nncdAgigex58w6WBd+CwvdMsqOyNyGs95Op5gWQ=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.15", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA=="], + + "neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="], + + "nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="], + + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + + "node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + + "ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], + + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + + "oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="], + + "p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], + + "p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], + + "p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], + + "package-manager-detector": ["package-manager-detector@1.7.0", "", {}, "sha512-xg1eHpwYL/D/HEdWw2goFZP6vV0FH7W+PZ5rFkGjdIDLtxq7EkzBUeT3m+lndYCt8wKbmofUu1MUdMCXkCk9ZQ=="], + + "pagefind": ["pagefind@1.5.2", "", { "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" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q=="], + + "parse-entities": ["parse-entities@4.0.2", "", { "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" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + + "parse-latin": ["parse-latin@7.0.0", "", { "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" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="], + + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + + "postcss": ["postcss@8.5.16", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg=="], + + "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="], + + "postcss-selector-parser": ["postcss-selector-parser@6.1.4", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ=="], + + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + + "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], + + "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], + + "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + + "recma-build-jsx": ["recma-build-jsx@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew=="], + + "recma-jsx": ["recma-jsx@1.0.1", "", { "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" }, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w=="], + + "recma-parse": ["recma-parse@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "esast-util-from-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ=="], + + "recma-stringify": ["recma-stringify@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-util-to-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g=="], + + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "rehype": ["rehype@13.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" } }, "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A=="], + + "rehype-expressive-code": ["rehype-expressive-code@0.41.7", "", { "dependencies": { "expressive-code": "^0.41.7" } }, "sha512-25f8ZMSF1d9CMscX7Cft0TSQIqdwjce2gDOvQ+d/w0FovsMwrSt3ODP4P3Z7wO1jsIJ4eYyaDRnIR/27bd/EMQ=="], + + "rehype-format": ["rehype-format@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-format": "^1.0.0" } }, "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ=="], + + "rehype-parse": ["rehype-parse@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="], + + "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], + + "rehype-recma": ["rehype-recma@1.0.0", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "hast-util-to-estree": "^3.0.0" } }, "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw=="], + + "rehype-stringify": ["rehype-stringify@10.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="], + + "remark-directive": ["remark-directive@3.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-directive": "^3.0.0", "micromark-extension-directive": "^3.0.0", "unified": "^11.0.0" } }, "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "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" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "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" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + + "remark-smartypants": ["remark-smartypants@3.0.2", "", { "dependencies": { "retext": "^9.0.0", "retext-smartypants": "^6.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + + "retext": ["retext@9.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" } }, "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA=="], + + "retext-latin": ["retext-latin@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "parse-latin": "^7.0.0", "unified": "^11.0.0" } }, "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA=="], + + "retext-smartypants": ["retext-smartypants@6.2.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ=="], + + "retext-stringify": ["retext-stringify@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" } }, "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA=="], + + "rollup": ["rollup@4.62.2", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.2", "@rollup/rollup-android-arm64": "4.62.2", "@rollup/rollup-darwin-arm64": "4.62.2", "@rollup/rollup-darwin-x64": "4.62.2", "@rollup/rollup-freebsd-arm64": "4.62.2", "@rollup/rollup-freebsd-x64": "4.62.2", "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", "@rollup/rollup-linux-arm-musleabihf": "4.62.2", "@rollup/rollup-linux-arm64-gnu": "4.62.2", "@rollup/rollup-linux-arm64-musl": "4.62.2", "@rollup/rollup-linux-loong64-gnu": "4.62.2", "@rollup/rollup-linux-loong64-musl": "4.62.2", "@rollup/rollup-linux-ppc64-gnu": "4.62.2", "@rollup/rollup-linux-ppc64-musl": "4.62.2", "@rollup/rollup-linux-riscv64-gnu": "4.62.2", "@rollup/rollup-linux-riscv64-musl": "4.62.2", "@rollup/rollup-linux-s390x-gnu": "4.62.2", "@rollup/rollup-linux-x64-gnu": "4.62.2", "@rollup/rollup-linux-x64-musl": "4.62.2", "@rollup/rollup-openbsd-x64": "4.62.2", "@rollup/rollup-openharmony-arm64": "4.62.2", "@rollup/rollup-win32-arm64-msvc": "4.62.2", "@rollup/rollup-win32-ia32-msvc": "4.62.2", "@rollup/rollup-win32-x64-gnu": "4.62.2", "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA=="], + + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], + + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + + "shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="], + + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "sitemap": ["sitemap@9.0.1", "", { "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" } }, "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ=="], + + "smol-toml": ["smol-toml@1.7.0", "", {}, "sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ=="], + + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "stream-replace-string": ["stream-replace-string@2.0.0", "", {}, "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w=="], + + "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], + + "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + + "svgo": ["svgo@4.0.1", "", { "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": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="], + + "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], + + "tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + + "ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="], + + "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], + + "undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], + + "unified": ["unified@11.0.5", "", { "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" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="], + + "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-modify-children": ["unist-util-modify-children@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "array-iterate": "^2.0.0" } }, "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-position-from-estree": ["unist-util-position-from-estree@2.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ=="], + + "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "unstorage": ["unstorage@1.17.5", "", { "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" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "vite": ["vite@6.4.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A=="], + + "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], + + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], + + "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], + + "widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], + + "workerd": ["workerd@1.20260701.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260701.1", "@cloudflare/workerd-darwin-arm64": "1.20260701.1", "@cloudflare/workerd-linux-64": "1.20260701.1", "@cloudflare/workerd-linux-arm64": "1.20260701.1", "@cloudflare/workerd-windows-64": "1.20260701.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-uF813NG09JwNRRUfJ0zBomyTslSPM810dMj9LVvkQ7RAkLrQLzAlPU8Xh/3dIqZDo2bfd7tChbf2PtqLRARRJQ=="], + + "wrangler": ["wrangler@4.107.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "4.20260701.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260701.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260701.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "cf-wrangler": "bin/cf-wrangler.js", "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-fw69ThymNitZ0oIEBU2yNeq3kK59UKz/jyA3udwRrQIAIsxX57q5qLOpPTN7qc5t8n9pnUeofe0uxtMuhQZW8w=="], + + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], + + "yocto-spinner": ["yocto-spinner@0.2.3", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ=="], + + "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + + "youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], + + "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], + + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + + "zod-to-ts": ["zod-to-ts@1.2.0", "", { "peerDependencies": { "typescript": "^4.9.4 || ^5.0.2", "zod": "^3" } }, "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@astrojs/sitemap/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "@poppinss/colors/kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + + "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + + "ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "astro/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + + "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], + + "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + + "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "astro/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + + "astro/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + + "astro/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + + "astro/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + + "astro/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + + "astro/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + + "astro/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + + "astro/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + + "astro/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + + "astro/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + + "astro/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + + "astro/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + + "astro/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + + "astro/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + + "astro/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + + "astro/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + + "astro/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + + "astro/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + + "astro/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + + "astro/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + + "astro/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + + "astro/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + + "astro/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + + "astro/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + + "astro/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + + "astro/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + + "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], + + "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..7055505 --- /dev/null +++ b/website/package.json @@ -0,0 +1,21 @@ +{ + "name": "emulate-docs", + "type": "module", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "sync:services": "bun scripts/sync-services.ts", + "deploy": "astro build && wrangler pages deploy dist" + }, + "dependencies": { + "@astrojs/starlight": "^0.36.0", + "astro": "^5.6.0", + "sharp": "^0.34.0" + }, + "devDependencies": { + "wrangler": "^4.0.0" + } +} diff --git a/website/public/favicon.svg b/website/public/favicon.svg new file mode 100644 index 0000000..204c811 --- /dev/null +++ b/website/public/favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/website/scripts/sync-services.ts b/website/scripts/sync-services.ts new file mode 100644 index 0000000..1d6fbf3 --- /dev/null +++ b/website/scripts/sync-services.ts @@ -0,0 +1,68 @@ +/** + * Regenerates src/content/docs/services/*.md from ../skills//SKILL.md. + * Run from website/: bun scripts/sync-services.ts + */ +import { readdir } from 'node:fs/promises' +import { join } from 'node:path' + +const ROOT = join(import.meta.dir, '..', '..') +const SKILLS = join(ROOT, 'skills') +const OUT = join(import.meta.dir, '..', 'src', 'content', 'docs', 'services') + +// Sidebar order follows default port assignment; label is the display name. +const SERVICES: Record = { + kakao: { label: 'Kakao', port: 4000, order: 1 }, + naver: { label: 'Naver', port: 4001, order: 2 }, + tosspayments: { label: 'Toss Payments', port: 4002, order: 3 }, + firebase: { label: 'Firebase', port: 4003, order: 4 }, + supabase: { label: 'Supabase', port: 4004, order: 5 }, + asana: { label: 'Asana', port: 4005, order: 6 }, + linear: { label: 'Linear', port: 4006, order: 7 }, + autumn: { label: 'Autumn', port: 4007, order: 8 }, + gitlab: { label: 'GitLab', port: 4008, order: 9 }, + posthog: { label: 'PostHog', port: 4009, order: 10 }, + spotify: { label: 'Spotify', port: 4010, order: 11 }, + workos: { label: 'WorkOS', port: 4011, order: 12 }, + x: { label: 'X (Twitter)', port: 4012, order: 13 }, +} + +function firstSentence(text: string): string { + const normalized = text.replaceAll('\n', ' ').trim() + const match = normalized.match(/^.*?[.!?](?:\s|$)/) + return (match ? match[0] : normalized).trim() +} + +for (const dir of await readdir(SKILLS)) { + const meta = SERVICES[dir] + if (!meta) { + console.warn(`skip: no service mapping for skills/${dir}`) + continue + } + const raw = await Bun.file(join(SKILLS, dir, 'SKILL.md')).text() + + const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n/) + if (!fmMatch) throw new Error(`no frontmatter in skills/${dir}/SKILL.md`) + const descMatch = fmMatch[1].match(/^description:\s*(.*)$/m) + const description = firstSentence(descMatch?.[1] ?? '') + + let body = raw.slice(fmMatch[0].length).trim() + // Drop the leading H1 — Starlight renders the frontmatter title instead. + body = body.replace(/^#\s+.*\n+/, '') + + const page = `--- +title: ${meta.label} +description: ${JSON.stringify(description)} +sidebar: + label: ${meta.label} + order: ${meta.order} +--- + +:::note +Default port **${meta.port}** · Package [\`@pleaseai/emulate-${dir === 'tosspayments' ? 'toss-payments' : dir}\`](https://www.npmjs.com/package/@pleaseai/emulate-${dir === 'tosspayments' ? 'toss-payments' : dir}) +::: + +${body} +` + await Bun.write(join(OUT, `${dir}.md`), page) + console.log(`wrote services/${dir}.md`) +} diff --git a/website/src/content.config.ts b/website/src/content.config.ts new file mode 100644 index 0000000..0ae6ac4 --- /dev/null +++ b/website/src/content.config.ts @@ -0,0 +1,7 @@ +import { docsLoader } from '@astrojs/starlight/loaders' +import { docsSchema } from '@astrojs/starlight/schema' +import { defineCollection } from 'astro:content' + +export const collections = { + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), +} diff --git a/website/src/content/docs/guides/authentication.md b/website/src/content/docs/guides/authentication.md new file mode 100644 index 0000000..3a50f83 --- /dev/null +++ b/website/src/content/docs/guides/authentication.md @@ -0,0 +1,79 @@ +--- +title: Authentication +description: How tokens, OAuth flows, and the user-picker login page work across the emulators. +sidebar: + order: 4 +--- + +Each emulator reproduces the real service's authentication scheme — Bearer +tokens, Basic auth, `apikey` headers, or a full OAuth 2.0 flow — validated +against in-memory state rather than cryptographic signatures. + +## Tokens + +The top-level `tokens` section of the seed config maps fixed bearer tokens to +users: + +```yaml +tokens: + test-token: + login: dev@example.com # matches a seeded user's email, id, or name +``` + +Without a `tokens` section, a default `test_token_admin` token is seeded. +Services with bearer-guarded REST APIs (Asana, for example) resolve unmatched +tokens to the first seeded user by default, so `me`-style endpoints work out +of the box: + +```bash +curl http://localhost:4005/api/1.0/users/me \ + -H "Authorization: Bearer any-token" +``` + +## OAuth 2.0 flows + +Kakao, Naver, PostHog, Spotify, WorkOS, and X implement real OAuth flows. +Access tokens issued by `/oauth/token`-style endpoints are stored in the +service's store and validated by lookup on subsequent API calls. + +### The login page + +Authorize endpoints render a user-picker page listing the seeded users. +Selecting a user issues an authorization code and redirects to the +`redirect_uri` — exactly like the real consent screen. + +### Instant approval for CI + +Every authorize endpoint also accepts a user id query parameter +(`?user_id=` or `?user=` depending on the service) that skips the +login page and auto-approves: + +```bash +curl -i "http://localhost:4000/oauth/authorize?client_id=kakao_rest_api_key_example\ +&redirect_uri=http://localhost:3000/api/auth/callback/kakao&response_type=code&user_id=1001" +# → 302 Location: .../callback?code= +``` + +## Service-specific schemes + +| Service | Scheme | +| --- | --- | +| Kakao | OAuth token exchange with `client_id` (+ `client_secret` if configured); `kapi` calls use `Authorization: Bearer` | +| Naver | OAuth issue/refresh/delete grants; profile API uses `Authorization: Bearer` | +| Toss Payments | `Authorization: Basic base64(":")` | +| Firebase | API key query param (`?key=`) for Identity Toolkit; OAuth 2.0 Bearer for FCM v1 | +| Supabase | `apikey` header (anon or service_role) + `Authorization: Bearer` for GoTrue sessions | +| Asana / Linear / GitLab / PostHog | `Authorization: Bearer ` | +| Spotify | Client credentials grant → Bearer token | +| WorkOS | API key Bearer auth + OAuth/OIDC user flows | +| X | OAuth 2.0 PKCE → Bearer token | + +See each [service page](/services/kakao/) for the exact endpoints and +example curl flows. + +## Intentional simplifications + +- Tokens and JWTs are **unsigned** — they are validated via store lookup, so + public-key verification against the emulator is not possible. +- Supabase RLS is not emulated: anon and service_role get identical access. +- Rate limiting is core's default only (5,000 requests/hour). diff --git a/website/src/content/docs/guides/configuration.md b/website/src/content/docs/guides/configuration.md new file mode 100644 index 0000000..65aacce --- /dev/null +++ b/website/src/content/docs/guides/configuration.md @@ -0,0 +1,87 @@ +--- +title: Configuration +description: Seed the emulators with app keys, users, and table data from an emulate.config.yaml file. +sidebar: + order: 2 +--- + +Emulators start with sensible built-in defaults, but most real test setups +want deterministic fixtures: known app keys, known users, known table rows. +That is what the seed config is for. + +## The seed config file + +When an `emulate.config.yaml` is present in the working directory (or a file +is passed via `--seed `), the emulators start pre-seeded with the data +it contains. **Only the services present in the config are started.** + +```bash +# Generate a starter config with example data for every service +npx @pleaseai/emulate init + +# Only include specific services +npx @pleaseai/emulate init --service kakao,supabase + +# Start from an explicit file (YAML or JSON) +npx @pleaseai/emulate --seed my-fixtures.yaml +``` + +## Structure + +The file has one top-level section per service, plus an optional shared +`tokens` section: + +```yaml +# Map specific bearer tokens to specific users (shared across services) +tokens: + test-token: + login: dev@example.com + +kakao: + apps: + - client_id: kakao_rest_api_key_example + client_secret: kakao_client_secret_example + redirect_uris: [http://localhost:3000/api/auth/callback/kakao] + users: + - user_id: 1001 + nickname: 홍길동 + email: hong@example.com + profile_image_url: https://k.kakaocdn.net/dn/profile.jpg + +supabase: + anon_key: test-anon-key + tables: + todos: + - { id: 1, title: Buy groceries, completed: false } +``` + +Each service page documents its own seed section shape — see the +**Seed Config** heading on any page under [Services](/services/kakao/). + +## Seeding rules + +- Seeding is **idempotent**: entries already present (matched by their natural + key) are skipped, so restarting with the same config never duplicates data. +- Config values win over the plugin's built-in defaults. +- Some services accept a `webhooks: [{ url, events }]` list in their section; + those subscriptions are registered at startup. + +## Tokens + +The top-level `tokens` map assigns fixed bearer tokens to users: + +```yaml +tokens: + test-token: + login: dev@example.com # matches a seeded user's email, id, or name + scopes: [] # optional +``` + +Services that use bearer auth resolve `Authorization: Bearer ` through +this map. Without it, a default `test_token_admin` token is available, and +most services fall back to the first seeded user for unmatched tokens — see +[Authentication](/guides/authentication/). + +## Environment variables + +The base port can also be set via `EMULATE_PORT` or `PORT`. diff --git a/website/src/content/docs/guides/getting-started.md b/website/src/content/docs/guides/getting-started.md new file mode 100644 index 0000000..48b09da --- /dev/null +++ b/website/src/content/docs/guides/getting-started.md @@ -0,0 +1,104 @@ +--- +title: Getting Started +description: Install and run the emulate CLI — local drop-in replacements for Kakao, Naver, Toss Payments, Firebase, Supabase, and more. +sidebar: + order: 1 +--- + +Local drop-in replacement for Kakao, Naver, Toss Payments, Firebase, Supabase, +Asana, Linear, Autumn, GitLab, PostHog, Spotify, WorkOS, and X APIs. Built for +CI and no-network sandboxes. Fully stateful, production-fidelity API +emulation. Not mocks. + +## Quick start + +```bash +npx @pleaseai/emulate +``` + +All services start with sensible defaults — no config file needed: + +| Service | Port | Emulated surface | +| --- | --- | --- | +| [Kakao](/services/kakao/) | 4000 | OAuth 2.0 (kauth), user API, KakaoTalk self-memo send (kapi) | +| [Naver](/services/naver/) | 4001 | Naver Login OAuth (issue/refresh/delete), profile API (`/v1/nid/me`) | +| [Toss Payments](/services/tosspayments/) | 4002 | Payment confirm/lookup/cancel, order lookup, checkout simulation, webhooks | +| [Firebase](/services/firebase/) | 4003 | Auth (Identity Toolkit REST), Secure Token, FCM v1 | +| [Supabase](/services/supabase/) | 4004 | GoTrue Auth (signup/token/user), PostgREST table CRUD + filters | +| [Asana](/services/asana/) | 4005 | Workspaces, teams, projects, sections, tasks, tags, stories, webhooks | +| [Linear](/services/linear/) | 4006 | Linear GraphQL API (read-only) with Relay pagination | +| [Autumn](/services/autumn/) | 4007 | Billing: customers, balances, plans, attach, hosted checkout | +| [GitLab](/services/gitlab/) | 4008 | GitLab GraphQL endpoint with real-schema introspection | +| [PostHog](/services/posthog/) | 4009 | Event capture, read API, OAuth 2.0 with dynamic client registration | +| [Spotify](/services/spotify/) | 4010 | OAuth client credentials, catalog search, artists, albums, tracks | +| [WorkOS](/services/workos/) | 4011 | User Management, organizations, OAuth/OIDC, Vault KV | +| [X](/services/x/) | 4012 | X API v2: OAuth 2.0 PKCE, tweets, users, timelines | + +## CLI + +```bash +# Start all services (zero-config) +npx @pleaseai/emulate + +# Start specific services +npx @pleaseai/emulate --service kakao,tosspayments + +# Custom base port +npx @pleaseai/emulate --port 3000 + +# Use a seed config file +npx @pleaseai/emulate --seed emulate.config.yaml + +# Generate a starter config +npx @pleaseai/emulate init + +# Generate config for a specific service +npx @pleaseai/emulate init --service kakao + +# List available services +npx @pleaseai/emulate list +``` + +## Options + +| Flag | Default | Description | +| --- | --- | --- | +| `-p, --port ` | `4000` | Base port (auto-increments per service) | +| `-s, --service ` | all | Comma-separated services to enable | +| `--seed ` | auto-detect | Path to seed config (YAML or JSON) | +| `--base-url ` | — | Override base URL (single service only) | + +When a single service is started with `--service`, it runs on the base port. +When multiple services run together, ports are assigned sequentially from the +base port in registry order. + +## Running from source + +Tool versions are pinned with [mise](https://mise.jdx.dev) — `mise install` +provisions bun and node. (Without mise, any recent bun works.) + +```bash +mise install +bun install +bun run build + +# Start every service +bun packages/emulate/dist/index.js + +# Start specific services only +bun packages/emulate/dist/index.js --service kakao,tosspayments +``` + +## Pointing your app at the emulator + +To use an emulator, only the base URL your SDK talks to needs to change: + +- Firebase: `FIREBASE_AUTH_EMULATOR_HOST=localhost:4003` +- Supabase: `createClient("http://localhost:4004", anonKey)` +- Everything else: set the service base URL to `http://localhost:` + +## Next steps + +- [Configuration](/guides/configuration/) — seed apps, users, and table data from YAML +- [Programmatic API](/guides/programmatic-api/) — embed emulators in your test suite +- [Authentication](/guides/authentication/) — how tokens and OAuth flows work diff --git a/website/src/content/docs/guides/programmatic-api.md b/website/src/content/docs/guides/programmatic-api.md new file mode 100644 index 0000000..81df6b9 --- /dev/null +++ b/website/src/content/docs/guides/programmatic-api.md @@ -0,0 +1,87 @@ +--- +title: Programmatic API +description: Use @pleaseai/emulate as a library — createEmulator, reset, and close for test suites. +sidebar: + order: 3 +--- + +Besides the CLI, `@pleaseai/emulate` exports a programmatic API for embedding +emulators directly in a test suite. + +## createEmulator + +```ts +import { createEmulator } from '@pleaseai/emulate' + +const emulator = await createEmulator({ + service: 'supabase', + port: 4004, + seed: { + supabase: { + anon_key: 'test-anon-key', + tables: { todos: [{ id: 1, title: 'Buy groceries', completed: false }] }, + }, + }, +}) + +// emulator.url → "http://localhost:4004" +// ... run your tests + +emulator.reset() // restore seeded state +await emulator.close() // shut down the HTTP server +``` + +### Options + +| Option | Type | Description | +| --- | --- | --- | +| `service` | `ServiceName` | Which emulator to start (`'kakao'`, `'supabase'`, …) — one per instance | +| `port` | `number` | Port to listen on (default `4000`) | +| `seed` | `SeedConfig` | Same shape as `emulate.config.yaml` — a per-service section plus optional `tokens` | +| `baseUrl` | `string` | Override the advertised base URL (useful behind a proxy) | + +### Instance methods + +| Member | Description | +| --- | --- | +| `url` | Base URL the emulator is reachable at | +| `reset()` | Clears the in-memory store and re-applies the seed — call between tests | +| `close()` | Stops the HTTP server; resolves when the port is released | + +## Test-runner setup (bun:test / Vitest / Jest) + +Start the emulator once per file (or in a global setup), reset between tests: + +```ts +import { afterAll, beforeAll, beforeEach } from 'bun:test' +import { createEmulator, type Emulator } from '@pleaseai/emulate' + +let emulator: Emulator + +beforeAll(async () => { + emulator = await createEmulator({ service: 'kakao', port: 4000 }) +}) + +beforeEach(() => emulator.reset()) + +afterAll(() => emulator.close()) +``` + +`reset()` is synchronous and cheap (in-memory only), so per-test resets add +no meaningful overhead. + +## Testing routes without a port + +Service plugins can also be exercised entirely in-process through +`app.fetch`, with no port bound at all — this is how the emulators' own test +suites run: + +```ts +import { createServer } from '@emulators/core' +import { kakaoPlugin } from '@pleaseai/emulate-kakao' + +const { app, store } = createServer(kakaoPlugin, { port: 4000 }) +kakaoPlugin.seed?.(store, 'http://localhost:4000') + +const res = await app.fetch(new Request('http://localhost:4000/v2/user/me')) +``` diff --git a/website/src/content/docs/index.mdx b/website/src/content/docs/index.mdx new file mode 100644 index 0000000..bcde4b4 --- /dev/null +++ b/website/src/content/docs/index.mdx @@ -0,0 +1,52 @@ +--- +title: emulate +description: Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation. Not mocks. +template: splash +hero: + tagline: Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation — not mocks. + actions: + - text: Get Started + link: /guides/getting-started/ + icon: right-arrow + - text: View on GitHub + link: https://github.com/pleaseai/emulate + icon: external + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components' + +## Why emulate? + + + + Response JSON field names, formats, and error shapes match the real + services exactly. Point your SDK at a different base URL and everything + else stays the same. + + + A real in-memory store with CRUD, indexing, filtering, and pagination — + state created by one API call is visible to the next. Not canned mocks. + + + No network, no external accounts, no flakes. Seed deterministic fixtures + from a YAML config and reset between tests in milliseconds. + + + Kakao, Naver, and Toss Payments alongside Firebase, Supabase, Asana, + Linear, GitLab, PostHog, Spotify, WorkOS, X, and more. + + + +## Quick start + +```bash +npx @pleaseai/emulate +``` + +Every service starts with sensible defaults — no config file needed. Ports are +assigned sequentially from `4000` (Kakao) through `4012` (X). + +Built on the architecture of +[vercel-labs/emulate](https://github.com/vercel-labs/emulate), using +[`@emulators/core`](https://www.npmjs.com/package/@emulators/core). diff --git a/website/src/content/docs/reference/architecture.md b/website/src/content/docs/reference/architecture.md new file mode 100644 index 0000000..317e231 --- /dev/null +++ b/website/src/content/docs/reference/architecture.md @@ -0,0 +1,76 @@ +--- +title: Architecture +description: How the emulate monorepo is structured — the core plugin system, in-memory store, and shared middleware. +sidebar: + order: 1 +--- + +``` +packages/ + emulate/ # @pleaseai/emulate — CLI (commander) + programmatic API + kakao/ # @pleaseai/emulate-kakao — kauth OAuth + kapi user/talk + naver/ # @pleaseai/emulate-naver — Naver Login OAuth + profile + toss-payments/ # @pleaseai/emulate-toss-payments — payments + webhooks + firebase/ # @pleaseai/emulate-firebase — Auth, Secure Token, FCM v1 + supabase/ # @pleaseai/emulate-supabase — GoTrue + PostgREST + asana/ # @pleaseai/emulate-asana — REST API v1.0 + linear/ # @pleaseai/emulate-linear — GraphQL (read-only) + autumn/ # @pleaseai/emulate-autumn — billing + gitlab/ # @pleaseai/emulate-gitlab — GraphQL with introspection + posthog/ # @pleaseai/emulate-posthog — capture + read API + OAuth + spotify/ # @pleaseai/emulate-spotify — token + catalog + workos/ # @pleaseai/emulate-workos — User Management + Vault + x/ # @pleaseai/emulate-x — X API v2 +docs/ + EMULATOR-CONVENTIONS.md # guide for adding new emulators +website/ # this documentation site (Astro Starlight) +``` + +Every service emulator follows the `@emulators/*` patterns from +[vercel-labs/emulate](https://github.com/vercel-labs/emulate) and depends on +[`@emulators/core`](https://www.npmjs.com/package/@emulators/core) from npm. +The runtime and package manager is [bun](https://bun.sh); task orchestration +is Turborepo; builds are tsup. + +## Core + +`@emulators/core` provides a generic `Store` with typed `Collection` +instances supporting CRUD, indexing, filtering, and pagination. Each service +plugin registers its routes with the shared internal app (Hono-style +handlers) and uses the store for state. + +## Plugin system + +Each service is a `ServicePlugin` that: + +1. Defines its entity types and store collections (`entities.ts`, `store.ts`) +2. Registers HTTP routes with the shared internal app (`routes/*.ts`) +3. Provides a `seed()` for minimal defaults and a `seedFromConfig()` that + consumes its section of `emulate.config.yaml` +4. Uses shared middleware for auth, error handling, and webhooks + +The CLI's registry (`packages/emulate/src/registry.ts`) lazily loads each +plugin, so starting one service does not import the other twelve. Multiple +services run simultaneously, each on its own port (auto-incremented from the +base port). + +## In-memory state + +All state is held in memory with no database. This makes the emulators fast, +easy to reset, and ideal for CI pipelines. State is populated from the seed +config on startup and mutated by API calls during a test run; `reset()` +restores the seeded snapshot. + +## Webhooks + +Core ships a `WebhookDispatcher`. Services dispatch events with +`webhooks.dispatch(event, undefined, payload, "")`, and seed configs +can register subscriptions declaratively (`webhooks: [{ url, events }]`). + +## Fidelity rules + +- Response JSON field names and formats match the real service exactly + (snake_case/camelCase per service). +- Error responses follow each real service's error format. +- Intentional divergences (unsigned JWTs, no Supabase RLS) are documented in + [Authentication](/guides/authentication/). diff --git a/website/src/content/docs/reference/conventions.md b/website/src/content/docs/reference/conventions.md new file mode 100644 index 0000000..bf5f410 --- /dev/null +++ b/website/src/content/docs/reference/conventions.md @@ -0,0 +1,182 @@ +--- +title: Emulator Conventions +description: Package layout and core patterns for adding a new service emulator. +sidebar: + order: 2 +--- + + +Every service emulator follows the `@emulators/*` patterns from +[vercel-labs/emulate](https://github.com/vercel-labs/emulate) and uses +`@emulators/core@0.6.0` as an npm dependency. + +## Package layout + +``` +packages// + package.json # @pleaseai/emulate-, includes the bun export condition + tsconfig.json # extends ../../tsconfig.base.json + tsup.config.ts + src/ + entities.ts # types extending core Entity + store.ts # getXxxStore(store) — namespaced collections + helpers.ts # service-specific error/response helpers + index.ts # exports plugin + seedFromConfig + routes/*.ts # route registration functions taking RouteContext + __tests__/*.test.ts # bun:test tests +``` + +## Core patterns + +### entities.ts + +Every entity extends core `Entity` (`id: number`, `created_at`, `updated_at` — +assigned automatically on insert). Externally visible service IDs live in a +separate field (`uuid`, etc.). + +```ts +import type { Entity } from "@emulators/core"; + +export interface KakaoUser extends Entity { + user_id: number; // Kakao member number (the externally exposed id) + nickname: string; + email: string | null; +} +``` + +### store.ts + +```ts +import { Store, type Collection } from "@emulators/core"; + +export interface KakaoStore { + users: Collection; + apps: Collection; +} + +export function getKakaoStore(store: Store): KakaoStore { + return { + users: store.collection("kakao.users", ["user_id", "email"]), + apps: store.collection("kakao.apps", ["client_id"]), + }; +} +``` + +- Collection names are namespaced as `.`. +- The second argument lists index fields — only the fields queried via + `findBy`/`findOneBy`. +- Re-requesting an existing collection with different indexes throws, so + indexes are defined exclusively in store.ts. + +### routes/*.ts + +```ts +import type { RouteContext } from "@emulators/core"; +import { getKakaoStore } from "../store.js"; + +export function userRoutes(ctx: RouteContext): void { + const { app, store, webhooks, baseUrl } = ctx; + const ks = () => getKakaoStore(store); + + app.get("/v2/user/me", (c) => { + // c.req.param("id"), c.req.query("key"), await c.req.json(), c.req.header("Authorization") + return c.json({ ... }); + }); +} +``` + +- Reproduce the real service's response JSON field names and formats exactly + (snake_case/camelCase per the real service). +- Error responses also follow the real service's format (implemented as + helpers in each service's helpers.ts). + +### index.ts + +```ts +export const kakaoPlugin: ServicePlugin = { + name: "kakao", + register(app, store, webhooks, baseUrl, tokenMap) { + const ctx: RouteContext = { app, store, webhooks, baseUrl, tokenMap }; + oauthRoutes(ctx); + userRoutes(ctx); + }, + seed(store, baseUrl) { + // Default seed (minimal data so the emulator works without a seed config) + }, +}; + +export function seedFromConfig(store: Store, baseUrl: string, config: KakaoSeedConfig, webhooks?: WebhookDispatcher): void { + // Receives the service section of emulate.config.yaml. Must be idempotent + // (findOneBy then skip), and config values win over plugin.seed() defaults. +} + +export default kakaoPlugin; +``` + +The config shape accepted by `seedFromConfig` must match the service's +`initConfig` in the CLI registry (`packages/emulate/src/registry.ts`). + +## Authentication + +- Core's `authMiddleware` is registered globally but **passes through when no + Authorization header is present**. Service-specific auth (Bearer token + validation, Basic auth, apikey headers, etc.) is implemented directly in + routes/helpers. +- OAuth access tokens are stored in a service store collection + (`.tokens`, etc.) and looked up in routes. +- Core provides `constantTimeSecretEqual`, `normalizeUri`, + `matchesRedirectUri`, `parseCookies`, and `bodyStr` for OAuth implementations. + +## OAuth login page + +`renderCardPage` (a core export) renders a user-picker login page. +The authorize endpoint shows the seeded users and, on selection, issues a code +and 302s to the redirect_uri. Also provide an instant-approval path via a +`?user=` query for automation (tests/CI). + +## Webhooks + +Dispatch with `webhooks.dispatch(event, undefined, payload, "")`. +Subscribe with `webhooks.register({ url, events, active: true, owner: "" })`. +If the seed config accepts `webhooks: [{ url, events }]`, register them in +seedFromConfig. + +## Tests (bun:test) + +Send Requests directly through `app.fetch` without binding a port: + +```ts +import { describe, it, expect, beforeEach } from "bun:test"; +import { createServer } from "@emulators/core"; +import { kakaoPlugin } from "../index.js"; + +function makeApp() { + const { app, store } = createServer(kakaoPlugin, { port: 4000 }); + kakaoPlugin.seed?.(store, "http://localhost:4000"); + return { app, store }; +} + +it("issues token for authorization code", async () => { + const { app } = makeApp(); + const res = await app.fetch( + new Request("http://localhost:4000/oauth/token", { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ grant_type: "authorization_code", code, client_id }), + }), + ); + expect(res.status).toBe(200); +}); +``` + +- Cover the happy path plus the main error paths (bad code/token/key, missing + required fields, 404s). +- No external network access — everything stays in memory. + +## Style + +- ESM with `.js` extension imports (`./store.js`). +- One route group per file. Response formatting functions (`formatXxx`) go at + the bottom of the route file. +- Comment only non-obvious behavior (places that intentionally diverge from + the real service). diff --git a/website/src/content/docs/services/asana.md b/website/src/content/docs/services/asana.md new file mode 100644 index 0000000..a4d1a23 --- /dev/null +++ b/website/src/content/docs/services/asana.md @@ -0,0 +1,117 @@ +--- +title: Asana +description: "Emulated Asana REST API (v1.0) for local development and testing." +sidebar: + label: Asana + order: 6 +--- + +:::note +Default port **4005** · Package [`@pleaseai/emulate-asana`](https://www.npmjs.com/package/@pleaseai/emulate-asana) +::: + +A stateful, in-memory Asana REST API (v1.0) emulator. + +Included now: + +- Users, workspaces, teams, projects, sections, tasks, tags, stories, webhooks +- Subtasks, task dependencies/dependents, followers, project/section membership +- Bearer-token auth and offset-based pagination (`limit` 1–100, `offset`) + +Follow-up work will add OAuth 2.0 and an inspector UI. + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service asana + +# Or from the published package +npx @pleaseai/emulate --service asana +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (asana's slot is `4005`). + +Default URL (asana alone): + +```text +http://localhost:4000 +``` + +## Auth + +All `/api/1.0/*` routes require a bearer token. Any token works — an unmatched +token resolves to the first seeded user, so `me` keywords work out of the box. + +```bash +curl http://localhost:4000/api/1.0/users/me \ + -H "Authorization: Bearer test-token" +``` + +Map specific tokens to users via the top-level `tokens` key in the config: + +```yaml +tokens: + test-token: + login: dev@example.com # matches a seeded user's email, gid, or name + scopes: [] +``` + +## Conventions + +- Write bodies are wrapped in `data`: `{"data": {"name": "My Task"}}`. +- Successful responses are wrapped in `data`; lists add a `next_page` cursor. +- Errors return `{"errors": [{"message": "..."}]}`. + +## Example + +```bash +# Create a task (assigned to the authenticated user) +curl -X POST http://localhost:4000/api/1.0/tasks \ + -H "Authorization: Bearer test-token" \ + -H "Content-Type: application/json" \ + -d '{"data":{"name":"Write the README","projects":[""],"assignee":"me"}}' + +# List tasks in a project +curl "http://localhost:4000/api/1.0/tasks?project=&limit=50" \ + -H "Authorization: Bearer test-token" +``` + +## Seed Config + +Add an `asana:` section to `emulate.config.yaml` (or pass `--seed `). +Inter-entity references are resolved by name; the workspace defaults to the +first one when omitted. + +```yaml +asana: + workspaces: + - name: My Workspace + is_organization: true + users: + - name: Developer + email: dev@example.com + teams: + - name: Engineering + workspace: My Workspace + projects: + - name: My Project + workspace: My Workspace + team: Engineering + owner: Developer + sections: + - name: To Do + project: My Project + tags: + - name: urgent + workspace: My Workspace + color: red + tasks: + - name: Example Task + project: My Project + section: To Do + assignee: Developer + due_on: 2026-01-31 +``` diff --git a/website/src/content/docs/services/autumn.md b/website/src/content/docs/services/autumn.md new file mode 100644 index 0000000..3d2e4f3 --- /dev/null +++ b/website/src/content/docs/services/autumn.md @@ -0,0 +1,91 @@ +--- +title: Autumn +description: "Emulated Autumn billing API for local development and testing." +sidebar: + label: Autumn + order: 8 +--- + +:::note +Default port **4007** · Package [`@pleaseai/emulate-autumn`](https://www.npmjs.com/package/@pleaseai/emulate-autumn) +::: + +Implements the v1 RPC-style API autumn-js speaks (`/v1/.`). + +Included now: + +- `customers.get_or_create`, `customers.update`, `customers.list` +- `balances.track` and `balances.check` +- `plans.list` with per-customer eligibility (trials, upgrades, downgrades) +- `billing.attach` and `billing.open_customer_portal` +- A hosted checkout page that settles paid plans and card-required trials + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service autumn + +# Or from the published package +npx @pleaseai/emulate --service autumn +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port. + +## Auth + +Any `am_`-prefixed bearer token is accepted as the secret key. + +```bash +curl -X POST http://localhost:4000/v1/customers.get_or_create \ + -H "Authorization: Bearer am_test_emulate" \ + -H "Content-Type: application/json" \ + -d '{"customer_id": "org_demo"}' +``` + +With the SDK: + +```ts +import { Autumn } from 'autumn-js' + +const autumn = new Autumn({ secretKey: 'am_test_emulate', serverURL: 'http://localhost:4000' }) +``` + +## Check Example + +```bash +curl -X POST http://localhost:4000/v1/balances.check \ + -H "Authorization: Bearer am_test_emulate" \ + -H "Content-Type: application/json" \ + -d '{"customer_id": "org_demo", "feature_id": "executions"}' +``` + +## Seed Config + +Add an `autumn:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +autumn: + plans: + - id: free + name: Free + auto_enable: true + items: + - feature_id: executions + included: 100 + - id: pro + name: Pro + price: + amount: 20 + interval: month + items: + - feature_id: executions + included: 10000 + customers: + - id: org_demo + subscriptions: + - plan_id: pro + status: active +``` diff --git a/website/src/content/docs/services/firebase.md b/website/src/content/docs/services/firebase.md new file mode 100644 index 0000000..3b0150a --- /dev/null +++ b/website/src/content/docs/services/firebase.md @@ -0,0 +1,92 @@ +--- +title: Firebase +description: "Emulated Firebase Auth (Identity Toolkit + Secure Token) and FCM v1 for local development and testing." +sidebar: + label: Firebase + order: 4 +--- + +:::note +Default port **4003** · Package [`@pleaseai/emulate-firebase`](https://www.npmjs.com/package/@pleaseai/emulate-firebase) +::: + +A stateful emulator for Firebase Authentication and Cloud Messaging. + +Included now: + +- Identity Toolkit REST: `signUp`, `signInWithPassword`, `lookup`, `update`, + `delete`, `sendOobCode` +- Secure Token: refresh ID tokens (`POST /v1/token`) +- FCM v1: `POST /v1/projects//messages:send` + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service firebase + +# Or from the published package +npx @pleaseai/emulate --service firebase +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (firebase's slot is `4003`). + +Point a Firebase SDK at the emulator with +`FIREBASE_AUTH_EMULATOR_HOST=localhost:4000`. + +## Auth + +- Identity Toolkit and Secure Token take the API key as a query param: + `?key=`. +- FCM `messages:send` requires `Authorization: Bearer ` (presence only — + the token is not cryptographically verified). + +The `googleapis.com`-prefixed path variants are also accepted, e.g. +`/identitytoolkit.googleapis.com/v1/accounts:signUp` and +`/securetoken.googleapis.com/v1/token`. + +## Flow + +```bash +# 1. Sign up with email + password +curl -X POST "http://localhost:4000/v1/accounts:signUp?key=firebase_api_key_example" \ + -H "Content-Type: application/json" \ + -d '{"email":"hong@example.com","password":"password123","returnSecureToken":true}' +# → {"idToken":"...","refreshToken":"...","expiresIn":"3600","localId":"..."} + +# 2. Sign in with email + password +curl -X POST "http://localhost:4000/v1/accounts:signInWithPassword?key=firebase_api_key_example" \ + -H "Content-Type: application/json" \ + -d '{"email":"hong@example.com","password":"password123","returnSecureToken":true}' + +# 3. Refresh the ID token +curl -X POST "http://localhost:4000/v1/token?key=firebase_api_key_example" \ + -H "Content-Type: application/json" \ + -d '{"grant_type":"refresh_token","refresh_token":""}' + +# 4. Send an FCM message +curl -X POST "http://localhost:4000/v1/projects/demo-project/messages:send" \ + -H "Authorization: Bearer test-token" \ + -H "Content-Type: application/json" \ + -d '{"message":{"token":"","notification":{"title":"Hi","body":"World"}}}' +``` + +Inspect emulator state (no auth) at `GET /internal/messages` and +`GET /internal/oob_codes`. + +## Seed Config + +Add a `firebase:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +firebase: + projects: + - project_id: demo-project + api_key: firebase_api_key_example + users: + - email: hong@example.com + password: password123 + display_name: 홍길동 +``` diff --git a/website/src/content/docs/services/gitlab.md b/website/src/content/docs/services/gitlab.md new file mode 100644 index 0000000..dfab051 --- /dev/null +++ b/website/src/content/docs/services/gitlab.md @@ -0,0 +1,53 @@ +--- +title: GitLab +description: "Emulated GitLab GraphQL API for local development and testing." +sidebar: + label: GitLab + order: 9 +--- + +:::note +Default port **4008** · Package [`@pleaseai/emulate-gitlab`](https://www.npmjs.com/package/@pleaseai/emulate-gitlab) +::: + +Serves `POST /api/graphql` backed by the real GitLab schema SDL. + +Included now: + +- `POST /api/graphql` +- Full GraphQL schema introspection against the real GitLab SDL +- Query validation against the schema + +Query resolvers and seed data are follow-up work — today the emulator is +enough for clients that build against the schema (codegen, IDE tooling, +connection smoke tests). + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service gitlab + +# Or from the published package +npx @pleaseai/emulate --service gitlab +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port. + +## Query Example + +```bash +curl http://localhost:4000/api/graphql \ + -H "Content-Type: application/json" \ + -d '{"query":"{ __schema { queryType { name } } }"}' +``` + +## Seed Config + +The GitLab emulator takes no seed data yet: + +```yaml +gitlab: {} +``` diff --git a/website/src/content/docs/services/kakao.md b/website/src/content/docs/services/kakao.md new file mode 100644 index 0000000..b0bf9ca --- /dev/null +++ b/website/src/content/docs/services/kakao.md @@ -0,0 +1,81 @@ +--- +title: Kakao +description: "Emulated Kakao API (kauth OAuth + kapi user/talk) for local development and testing." +sidebar: + label: Kakao + order: 1 +--- + +:::note +Default port **4000** · Package [`@pleaseai/emulate-kakao`](https://www.npmjs.com/package/@pleaseai/emulate-kakao) +::: + +A stateful emulator for Kakao's `kauth` (OAuth 2.0) and `kapi` (user/talk) APIs. + +Included now: + +- OAuth 2.0 authorize + token (authorization_code and refresh_token grants) +- User API: `/v2/user/me`, access-token info, logout, unlink +- KakaoTalk self-memo send (`/v2/api/talk/memo/default/send`) + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service kakao + +# Or from the published package +npx @pleaseai/emulate --service kakao +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (kakao's slot is `4000`). + +## Auth + +- OAuth token exchange authenticates with `client_id` (and `client_secret` if + the app is configured with one). +- `kapi` calls (user/talk) use `Authorization: Bearer `. + +## Flow + +```bash +# 1. Authorization code — in CI, ?user_id= auto-approves without the login page +curl -i "http://localhost:4000/oauth/authorize?client_id=kakao_rest_api_key_example\ +&redirect_uri=http://localhost:3000/api/auth/callback/kakao&response_type=code&user_id=1001" +# → redirect with Location: .../callback?code= + +# 2. Exchange the code for tokens +curl -X POST http://localhost:4000/oauth/token \ + -d "grant_type=authorization_code&client_id=kakao_rest_api_key_example&code=\ +&redirect_uri=http://localhost:3000/api/auth/callback/kakao" +# → {"token_type":"bearer","access_token":"...","refresh_token":"...","expires_in":21599} + +# 3. Fetch the user profile +curl http://localhost:4000/v2/user/me -H "Authorization: Bearer " + +# 4. Send a KakaoTalk self-memo (template_object is a JSON-encoded string) +curl -X POST http://localhost:4000/v2/api/talk/memo/default/send \ + -H "Authorization: Bearer " \ + -d 'template_object={"object_type":"text","text":"Hello from the emulator"}' +``` + +Inspect sent memos (no auth) at `GET /internal/talk/memos`. + +## Seed Config + +Add a `kakao:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +kakao: + apps: + - client_id: kakao_rest_api_key_example + client_secret: kakao_client_secret_example + redirect_uris: [http://localhost:3000/api/auth/callback/kakao] + users: + - user_id: 1001 + nickname: 홍길동 + email: hong@example.com + profile_image_url: https://k.kakaocdn.net/dn/profile.jpg +``` diff --git a/website/src/content/docs/services/linear.md b/website/src/content/docs/services/linear.md new file mode 100644 index 0000000..ec936f4 --- /dev/null +++ b/website/src/content/docs/services/linear.md @@ -0,0 +1,96 @@ +--- +title: Linear +description: "Emulated Linear GraphQL API for local development and testing." +sidebar: + label: Linear + order: 7 +--- + +:::note +Default port **4006** · Package [`@pleaseai/emulate-linear`](https://www.npmjs.com/package/@pleaseai/emulate-linear) +::: + +Phase 1 provides a read only Linear GraphQL emulator. + +Included now: + +- `POST /graphql` +- GraphQL schema introspection +- PAT authentication with `Authorization: ` +- Query resolvers for `Issue`, `Project`, `Team`, `User`, `Organization`, `Label`, and `WorkflowState` +- Relay style pagination with `edges`, `nodes`, and `pageInfo` + +Follow up work will add mutations, webhooks, OAuth 2.0, and an inspector UI. + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service linear + +# Or from the published package +npx @pleaseai/emulate --service linear +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it, e.g. `--service linear -p 4012`. When started alongside other +services, ports are assigned sequentially from the base port. + +Default URL (linear alone): + +```text +http://localhost:4000 +``` + +## Auth + +Use a seeded Linear API key as the raw `Authorization` header value. + +```bash +curl http://localhost:4000/graphql \ + -H "Authorization: lin_api_test" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ viewer { id name email } }"}' +``` + +## Query Example + +```bash +curl http://localhost:4000/graphql \ + -H "Authorization: lin_api_test" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ issues(first: 10) { nodes { id identifier title state { name } team { key } } pageInfo { hasNextPage endCursor } } }"}' +``` + +## Seed Config + +Add a `linear:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +linear: + api_keys: [lin_api_test] + organizations: + - id: org-1 + name: My Org + teams: + - id: team-1 + name: Engineering + key: ENG + organization: org-1 + workflow_states: + - id: ws-1 + name: Todo + type: unstarted + team: team-1 + users: + - id: user-1 + name: Developer + email: dev@example.com + organization: org-1 + issues: + - id: issue-1 + title: First issue + team: team-1 + state: ws-1 + assignee: user-1 +``` diff --git a/website/src/content/docs/services/naver.md b/website/src/content/docs/services/naver.md new file mode 100644 index 0000000..4b7d9a0 --- /dev/null +++ b/website/src/content/docs/services/naver.md @@ -0,0 +1,86 @@ +--- +title: Naver +description: "Emulated Naver API (nid OAuth + openapi profile) for local development and testing." +sidebar: + label: Naver + order: 2 +--- + +:::note +Default port **4001** · Package [`@pleaseai/emulate-naver`](https://www.npmjs.com/package/@pleaseai/emulate-naver) +::: + +A stateful emulator for Naver Login (`nid`) OAuth 2.0 and the profile openapi. + +Included now: + +- OAuth 2.0 token endpoint: issue (`authorization_code`), `refresh_token`, + and `delete` (token revocation) grants +- Profile API `GET /v1/nid/me` and token check `GET /v1/nid/verify` + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service naver + +# Or from the published package +npx @pleaseai/emulate --service naver +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (naver's slot is `4001`). + +## Auth + +- OAuth token operations authenticate with `client_id` (and `client_secret` + if configured). +- Profile calls use `Authorization: Bearer `. +- OAuth/token errors are returned with HTTP 200 and an + `{ "error", "error_description" }` body (Naver convention). + +## Flow + +```bash +# 1. Authorization code — in CI, ?user= auto-approves without the login page +curl -i "http://localhost:4000/oauth2.0/authorize?response_type=code\ +&client_id=naver_client_id_example&redirect_uri=http://localhost:3000/api/auth/callback/naver\ +&state=abc123&user=" +# → redirect with Location: .../callback?code=&state=abc123 + +# 2. Exchange the code for tokens +curl -X POST http://localhost:4000/oauth2.0/token \ + -d "grant_type=authorization_code&client_id=naver_client_id_example\ +&client_secret=naver_client_secret_example&code=&state=abc123" +# → {"access_token":"...","refresh_token":"...","token_type":"bearer","expires_in":"3600"} + +# 3. Fetch the user profile +curl http://localhost:4000/v1/nid/me -H "Authorization: Bearer " + +# 4. Revoke the token +curl -X POST http://localhost:4000/oauth2.0/token \ + -d "grant_type=delete&client_id=naver_client_id_example\ +&access_token=&service_provider=NAVER" +``` + +The token endpoint accepts both `GET` query params and `POST` form bodies. + +## Seed Config + +Add a `naver:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +naver: + apps: + - client_id: naver_client_id_example + client_secret: naver_client_secret_example + callback_urls: [http://localhost:3000/api/auth/callback/naver] + users: + - name: 홍길동 + nickname: gildong + email: hong@example.com + gender: M + birthyear: '1990' + mobile: 010-1234-5678 +``` diff --git a/website/src/content/docs/services/posthog.md b/website/src/content/docs/services/posthog.md new file mode 100644 index 0000000..317462b --- /dev/null +++ b/website/src/content/docs/services/posthog.md @@ -0,0 +1,71 @@ +--- +title: PostHog +description: "Emulated PostHog analytics API for local development and testing." +sidebar: + label: PostHog + order: 10 +--- + +:::note +Default port **4009** · Package [`@pleaseai/emulate-posthog`](https://www.npmjs.com/package/@pleaseai/emulate-posthog) +::: + +Included now: + +- Event capture and per-project event reads (`/api/projects/:id/events`) +- `/api/projects` and `/api/users/@me` +- OAuth 2.0: dynamic client registration (`/oauth/register`), authorize/token +- RFC 8414 and OIDC discovery documents +- OpenAPI subset at `/openapi.json` + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service posthog + +# Or from the published package +npx @pleaseai/emulate --service posthog +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port. + +## Auth + +Use a seeded project API token (or a personal API key) as a bearer token. + +```bash +curl http://localhost:4000/api/projects \ + -H "Authorization: Bearer phc_test_token" +``` + +## Read Example + +```bash +curl "http://localhost:4000/api/projects/1/events" \ + -H "Authorization: Bearer phc_test_token" +``` + +## Seed Config + +Add a `posthog:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +posthog: + users: + - email: dev@example.com + name: Developer + projects: + - name: Demo Project + api_token: phc_test_token + events: + - event: $pageview + distinct_id: user-1 + oauth_clients: + - client_id: posthog-client + client_secret: posthog-secret + redirect_uris: + - http://localhost:3000/callback +``` diff --git a/website/src/content/docs/services/spotify.md b/website/src/content/docs/services/spotify.md new file mode 100644 index 0000000..a319997 --- /dev/null +++ b/website/src/content/docs/services/spotify.md @@ -0,0 +1,71 @@ +--- +title: Spotify +description: "Emulated Spotify Web API for local development and testing." +sidebar: + label: Spotify + order: 11 +--- + +:::note +Default port **4010** · Package [`@pleaseai/emulate-spotify`](https://www.npmjs.com/package/@pleaseai/emulate-spotify) +::: + +Models the canonical OAuth 2.0 client credentials provider: an app-only token +(no user) minted from a `client_id`/`client_secret`, reaching the public catalog. + +Included now: + +- `POST /api/token` (client credentials; form body or HTTP Basic) +- `GET /v1/search` with `q` and `type=artist,album,track` +- `GET /v1/artists/:id`, `/v1/artists/:id/albums`, `/v1/albums/:id`, `/v1/tracks/:id` +- OpenAPI subset at `/openapi.json` + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service spotify + +# Or from the published package +npx @pleaseai/emulate --service spotify +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port. + +## Auth + +```bash +curl -X POST http://localhost:4000/api/token \ + -d grant_type=client_credentials \ + -d client_id=spotify_client_id_example \ + -d client_secret=spotify_client_secret_example +``` + +## Catalog Example + +```bash +curl "http://localhost:4000/v1/search?q=daft&type=artist" \ + -H "Authorization: Bearer " +``` + +## Seed Config + +Add a `spotify:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +spotify: + clients: + - client_id: spotify_client_id_example + client_secret: spotify_client_secret_example + name: Demo App + artists: + - name: Daft Punk + genres: [electronic] + albums: + - name: Discovery + release_date: 2001-03-12 + tracks: + - name: One More Time +``` diff --git a/website/src/content/docs/services/supabase.md b/website/src/content/docs/services/supabase.md new file mode 100644 index 0000000..a54e44f --- /dev/null +++ b/website/src/content/docs/services/supabase.md @@ -0,0 +1,110 @@ +--- +title: Supabase +description: "Emulated Supabase (GoTrue Auth + PostgREST) for local development and testing." +sidebar: + label: Supabase + order: 5 +--- + +:::note +Default port **4004** · Package [`@pleaseai/emulate-supabase`](https://www.npmjs.com/package/@pleaseai/emulate-supabase) +::: + +A stateful emulator for Supabase's GoTrue auth and PostgREST data API. + +Included now: + +- GoTrue auth: signup, password/refresh-token grants, get/update user, logout, + password recovery +- PostgREST: table CRUD (`GET`/`POST`/`PATCH`/`DELETE`) with filters, ordering, + pagination, and column selection + +RLS is not emulated — `anon_key` and `service_role_key` get identical access. + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service supabase + +# Or from the published package +npx @pleaseai/emulate --service supabase +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (supabase's slot is `4004`). + +Point a Supabase client at the emulator with +`createClient("http://localhost:4000", anonKey)`. + +## Auth + +Pass the project key as either an `apikey` header or `Authorization: Bearer +`. The key must match the configured `anon_key` or `service_role_key`. +User-scoped routes (`GET/PUT /auth/v1/user`, logout) take the user's +`access_token` as the bearer token. + +## Flow + +```bash +# 1. Sign up (returns a session immediately — auto-confirmed) +curl -X POST http://localhost:4000/auth/v1/signup \ + -H "apikey: supabase_anon_key_example" \ + -H "Content-Type: application/json" \ + -d '{"email":"hong@example.com","password":"password123","data":{"name":"홍길동"}}' +# → {"access_token":"...","refresh_token":"...","token_type":"bearer","expires_in":3600,"user":{...}} + +# 2. Log in (grant_type=password) +curl -X POST "http://localhost:4000/auth/v1/token?grant_type=password" \ + -H "apikey: supabase_anon_key_example" \ + -H "Content-Type: application/json" \ + -d '{"email":"hong@example.com","password":"password123"}' + +# 3. Get the current user +curl http://localhost:4000/auth/v1/user \ + -H "apikey: supabase_anon_key_example" \ + -H "Authorization: Bearer " +``` + +## PostgREST + +Filters use `column=op.value`; multiple params are AND-ed. Operators: `eq`, +`neq`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`, `is`, `in`. Reserved params: +`select`, `order`, `limit`, `offset`. + +```bash +# Insert a row (Prefer: return=representation echoes the inserted row) +curl -X POST http://localhost:4000/rest/v1/todos \ + -H "apikey: supabase_anon_key_example" \ + -H "Content-Type: application/json" \ + -H "Prefer: return=representation" \ + -d '{"title":"장보기","completed":false}' + +# Query with filter, order, limit, and column selection +curl "http://localhost:4000/rest/v1/todos?completed=is.false&order=id.asc&limit=10&select=id,title" \ + -H "apikey: supabase_anon_key_example" + +# Update rows matching a filter +curl -X PATCH "http://localhost:4000/rest/v1/todos?id=eq.1" \ + -H "apikey: supabase_service_role_key_example" \ + -H "Content-Type: application/json" \ + -d '{"completed":true}' +``` + +## Seed Config + +Add a `supabase:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +supabase: + anon_key: supabase_anon_key_example + service_role_key: supabase_service_role_key_example + users: + - email: hong@example.com + password: password123 + tables: + todos: + - {id: 1, title: 장보기, completed: false} + - {id: 2, title: 청소하기, completed: true} +``` diff --git a/website/src/content/docs/services/tosspayments.md b/website/src/content/docs/services/tosspayments.md new file mode 100644 index 0000000..bcde3b1 --- /dev/null +++ b/website/src/content/docs/services/tosspayments.md @@ -0,0 +1,87 @@ +--- +title: Toss Payments +description: "Emulated Toss Payments API for local development and testing." +sidebar: + label: Toss Payments + order: 3 +--- + +:::note +Default port **4002** · Package [`@pleaseai/emulate-toss-payments`](https://www.npmjs.com/package/@pleaseai/emulate-toss-payments) +::: + +A stateful emulator for the Toss Payments REST API. + +Included now: + +- Payment confirm / lookup (by paymentKey or orderId) / cancel (incl. partial) +- Checkout page simulation (`/checkout` → `/checkout/approve`) +- An `/internal/payments` helper to create a payment without the widget +- `PAYMENT_STATUS_CHANGED` webhooks on confirm and cancel + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service tosspayments + +# Or from the published package +npx @pleaseai/emulate --service tosspayments +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port (tosspayments' slot is `4002`). + +## Auth + +Payment API routes use HTTP Basic auth with the secret key as the username and +an empty password — `Authorization: Basic base64(":")` (note the +trailing colon). The checkout pages and `/internal/payments` helper need no auth. + +```bash +# Build the header value for test_sk_example +echo -n 'test_sk_example:' | base64 # → dGVzdF9za19leGFtcGxlOg== +``` + +## Flow + +```bash +# 1. Create a payment (replaces the payment-widget step) +curl -X POST http://localhost:4000/internal/payments \ + -H "Content-Type: application/json" \ + -d '{"orderId":"order-1","orderName":"Test order","amount":11000}' +# → {"paymentKey":"", ...} + +# 2. Confirm it +curl -X POST http://localhost:4000/v1/payments/confirm \ + -H "Authorization: Basic $(echo -n 'test_sk_example:' | base64)" \ + -H "Content-Type: application/json" \ + -d '{"paymentKey":"","orderId":"order-1","amount":11000}' + +# 3. Look it up (by paymentKey or by orderId) +curl http://localhost:4000/v1/payments/ \ + -H "Authorization: Basic $(echo -n 'test_sk_example:' | base64)" +curl http://localhost:4000/v1/payments/orders/order-1 \ + -H "Authorization: Basic $(echo -n 'test_sk_example:' | base64)" + +# 4. Cancel it (omit cancelAmount for a full cancel) +curl -X POST http://localhost:4000/v1/payments//cancel \ + -H "Authorization: Basic $(echo -n 'test_sk_example:' | base64)" \ + -H "Content-Type: application/json" \ + -d '{"cancelReason":"User requested","cancelAmount":11000}' +``` + +Confirm and cancel dispatch a `PAYMENT_STATUS_CHANGED` webhook to any configured +webhook URLs. + +## Seed Config + +Add a `tosspayments:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +tosspayments: + merchants: + - client_key: test_ck_example + secret_key: test_sk_example +``` diff --git a/website/src/content/docs/services/workos.md b/website/src/content/docs/services/workos.md new file mode 100644 index 0000000..50182b6 --- /dev/null +++ b/website/src/content/docs/services/workos.md @@ -0,0 +1,70 @@ +--- +title: WorkOS +description: "Emulated WorkOS API for local development and testing." +sidebar: + label: WorkOS + order: 12 +--- + +:::note +Default port **4011** · Package [`@pleaseai/emulate-workos`](https://www.npmjs.com/package/@pleaseai/emulate-workos) +::: + +Included now: + +- User Management: authenticate, users, organization memberships, invitations, user API keys +- Organizations and organization domains +- API key validation (`/api_keys/validations`) +- OAuth 2.0 / OIDC: hosted authorize pages, dynamic client registration, + token exchange (RFC 8693 id-jag), JWKS, discovery metadata +- Vault KV (`/vault/v1/kv`) +- Admin portal link generation + +JWTs are signed with a real key (via `jose`) and verify against `/oauth2/jwks`. + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service workos + +# Or from the published package +npx @pleaseai/emulate --service workos +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port. + +## Auth + +Any `sk_`-prefixed bearer token is accepted as the API key. + +```ts +import { WorkOS } from '@workos-inc/node' + +const workos = new WorkOS('sk_test_emulate', { apiHostname: 'localhost', port: 4000, https: false }) +``` + +```bash +curl http://localhost:4000/user_management/organization_memberships \ + -H "Authorization: Bearer sk_test_emulate" +``` + +## Seed Config + +Add a `workos:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +workos: + users: + - email: dev@example.com + first_name: Dev + last_name: Eloper + organizations: + - name: Demo Org + members: + - dev@example.com + oauth: + default_access_token_ttl_seconds: 3600 +``` diff --git a/website/src/content/docs/services/x.md b/website/src/content/docs/services/x.md new file mode 100644 index 0000000..c735911 --- /dev/null +++ b/website/src/content/docs/services/x.md @@ -0,0 +1,73 @@ +--- +title: X (Twitter) +description: "Emulated X (Twitter) API v2 for local development and testing." +sidebar: + label: X (Twitter) + order: 13 +--- + +:::note +Default port **4012** · Package [`@pleaseai/emulate-x`](https://www.npmjs.com/package/@pleaseai/emulate-x) +::: + +Included now: + +- OAuth 2.0 authorization code with PKCE: `/2/oauth2/authorize` (hosted consent + page), `/2/oauth2/token`, `/2/oauth2/revoke` — confidential and public clients +- Tweets: create, delete, lookup, user timelines (`/2/tweets`, `/2/users/:id/tweets`) +- Users: `/2/users/me`, `/2/users/:id`, `/2/users/by/username/:username` +- OpenAPI subset at `/2/openapi.json` + +## Start + +```bash +# From this repo (after `bun install && bun run build`) +bun packages/emulate/dist/index.js --service x + +# Or from the published package +npx @pleaseai/emulate --service x +``` + +A single service starts on the base port (default `4000`). Use `-p ` to +change it. When started alongside other services, ports are assigned +sequentially from the base port. + +## Auth + +Run the PKCE flow against `/2/oauth2/authorize` + `/2/oauth2/token`, then call +the API with the issued token: + +```bash +curl http://localhost:4000/2/users/me \ + -H "Authorization: Bearer " +``` + +## Tweet Example + +```bash +curl -X POST http://localhost:4000/2/tweets \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"text": "Hello from the emulator!"}' +``` + +## Seed Config + +Add an `x:` section to `emulate.config.yaml` (or pass `--seed `): + +```yaml +x: + users: + - username: developer + name: Developer + verified: true + oauth_clients: + - client_id: x_client_id_example + client_secret: x_client_secret_example + client_type: confidential + redirect_uris: + - http://localhost:3000/api/auth/callback/x + tweets: + - text: Hello from the emulator! + author: developer +``` diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 0000000..8bf91d3 --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +} diff --git a/website/wrangler.jsonc b/website/wrangler.jsonc new file mode 100644 index 0000000..0bf5e80 --- /dev/null +++ b/website/wrangler.jsonc @@ -0,0 +1,6 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "emulate-docs", + "compatibility_date": "2026-07-01", + "pages_build_output_dir": "dist" +} From 1a6b359e8161b9dd74befb25cc590e1662a29895 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sat, 4 Jul 2026 18:20:57 +0900 Subject: [PATCH 02/15] fix: make website pass root eslint Ignore website content markdown (like docs/**) and fix curly/regexp lint errors in sync-services.ts. --- eslint.config.ts | 2 +- website/scripts/sync-services.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index ab21d53..de444ba 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -2,7 +2,7 @@ import pleaseai from '@pleaseai/eslint-config' export default pleaseai( { - ignores: ['docs/**'], + ignores: ['docs/**', 'website/src/content/**', 'website/.astro/**', 'website/dist/**'], }, { // The CLI legitimately prints to stdout. diff --git a/website/scripts/sync-services.ts b/website/scripts/sync-services.ts index 1d6fbf3..8469a19 100644 --- a/website/scripts/sync-services.ts +++ b/website/scripts/sync-services.ts @@ -41,13 +41,15 @@ for (const dir of await readdir(SKILLS)) { const raw = await Bun.file(join(SKILLS, dir, 'SKILL.md')).text() const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n/) - if (!fmMatch) throw new Error(`no frontmatter in skills/${dir}/SKILL.md`) - const descMatch = fmMatch[1].match(/^description:\s*(.*)$/m) + if (!fmMatch) { + throw new Error(`no frontmatter in skills/${dir}/SKILL.md`) + } + const descMatch = fmMatch[1].match(/^description: (.*)$/m) const description = firstSentence(descMatch?.[1] ?? '') let body = raw.slice(fmMatch[0].length).trim() // Drop the leading H1 — Starlight renders the frontmatter title instead. - body = body.replace(/^#\s+.*\n+/, '') + body = body.replace(/^# .*\n+/, '') const page = `--- title: ${meta.label} From a7dea74023968978f3c22fb97b755dbb6fbe4cb2 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sat, 4 Jul 2026 18:22:40 +0900 Subject: [PATCH 03/15] chore: address review feedback on docs workflow and sync script - pin bun via packageManager in website/package.json (unambiguous bun-version-file) - extract duplicated toss-payments package-name mapping into a variable - fail CI when generated services/*.md drift from skills/ --- .github/workflows/docs.yml | 4 ++++ website/package.json | 1 + website/scripts/sync-services.ts | 6 +++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3df0da5..a4f1b1a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,6 +24,10 @@ jobs: bun-version-file: package.json - run: bun install --frozen-lockfile - run: bun scripts/sync-services.ts + - name: Check generated service pages are up to date + run: | + git diff --exit-code -- src/content/docs/services \ + || { echo "::error::services/*.md is stale — run 'bun run sync:services' in website/ and commit"; exit 1; } - run: bun run build - name: Deploy to Cloudflare Pages if: github.event_name != 'pull_request' diff --git a/website/package.json b/website/package.json index 7055505..a702285 100644 --- a/website/package.json +++ b/website/package.json @@ -3,6 +3,7 @@ "type": "module", "version": "0.0.1", "private": true, + "packageManager": "bun@1.3.14", "scripts": { "dev": "astro dev", "build": "astro build", diff --git a/website/scripts/sync-services.ts b/website/scripts/sync-services.ts index 8469a19..ca9555f 100644 --- a/website/scripts/sync-services.ts +++ b/website/scripts/sync-services.ts @@ -51,6 +51,10 @@ for (const dir of await readdir(SKILLS)) { // Drop the leading H1 — Starlight renders the frontmatter title instead. body = body.replace(/^# .*\n+/, '') + // npm package names use the directory name under packages/, which differs + // from the skill/service name for tosspayments. + const pkg = `@pleaseai/emulate-${dir === 'tosspayments' ? 'toss-payments' : dir}` + const page = `--- title: ${meta.label} description: ${JSON.stringify(description)} @@ -60,7 +64,7 @@ sidebar: --- :::note -Default port **${meta.port}** · Package [\`@pleaseai/emulate-${dir === 'tosspayments' ? 'toss-payments' : dir}\`](https://www.npmjs.com/package/@pleaseai/emulate-${dir === 'tosspayments' ? 'toss-payments' : dir}) +Default port **${meta.port}** · Package [\`${pkg}\`](https://www.npmjs.com/package/${pkg}) ::: ${body} From e72a13f56785f31a3906b7b38697ee3bb02790e0 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sat, 4 Jul 2026 18:27:44 +0900 Subject: [PATCH 04/15] chore: fix Codacy warnings - pin docs workflow actions to full commit SHAs (repo convention), using mise-action to provision bun like ci.yml - iterate the service map instead of readdir in sync-services.ts --- .github/workflows/docs.yml | 8 +++----- website/scripts/sync-services.ts | 12 +++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a4f1b1a..25f0857 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,10 +18,8 @@ jobs: run: working-directory: website steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - with: - bun-version-file: package.json + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 - run: bun install --frozen-lockfile - run: bun scripts/sync-services.ts - name: Check generated service pages are up to date @@ -31,7 +29,7 @@ jobs: - run: bun run build - name: Deploy to Cloudflare Pages if: github.event_name != 'pull_request' - uses: cloudflare/wrangler-action@v3 + uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} diff --git a/website/scripts/sync-services.ts b/website/scripts/sync-services.ts index ca9555f..a4e6c77 100644 --- a/website/scripts/sync-services.ts +++ b/website/scripts/sync-services.ts @@ -2,7 +2,6 @@ * Regenerates src/content/docs/services/*.md from ../skills//SKILL.md. * Run from website/: bun scripts/sync-services.ts */ -import { readdir } from 'node:fs/promises' import { join } from 'node:path' const ROOT = join(import.meta.dir, '..', '..') @@ -32,13 +31,12 @@ function firstSentence(text: string): string { return (match ? match[0] : normalized).trim() } -for (const dir of await readdir(SKILLS)) { - const meta = SERVICES[dir] - if (!meta) { - console.warn(`skip: no service mapping for skills/${dir}`) - continue +for (const [dir, meta] of Object.entries(SERVICES)) { + const skill = Bun.file(join(SKILLS, dir, 'SKILL.md')) + if (!(await skill.exists())) { + throw new Error(`missing skills/${dir}/SKILL.md`) } - const raw = await Bun.file(join(SKILLS, dir, 'SKILL.md')).text() + const raw = await skill.text() const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n/) if (!fmMatch) { From f9e73b14bbc56abfe2aba297f8175e8b5363b0f5 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sat, 4 Jul 2026 19:01:41 +0900 Subject: [PATCH 05/15] docs: add HTTPS with portless guide Covers using vercel-labs/portless as a local HTTPS proxy in front of the emulators via --base-url / per-service seed baseUrl, and notes that the upstream --portless flag is not implemented in this fork. --- website/astro.config.mjs | 1 + .../content/docs/guides/getting-started.md | 1 + website/src/content/docs/guides/portless.md | 88 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 website/src/content/docs/guides/portless.md diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 84aa5a6..b30c5b8 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -23,6 +23,7 @@ export default defineConfig({ { label: 'Configuration', slug: 'guides/configuration' }, { label: 'Programmatic API', slug: 'guides/programmatic-api' }, { label: 'Authentication', slug: 'guides/authentication' }, + { label: 'HTTPS with portless', slug: 'guides/portless' }, ], }, { diff --git a/website/src/content/docs/guides/getting-started.md b/website/src/content/docs/guides/getting-started.md index 48b09da..bef21fb 100644 --- a/website/src/content/docs/guides/getting-started.md +++ b/website/src/content/docs/guides/getting-started.md @@ -102,3 +102,4 @@ To use an emulator, only the base URL your SDK talks to needs to change: - [Configuration](/guides/configuration/) — seed apps, users, and table data from YAML - [Programmatic API](/guides/programmatic-api/) — embed emulators in your test suite - [Authentication](/guides/authentication/) — how tokens and OAuth flows work +- [HTTPS with portless](/guides/portless/) — trusted `https://.emulate.localhost` URLs diff --git a/website/src/content/docs/guides/portless.md b/website/src/content/docs/guides/portless.md new file mode 100644 index 0000000..6954f89 --- /dev/null +++ b/website/src/content/docs/guides/portless.md @@ -0,0 +1,88 @@ +--- +title: HTTPS with portless +description: Give emulators trusted HTTPS URLs like https://kakao.emulate.localhost using portless as a local reverse proxy. +sidebar: + order: 5 +--- + +[portless](https://github.com/vercel-labs/portless) gives local servers +trusted HTTPS URLs (`https://.localhost`) with an auto-generated, +auto-trusted CA — no port numbers in URLs and no browser warnings. That +matters for emulators when the client requires HTTPS (secure cookies, +OAuth libraries that reject `http://` redirect URIs, mobile webviews). + +## Setup + +```bash +npm install -g portless + +# Start the HTTPS proxy once (generates and trusts the local CA) +portless proxy start +``` + +## Point an alias at an emulator + +Start a service and register its port as an alias: + +```bash +npx @pleaseai/emulate --service kakao # listens on http://localhost:4000 + +portless alias kakao.emulate 4000 # → https://kakao.emulate.localhost +``` + +Requests to `https://kakao.emulate.localhost` are now proxied to the +emulator, HTTP/2 and TLS included. + +## Tell the emulator its public URL + +Emulators embed their base URL in the responses they generate — OAuth +authorize redirects, issued URLs, webhook payloads. Behind a proxy the +default `http://localhost:4000` would leak through, so override it. + +For a single service, use `--base-url`: + +```bash +npx @pleaseai/emulate --service kakao --base-url https://kakao.emulate.localhost +``` + +For multiple services, set `baseUrl` per service in the seed config +(`--base-url` only works with a single `--service`): + +```yaml +kakao: + baseUrl: https://kakao.emulate.localhost + # ...apps, users +supabase: + baseUrl: https://supabase.emulate.localhost + # ...tables +``` + +```bash +portless alias kakao.emulate 4000 +portless alias supabase.emulate 4004 +npx @pleaseai/emulate --seed emulate.config.yaml +``` + +The seed `baseUrl` takes priority over the computed default; the +`--base-url` flag beats both. The same override works programmatically: + +```ts +const emulator = await createEmulator({ + service: 'kakao', + port: 4000, + baseUrl: 'https://kakao.emulate.localhost', +}) +``` + +:::note +The upstream vercel-labs/emulate CLI has a `--portless` flag that +auto-registers one alias per service. This fork does not implement it — +register aliases with the `portless` CLI as shown above. +::: + +## Cleanup + +```bash +portless alias --remove kakao.emulate +portless clean # remove all portless state, CA trust, and hosts entries +``` From 405734f4c051944d6c63037e0e4089958e360cd1 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sat, 4 Jul 2026 19:11:14 +0900 Subject: [PATCH 06/15] feat: port --portless flag and base URL fallback chain from upstream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports vercel-labs/emulate portless integration: --portless auto-registers .emulate aliases (removed on shutdown, prompts install when missing), and resolveBaseUrl gains the upstream fallback chain — seed baseUrl > --base-url > EMULATE_BASE_URL > PORTLESS_URL > localhost — with {service} interpolation. --base-url now works with multiple services when it contains a {service} placeholder. Docs: portless guide now documents the flag; README and getting-started options updated. --- README.md | 8 ++ eslint.config.ts | 2 +- .../emulate/src/__tests__/base-url.test.ts | 55 +++++++++++ packages/emulate/src/base-url.ts | 26 ++++- packages/emulate/src/commands/start.ts | 25 ++++- packages/emulate/src/index.ts | 3 +- packages/emulate/src/portless.ts | 95 +++++++++++++++++++ .../content/docs/guides/getting-started.md | 3 +- website/src/content/docs/guides/portless.md | 65 +++++++------ 9 files changed, 243 insertions(+), 39 deletions(-) create mode 100644 packages/emulate/src/__tests__/base-url.test.ts create mode 100644 packages/emulate/src/portless.ts diff --git a/README.md b/README.md index a304a30..6c0c6ab 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,16 @@ bun packages/emulate/dist/index.js --service kakao,tosspayments # Generate a seed config file bun packages/emulate/dist/index.js init + +# Serve over HTTPS via portless (requires `portless proxy start`) +bun packages/emulate/dist/index.js --portless ``` +With `--portless`, each service registers a [portless](https://github.com/vercel-labs/portless) +alias and gets a trusted HTTPS URL like `https://kakao.emulate.localhost`. +For other proxies, `--base-url` / `EMULATE_BASE_URL` support `{service}` +interpolation (e.g. `--base-url "https://{service}.myproxy.test"`). + When an `emulate.config.yaml` (or `--seed `) is present, the emulators start pre-seeded with app keys, users, and table data. Only the services present in the config are started. diff --git a/eslint.config.ts b/eslint.config.ts index de444ba..30e8342 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -6,7 +6,7 @@ export default pleaseai( }, { // The CLI legitimately prints to stdout. - files: ['packages/emulate/src/index.ts', 'packages/emulate/src/commands/**'], + files: ['packages/emulate/src/index.ts', 'packages/emulate/src/commands/**', 'packages/emulate/src/portless.ts'], rules: { 'no-console': 'off', }, diff --git a/packages/emulate/src/__tests__/base-url.test.ts b/packages/emulate/src/__tests__/base-url.test.ts new file mode 100644 index 0000000..ad1be16 --- /dev/null +++ b/packages/emulate/src/__tests__/base-url.test.ts @@ -0,0 +1,55 @@ +import process from 'node:process' +import { afterEach, describe, expect, it } from 'bun:test' +import { resolveBaseUrl } from '../base-url.js' +import { portlessBaseUrl } from '../portless.js' + +const savedEnv = { EMULATE_BASE_URL: process.env.EMULATE_BASE_URL, PORTLESS_URL: process.env.PORTLESS_URL } + +afterEach(() => { + delete process.env.EMULATE_BASE_URL + delete process.env.PORTLESS_URL + if (savedEnv.EMULATE_BASE_URL !== undefined) { + process.env.EMULATE_BASE_URL = savedEnv.EMULATE_BASE_URL + } + if (savedEnv.PORTLESS_URL !== undefined) { + process.env.PORTLESS_URL = savedEnv.PORTLESS_URL + } +}) + +describe('resolveBaseUrl', () => { + it('defaults to localhost with the port', () => { + expect(resolveBaseUrl({ service: 'kakao', port: 4000 })).toBe('http://localhost:4000') + }) + + it('prefers seed baseUrl over everything else', () => { + process.env.EMULATE_BASE_URL = 'https://env.test' + expect( + resolveBaseUrl({ service: 'kakao', port: 4000, baseUrl: 'https://flag.test', seedBaseUrl: 'https://seed.test/' }), + ).toBe('https://seed.test') + }) + + it('uses the explicit baseUrl over env vars', () => { + process.env.EMULATE_BASE_URL = 'https://env.test' + expect(resolveBaseUrl({ service: 'kakao', port: 4000, baseUrl: 'https://flag.test' })).toBe('https://flag.test') + }) + + it('falls back to EMULATE_BASE_URL, then PORTLESS_URL', () => { + process.env.PORTLESS_URL = 'https://{service}.emulate.localhost' + expect(resolveBaseUrl({ service: 'naver', port: 4001 })).toBe('https://naver.emulate.localhost') + + process.env.EMULATE_BASE_URL = 'https://{service}.myproxy.test' + expect(resolveBaseUrl({ service: 'naver', port: 4001 })).toBe('https://naver.myproxy.test') + }) + + it('interpolates {service} in explicit baseUrl', () => { + expect(resolveBaseUrl({ service: 'supabase', port: 4004, baseUrl: 'https://{service}.proxy.test' })).toBe( + 'https://supabase.proxy.test', + ) + }) +}) + +describe('portlessBaseUrl', () => { + it('builds the .emulate.localhost HTTPS URL', () => { + expect(portlessBaseUrl('kakao')).toBe('https://kakao.emulate.localhost') + }) +}) diff --git a/packages/emulate/src/base-url.ts b/packages/emulate/src/base-url.ts index 7abc714..24cb94a 100644 --- a/packages/emulate/src/base-url.ts +++ b/packages/emulate/src/base-url.ts @@ -1,4 +1,5 @@ import type { ServiceName } from './registry.js' +import process from 'node:process' export interface ResolveBaseUrlOptions { service: ServiceName @@ -7,12 +8,31 @@ export interface ResolveBaseUrlOptions { seedBaseUrl?: string } +function normalize(url: string, service: ServiceName): string { + return url.replaceAll('{service}', service).replace(/\/$/, '') +} + +/** + * Fallback chain (matches upstream vercel-labs/emulate): + * 1. Per-service baseUrl from seed config + * 2. Explicit baseUrl (CLI flag or programmatic option) + * 3. EMULATE_BASE_URL env var + * 4. PORTLESS_URL env var (set by the portless CLI wrapper) + * 5. http://localhost: + * All sources support {service} interpolation. + */ export function resolveBaseUrl(options: ResolveBaseUrlOptions): string { + if (options.seedBaseUrl) { + return normalize(options.seedBaseUrl, options.service) + } if (options.baseUrl) { - return options.baseUrl.replace(/\/$/, '') + return normalize(options.baseUrl, options.service) } - if (options.seedBaseUrl) { - return options.seedBaseUrl.replace(/\/$/, '') + if (process.env.EMULATE_BASE_URL) { + return normalize(process.env.EMULATE_BASE_URL, options.service) + } + if (process.env.PORTLESS_URL) { + return normalize(process.env.PORTLESS_URL, options.service) } return `http://localhost:${options.port}` } diff --git a/packages/emulate/src/commands/start.ts b/packages/emulate/src/commands/start.ts index 7f9c5cb..18d813f 100644 --- a/packages/emulate/src/commands/start.ts +++ b/packages/emulate/src/commands/start.ts @@ -1,4 +1,5 @@ import type { Emulator, SeedConfig } from '../api.js' +import type { PortlessAlias } from '../portless.js' import type { ServiceName } from '../registry.js' import { existsSync, readFileSync } from 'node:fs' import { resolve } from 'node:path' @@ -6,6 +7,7 @@ import process from 'node:process' import pc from 'picocolors' import { parse as parseYaml } from 'yaml' import { createEmulator } from '../api.js' +import { ensurePortless, portlessBaseUrl, registerAliases, removeAliases } from '../portless.js' import { SERVICE_NAMES, SERVICE_REGISTRY } from '../registry.js' export interface StartOptions { @@ -13,6 +15,7 @@ export interface StartOptions { service?: string seed?: string baseUrl?: string + portless?: boolean } interface LoadResult { @@ -89,11 +92,27 @@ export async function startCommand(options: StartOptions): Promise { } } - if (options.baseUrl && services.length > 1) { - console.error('--base-url can only be used with a single service (--service).') + if (options.portless && options.baseUrl) { + console.error('--portless and --base-url are mutually exclusive.') process.exit(1) } + if (options.baseUrl && services.length > 1 && !options.baseUrl.includes('{service}')) { + console.error('--base-url with multiple services requires a {service} placeholder, e.g. https://{service}.myproxy.test') + process.exit(1) + } + + if (options.portless) { + await ensurePortless() + const aliases: PortlessAlias[] = services.map((service, i) => ({ + name: `${service}.emulate`, + port: basePort + i, + })) + registerAliases(aliases) + const cleanup = () => removeAliases(aliases) + process.on('exit', cleanup) + } + const emulators: Array<{ service: ServiceName, emulator: Emulator }> = [] for (let i = 0; i < services.length; i++) { @@ -103,7 +122,7 @@ export async function startCommand(options: StartOptions): Promise { service, port, seed: seedConfig ?? undefined, - baseUrl: services.length === 1 ? options.baseUrl : undefined, + baseUrl: options.portless ? portlessBaseUrl(service) : options.baseUrl, }) emulators.push({ service, emulator }) } diff --git a/packages/emulate/src/index.ts b/packages/emulate/src/index.ts index 60aa51c..9c44168 100644 --- a/packages/emulate/src/index.ts +++ b/packages/emulate/src/index.ts @@ -18,7 +18,8 @@ program .option('-p, --port ', 'base port (services use port, port+1, ...)', v => Number.parseInt(v, 10), 4000) .option('-s, --service ', `comma-separated services to start`) .option('--seed ', 'seed config file (yaml or json)') - .option('--base-url ', 'override base URL (single service only)') + .option('--base-url ', 'override base URL (supports {service} interpolation)') + .option('--portless', 'serve over HTTPS via portless (auto-registers .emulate aliases)') .action(startCommand) program diff --git a/packages/emulate/src/portless.ts b/packages/emulate/src/portless.ts new file mode 100644 index 0000000..95db1a6 --- /dev/null +++ b/packages/emulate/src/portless.ts @@ -0,0 +1,95 @@ +import { execSync, spawnSync } from 'node:child_process' +import process from 'node:process' +import { createInterface } from 'node:readline' + +function isInteractive(): boolean { + return Boolean(process.stdin.isTTY) && !process.env.CI +} + +function hasPortless(): boolean { + const result = spawnSync('portless', ['--version'], { stdio: 'ignore' }) + return result.status === 0 +} + +function promptYesNo(question: string): Promise { + return new Promise((resolve) => { + const rl = createInterface({ input: process.stdin, output: process.stdout }) + rl.question(question, (answer) => { + rl.close() + const normalized = answer.trim().toLowerCase() + resolve(normalized === '' || normalized === 'y' || normalized === 'yes') + }) + }) +} + +function isProxyRunning(): boolean { + const result = spawnSync('portless', ['list'], { stdio: 'ignore' }) + return result.status === 0 +} + +export async function ensurePortless(): Promise { + if (!hasPortless()) { + if (!isInteractive()) { + console.error('portless is required but not installed. Run: npm i -g portless') + process.exit(1) + } + + const yes = await promptYesNo('portless is not installed. Install it now? (npm i -g portless) [Y/n] ') + if (!yes) { + console.error('Cannot continue without portless.') + process.exit(1) + } + + try { + execSync('npm i -g portless', { stdio: 'inherit' }) + } + catch { + console.error('Failed to install portless.') + process.exit(1) + } + + if (!hasPortless()) { + console.error('portless was installed but could not be found on PATH.') + process.exit(1) + } + } + + if (!isProxyRunning()) { + console.error('portless proxy is not running. Start it with: portless proxy start') + process.exit(1) + } +} + +export interface PortlessAlias { + name: string + port: number +} + +export function registerAliases(aliases: PortlessAlias[]): void { + const registered: PortlessAlias[] = [] + for (const { name, port } of aliases) { + const result = spawnSync('portless', ['alias', name, String(port), '--force'], { + stdio: 'inherit', + }) + if (result.status !== 0) { + if (registered.length > 0) { + removeAliases(registered) + } + throw new Error(`Failed to register portless alias: ${name} -> ${port}`) + } + registered.push({ name, port }) + } +} + +export function removeAliases(aliases: PortlessAlias[]): void { + for (const { name } of aliases) { + const result = spawnSync('portless', ['alias', '--remove', name], { stdio: 'ignore' }) + if (result.status !== 0) { + console.error(`Warning: failed to remove portless alias: ${name}`) + } + } +} + +export function portlessBaseUrl(serviceName: string): string { + return `https://${serviceName}.emulate.localhost` +} diff --git a/website/src/content/docs/guides/getting-started.md b/website/src/content/docs/guides/getting-started.md index bef21fb..ec18267 100644 --- a/website/src/content/docs/guides/getting-started.md +++ b/website/src/content/docs/guides/getting-started.md @@ -66,7 +66,8 @@ npx @pleaseai/emulate list | `-p, --port ` | `4000` | Base port (auto-increments per service) | | `-s, --service ` | all | Comma-separated services to enable | | `--seed ` | auto-detect | Path to seed config (YAML or JSON) | -| `--base-url ` | — | Override base URL (single service only) | +| `--base-url ` | — | Override base URL (supports `{service}` interpolation) | +| `--portless` | off | Serve over HTTPS via [portless](/guides/portless/) (auto-registers aliases) | When a single service is started with `--service`, it runs on the base port. When multiple services run together, ports are assigned sequentially from the diff --git a/website/src/content/docs/guides/portless.md b/website/src/content/docs/guides/portless.md index 6954f89..5e67e38 100644 --- a/website/src/content/docs/guides/portless.md +++ b/website/src/content/docs/guides/portless.md @@ -20,51 +20,53 @@ npm install -g portless portless proxy start ``` -## Point an alias at an emulator +## The --portless flag -Start a service and register its port as an alias: +Start emulate with portless integration and every service registers itself +as a portless alias automatically: ```bash -npx @pleaseai/emulate --service kakao # listens on http://localhost:4000 +npx @pleaseai/emulate --portless +``` -portless alias kakao.emulate 4000 # → https://kakao.emulate.localhost +Each service gets a named HTTPS URL: + +``` +kakao https://kakao.emulate.localhost +naver https://naver.emulate.localhost +supabase https://supabase.emulate.localhost ``` -Requests to `https://kakao.emulate.localhost` are now proxied to the -emulator, HTTP/2 and TLS included. +If portless is not installed, emulate prompts to install it +(`npm i -g portless`); the proxy must already be running +(`portless proxy start`). The flag overwrites any existing aliases matching +`.emulate`, and aliases are removed automatically when emulate +shuts down. -## Tell the emulator its public URL +The emulators also advertise these HTTPS URLs as their base URL, so OAuth +redirects, issued URLs, and webhook payloads use +`https://.emulate.localhost` instead of `http://localhost:`. -Emulators embed their base URL in the responses they generate — OAuth -authorize redirects, issued URLs, webhook payloads. Behind a proxy the -default `http://localhost:4000` would leak through, so override it. +## Custom base URLs without --portless -For a single service, use `--base-url`: +For any other reverse proxy, use `--base-url` or the `EMULATE_BASE_URL` +env var — both support `{service}` interpolation for multi-service runs: ```bash -npx @pleaseai/emulate --service kakao --base-url https://kakao.emulate.localhost +npx @pleaseai/emulate --base-url "https://{service}.myproxy.test" ``` -For multiple services, set `baseUrl` per service in the seed config -(`--base-url` only works with a single `--service`): +Per-service overrides in the seed config take the highest priority: ```yaml kakao: baseUrl: https://kakao.emulate.localhost # ...apps, users -supabase: - baseUrl: https://supabase.emulate.localhost - # ...tables -``` - -```bash -portless alias kakao.emulate 4000 -portless alias supabase.emulate 4004 -npx @pleaseai/emulate --seed emulate.config.yaml ``` -The seed `baseUrl` takes priority over the computed default; the -`--base-url` flag beats both. The same override works programmatically: +The full base URL priority is: seed `baseUrl` → `--base-url` flag → +`EMULATE_BASE_URL` → `PORTLESS_URL` (set by the portless CLI wrapper) → +`http://localhost:`. The same override works programmatically: ```ts const emulator = await createEmulator({ @@ -74,11 +76,14 @@ const emulator = await createEmulator({ }) ``` -:::note -The upstream vercel-labs/emulate CLI has a `--portless` flag that -auto-registers one alias per service. This fork does not implement it — -register aliases with the `portless` CLI as shown above. -::: +## Manual aliases + +You can also register aliases yourself instead of using `--portless`: + +```bash +npx @pleaseai/emulate --service kakao --base-url https://kakao.emulate.localhost +portless alias kakao.emulate 4000 # → https://kakao.emulate.localhost +``` ## Cleanup From a432f1b42be1d8b4b5869d5a7f26e04538ec17d1 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sun, 5 Jul 2026 12:16:12 +0900 Subject: [PATCH 07/15] docs: apply starlight-theme-black to the docs site --- website/astro.config.mjs | 8 ++++++++ website/bun.lock | 9 +++++++++ website/package.json | 3 ++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/website/astro.config.mjs b/website/astro.config.mjs index b30c5b8..5c61cd2 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -1,6 +1,7 @@ // @ts-check import starlight from '@astrojs/starlight' import { defineConfig } from 'astro/config' +import starlightThemeBlack from 'starlight-theme-black' export default defineConfig({ site: 'https://emulate.pleaseai.dev', @@ -15,6 +16,13 @@ export default defineConfig({ editLink: { baseUrl: 'https://github.com/pleaseai/emulate/edit/main/website/', }, + plugins: [ + starlightThemeBlack({ + navLinks: [{ label: 'Docs', link: '/guides/getting-started/' }], + footerText: + 'Built by [PleaseAI](https://github.com/pleaseai). Based on [vercel-labs/emulate](https://github.com/vercel-labs/emulate). Licensed Apache-2.0.', + }), + ], sidebar: [ { label: 'Guides', diff --git a/website/bun.lock b/website/bun.lock index 8e813f0..d874ffa 100644 --- a/website/bun.lock +++ b/website/bun.lock @@ -8,6 +8,7 @@ "@astrojs/starlight": "^0.36.0", "astro": "^5.6.0", "sharp": "^0.34.0", + "starlight-theme-black": "^0.6.0", }, "devDependencies": { "wrangler": "^4.0.0", @@ -123,6 +124,10 @@ "@expressive-code/plugin-text-markers": ["@expressive-code/plugin-text-markers@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7" } }, "sha512-Ewpwuc5t6eFdZmWlFyeuy3e1PTQC0jFvw2Q+2bpcWXbOZhPLsT7+h8lsSIJxb5mS7wZko7cKyQ2RLYDyK6Fpmw=="], + "@fontsource/geist-mono": ["@fontsource/geist-mono@5.2.8", "", {}, "sha512-YqZUb3X42GjRaHkibUNyE8K4Rk9A6I9Ez81YpJYiuJN4ggmTW2ixoQpa9EWCPfXZtIsCQJTyrDoV9OJOZO/UcA=="], + + "@fontsource/geist-sans": ["@fontsource/geist-sans@5.2.5", "", {}, "sha512-anllOHyJbElRs9fV15TeDRqAeb1IKm4bSknPl6ZMoyPTx1BBy7logudcUwpNjmQLkzn4Q0JGQLRCUKJYoyST6A=="], + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], @@ -569,6 +574,8 @@ "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + "mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="], "mdast-util-directive": ["mdast-util-directive@3.1.0", "", { "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" } }, "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q=="], @@ -821,6 +828,8 @@ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "starlight-theme-black": ["starlight-theme-black@0.6.0", "", { "dependencies": { "@fontsource/geist-mono": "^5.1.0", "@fontsource/geist-sans": "^5.1.0", "@pagefind/default-ui": "^1.3.0", "marked": "^15.0.6" }, "peerDependencies": { "@astrojs/starlight": ">=0.30.0" } }, "sha512-SoYAlyFEyRizcxQXd1ibfAGytmI5gVbSBReK25YFeASI51YwdB9bQtmp14/d70KgfD6kD7SRidhOrPBMD4S3AA=="], + "stream-replace-string": ["stream-replace-string@2.0.0", "", {}, "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w=="], "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], diff --git a/website/package.json b/website/package.json index a702285..38b31de 100644 --- a/website/package.json +++ b/website/package.json @@ -14,7 +14,8 @@ "dependencies": { "@astrojs/starlight": "^0.36.0", "astro": "^5.6.0", - "sharp": "^0.34.0" + "sharp": "^0.34.0", + "starlight-theme-black": "^0.6.0" }, "devDependencies": { "wrangler": "^4.0.0" From abe5c5f4f4670ab15a22fc95643dba55585b9964 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sun, 5 Jul 2026 12:18:26 +0900 Subject: [PATCH 08/15] chore: fix Codacy warnings in portless port --- packages/emulate/src/__tests__/base-url.test.ts | 2 +- packages/emulate/src/commands/start.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/emulate/src/__tests__/base-url.test.ts b/packages/emulate/src/__tests__/base-url.test.ts index ad1be16..8745a1f 100644 --- a/packages/emulate/src/__tests__/base-url.test.ts +++ b/packages/emulate/src/__tests__/base-url.test.ts @@ -49,7 +49,7 @@ describe('resolveBaseUrl', () => { }) describe('portlessBaseUrl', () => { - it('builds the .emulate.localhost HTTPS URL', () => { + it('builds the service.emulate.localhost HTTPS URL', () => { expect(portlessBaseUrl('kakao')).toBe('https://kakao.emulate.localhost') }) }) diff --git a/packages/emulate/src/commands/start.ts b/packages/emulate/src/commands/start.ts index 18d813f..5e61f16 100644 --- a/packages/emulate/src/commands/start.ts +++ b/packages/emulate/src/commands/start.ts @@ -109,8 +109,9 @@ export async function startCommand(options: StartOptions): Promise { port: basePort + i, })) registerAliases(aliases) - const cleanup = () => removeAliases(aliases) - process.on('exit', cleanup) + process.on('exit', () => { + removeAliases(aliases) + }) } const emulators: Array<{ service: ServiceName, emulator: Emulator }> = [] From bae3bb0bf6ea62f13ce9ea7d1c87cdeadfad7674 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sun, 5 Jul 2026 12:21:32 +0900 Subject: [PATCH 09/15] test: cover portless integration and reduce start command complexity Addresses the SonarCloud quality gate: extracts resolveServices / validateBaseUrlOptions / setupPortless from startCommand (cognitive complexity), moves alias construction into buildAliases, adds tests for alias registration/rollback/removal with a mocked child_process, and marks the intentional PATH-based portless invocations with NOSONAR. --- .../emulate/src/__tests__/base-url.test.ts | 17 ++++- .../emulate/src/__tests__/portless.test.ts | 75 +++++++++++++++++++ packages/emulate/src/commands/start.ts | 54 +++++++------ packages/emulate/src/portless.ts | 14 ++-- 4 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 packages/emulate/src/__tests__/portless.test.ts diff --git a/packages/emulate/src/__tests__/base-url.test.ts b/packages/emulate/src/__tests__/base-url.test.ts index 8745a1f..bcc823a 100644 --- a/packages/emulate/src/__tests__/base-url.test.ts +++ b/packages/emulate/src/__tests__/base-url.test.ts @@ -1,7 +1,7 @@ import process from 'node:process' import { afterEach, describe, expect, it } from 'bun:test' import { resolveBaseUrl } from '../base-url.js' -import { portlessBaseUrl } from '../portless.js' +import { validateBaseUrlOptions } from '../commands/start.js' const savedEnv = { EMULATE_BASE_URL: process.env.EMULATE_BASE_URL, PORTLESS_URL: process.env.PORTLESS_URL } @@ -48,8 +48,17 @@ describe('resolveBaseUrl', () => { }) }) -describe('portlessBaseUrl', () => { - it('builds the service.emulate.localhost HTTPS URL', () => { - expect(portlessBaseUrl('kakao')).toBe('https://kakao.emulate.localhost') +describe('validateBaseUrlOptions', () => { + it('rejects --portless together with --base-url', () => { + expect(validateBaseUrlOptions({ portless: true, baseUrl: 'https://x.test' }, 1)).toContain('mutually exclusive') + }) + + it('requires {service} for multi-service --base-url', () => { + expect(validateBaseUrlOptions({ baseUrl: 'https://x.test' }, 2)).toContain('{service} placeholder') + expect(validateBaseUrlOptions({ baseUrl: 'https://{service}.x.test' }, 2)).toBeNull() + }) + + it('accepts a literal --base-url for a single service', () => { + expect(validateBaseUrlOptions({ baseUrl: 'https://x.test' }, 1)).toBeNull() }) }) diff --git a/packages/emulate/src/__tests__/portless.test.ts b/packages/emulate/src/__tests__/portless.test.ts new file mode 100644 index 0000000..e589d95 --- /dev/null +++ b/packages/emulate/src/__tests__/portless.test.ts @@ -0,0 +1,75 @@ +import { beforeEach, describe, expect, it, mock } from 'bun:test' + +interface SpawnCall { + cmd: string + args: string[] +} + +const spawnCalls: SpawnCall[] = [] +let spawnResults: Array<{ status: number }> = [] + +mock.module('node:child_process', () => ({ + spawnSync: (cmd: string, args: string[]) => { + spawnCalls.push({ cmd, args }) + return spawnResults.shift() ?? { status: 0 } + }, + execSync: () => '', +})) + +const { buildAliases, portlessBaseUrl, registerAliases, removeAliases } = await import('../portless.js') + +beforeEach(() => { + spawnCalls.length = 0 + spawnResults = [] +}) + +describe('buildAliases', () => { + it('assigns sequential ports from the base port', () => { + expect(buildAliases(['kakao', 'naver'], 4000)).toEqual([ + { name: 'kakao.emulate', port: 4000 }, + { name: 'naver.emulate', port: 4001 }, + ]) + }) +}) + +describe('registerAliases', () => { + it('registers each alias with --force', () => { + registerAliases([{ name: 'kakao.emulate', port: 4000 }]) + expect(spawnCalls).toEqual([{ cmd: 'portless', args: ['alias', 'kakao.emulate', '4000', '--force'] }]) + }) + + it('rolls back already-registered aliases when one fails', () => { + spawnResults = [{ status: 0 }, { status: 1 }] + expect(() => + registerAliases([ + { name: 'kakao.emulate', port: 4000 }, + { name: 'naver.emulate', port: 4001 }, + ]), + ).toThrow('Failed to register portless alias: naver.emulate -> 4001') + expect(spawnCalls.map(c => c.args)).toEqual([ + ['alias', 'kakao.emulate', '4000', '--force'], + ['alias', 'naver.emulate', '4001', '--force'], + ['alias', '--remove', 'kakao.emulate'], + ]) + }) +}) + +describe('removeAliases', () => { + it('removes every alias even when one fails', () => { + spawnResults = [{ status: 1 }, { status: 0 }] + removeAliases([ + { name: 'kakao.emulate', port: 4000 }, + { name: 'naver.emulate', port: 4001 }, + ]) + expect(spawnCalls.map(c => c.args)).toEqual([ + ['alias', '--remove', 'kakao.emulate'], + ['alias', '--remove', 'naver.emulate'], + ]) + }) +}) + +describe('portlessBaseUrl', () => { + it('builds the service.emulate.localhost HTTPS URL', () => { + expect(portlessBaseUrl('kakao')).toBe('https://kakao.emulate.localhost') + }) +}) diff --git a/packages/emulate/src/commands/start.ts b/packages/emulate/src/commands/start.ts index 5e61f16..f1f58cb 100644 --- a/packages/emulate/src/commands/start.ts +++ b/packages/emulate/src/commands/start.ts @@ -7,7 +7,7 @@ import process from 'node:process' import pc from 'picocolors' import { parse as parseYaml } from 'yaml' import { createEmulator } from '../api.js' -import { ensurePortless, portlessBaseUrl, registerAliases, removeAliases } from '../portless.js' +import { buildAliases, ensurePortless, portlessBaseUrl, registerAliases, removeAliases } from '../portless.js' import { SERVICE_NAMES, SERVICE_REGISTRY } from '../registry.js' export interface StartOptions { @@ -66,13 +66,7 @@ function inferServicesFromConfig(config: SeedConfig): ServiceName[] | null { return found.length > 0 ? [...found] : null } -export async function startCommand(options: StartOptions): Promise { - const { port: basePort } = options - - const loaded = loadSeedConfig(options.seed) - const seedConfig = loaded?.config ?? null - const configSource = loaded?.source ?? null - +function resolveServices(options: StartOptions, seedConfig: SeedConfig | null): ServiceName[] { let services: ServiceName[] if (options.service) { services = options.service.split(',').map(s => s.trim()) as ServiceName[] @@ -91,27 +85,45 @@ export async function startCommand(options: StartOptions): Promise { process.exit(1) } } + return services +} +export function validateBaseUrlOptions(options: Pick, serviceCount: number): string | null { if (options.portless && options.baseUrl) { - console.error('--portless and --base-url are mutually exclusive.') - process.exit(1) + return '--portless and --base-url are mutually exclusive.' } + if (options.baseUrl && serviceCount > 1 && !options.baseUrl.includes('{service}')) { + return '--base-url with multiple services requires a {service} placeholder, e.g. https://{service}.myproxy.test' + } + return null +} - if (options.baseUrl && services.length > 1 && !options.baseUrl.includes('{service}')) { - console.error('--base-url with multiple services requires a {service} placeholder, e.g. https://{service}.myproxy.test') +async function setupPortless(services: ServiceName[], basePort: number): Promise { + await ensurePortless() + const aliases: PortlessAlias[] = buildAliases(services, basePort) + registerAliases(aliases) + process.on('exit', () => { + removeAliases(aliases) + }) +} + +export async function startCommand(options: StartOptions): Promise { + const { port: basePort } = options + + const loaded = loadSeedConfig(options.seed) + const seedConfig = loaded?.config ?? null + const configSource = loaded?.source ?? null + + const services = resolveServices(options, seedConfig) + + const optionError = validateBaseUrlOptions(options, services.length) + if (optionError) { + console.error(optionError) process.exit(1) } if (options.portless) { - await ensurePortless() - const aliases: PortlessAlias[] = services.map((service, i) => ({ - name: `${service}.emulate`, - port: basePort + i, - })) - registerAliases(aliases) - process.on('exit', () => { - removeAliases(aliases) - }) + await setupPortless(services, basePort) } const emulators: Array<{ service: ServiceName, emulator: Emulator }> = [] diff --git a/packages/emulate/src/portless.ts b/packages/emulate/src/portless.ts index 95db1a6..dad6f26 100644 --- a/packages/emulate/src/portless.ts +++ b/packages/emulate/src/portless.ts @@ -7,7 +7,7 @@ function isInteractive(): boolean { } function hasPortless(): boolean { - const result = spawnSync('portless', ['--version'], { stdio: 'ignore' }) + const result = spawnSync('portless', ['--version'], { stdio: 'ignore' }) // NOSONAR: resolving portless from PATH is the point of this integration return result.status === 0 } @@ -23,7 +23,7 @@ function promptYesNo(question: string): Promise { } function isProxyRunning(): boolean { - const result = spawnSync('portless', ['list'], { stdio: 'ignore' }) + const result = spawnSync('portless', ['list'], { stdio: 'ignore' }) // NOSONAR: PATH lookup is intentional return result.status === 0 } @@ -41,7 +41,7 @@ export async function ensurePortless(): Promise { } try { - execSync('npm i -g portless', { stdio: 'inherit' }) + execSync('npm i -g portless', { stdio: 'inherit' }) // NOSONAR: PATH lookup is intentional } catch { console.error('Failed to install portless.') @@ -65,12 +65,16 @@ export interface PortlessAlias { port: number } +export function buildAliases(services: readonly string[], basePort: number): PortlessAlias[] { + return services.map((service, i) => ({ name: `${service}.emulate`, port: basePort + i })) +} + export function registerAliases(aliases: PortlessAlias[]): void { const registered: PortlessAlias[] = [] for (const { name, port } of aliases) { const result = spawnSync('portless', ['alias', name, String(port), '--force'], { stdio: 'inherit', - }) + }) // NOSONAR: PATH lookup is intentional if (result.status !== 0) { if (registered.length > 0) { removeAliases(registered) @@ -83,7 +87,7 @@ export function registerAliases(aliases: PortlessAlias[]): void { export function removeAliases(aliases: PortlessAlias[]): void { for (const { name } of aliases) { - const result = spawnSync('portless', ['alias', '--remove', name], { stdio: 'ignore' }) + const result = spawnSync('portless', ['alias', '--remove', name], { stdio: 'ignore' }) // NOSONAR: PATH lookup is intentional if (result.status !== 0) { console.error(`Warning: failed to remove portless alias: ${name}`) } From eded9559628b3daddf52eca1e7f533ea53b1102f Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sun, 5 Jul 2026 12:27:40 +0900 Subject: [PATCH 10/15] ci: add docs PR preview deployments with sticky comment; finish Sonar gate fixes - docs workflow now deploys every run: main goes to production, PR branches get a Cloudflare Pages preview (fork PRs skipped), following the wrangler-action recommended setup with gitHubToken deployment tracking; a sticky PR comment posts the preview + branch alias URLs - cover ensurePortless fully (mocked child_process/readline/process.exit) and fix the misplaced NOSONAR on the multi-line spawnSync call - exclude CLI wiring (index.ts, commands/) from Sonar coverage like the existing openapi.ts exclusion; add braces to a void-returning arrow (Codacy) --- .github/workflows/docs.yml | 46 +++++- .../emulate/src/__tests__/portless.test.ts | 135 +++++++++++++++++- packages/emulate/src/portless.ts | 4 +- sonar-project.properties | 6 +- 4 files changed, 178 insertions(+), 13 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 25f0857..535725d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,6 +14,7 @@ jobs: permissions: contents: read deployments: write + pull-requests: write defaults: run: working-directory: website @@ -27,11 +28,52 @@ jobs: git diff --exit-code -- src/content/docs/services \ || { echo "::error::services/*.md is stale — run 'bun run sync:services' in website/ and commit"; exit 1; } - run: bun run build + # Pushes to main deploy to production; PR branches get a preview + # deployment. Fork PRs are skipped (no access to secrets). - name: Deploy to Cloudflare Pages - if: github.event_name != 'pull_request' + id: deploy + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + gitHubToken: ${{ secrets.GITHUB_TOKEN }} workingDirectory: website - command: pages deploy dist --project-name=emulate-docs + command: pages deploy dist --project-name=emulate-docs --branch=${{ github.head_ref || github.ref_name }} + - name: Comment preview URL on PR + if: github.event_name == 'pull_request' && steps.deploy.outcome == 'success' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }} + ALIAS_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }} + with: + script: | + const marker = '' + const url = process.env.DEPLOYMENT_URL + const alias = process.env.ALIAS_URL + const lines = [ + marker, + '### 📖 Docs preview deployed', + '', + `| | URL |`, + `| --- | --- |`, + `| Preview | ${url} |`, + ] + if (alias && alias !== url) { + lines.push(`| Branch alias | ${alias} |`) + } + lines.push('', `_Commit: ${context.payload.pull_request.head.sha}_`) + const body = lines.join('\n') + + const { data: comments } = await github.rest.issues.listComments({ + ...context.repo, + issue_number: context.issue.number, + per_page: 100, + }) + const existing = comments.find(c => c.body.includes(marker)) + if (existing) { + await github.rest.issues.updateComment({ ...context.repo, comment_id: existing.id, body }) + } + else { + await github.rest.issues.createComment({ ...context.repo, issue_number: context.issue.number, body }) + } diff --git a/packages/emulate/src/__tests__/portless.test.ts b/packages/emulate/src/__tests__/portless.test.ts index e589d95..95dd2c4 100644 --- a/packages/emulate/src/__tests__/portless.test.ts +++ b/packages/emulate/src/__tests__/portless.test.ts @@ -1,4 +1,5 @@ -import { beforeEach, describe, expect, it, mock } from 'bun:test' +import process from 'node:process' +import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test' interface SpawnCall { cmd: string @@ -7,20 +8,39 @@ interface SpawnCall { const spawnCalls: SpawnCall[] = [] let spawnResults: Array<{ status: number }> = [] +let execCalls = 0 +let execShouldThrow = false +let promptAnswer = '' mock.module('node:child_process', () => ({ spawnSync: (cmd: string, args: string[]) => { spawnCalls.push({ cmd, args }) return spawnResults.shift() ?? { status: 0 } }, - execSync: () => '', + execSync: () => { + execCalls++ + if (execShouldThrow) { + throw new Error('install failed') + } + return '' + }, +})) + +mock.module('node:readline', () => ({ + createInterface: () => ({ + question: (_q: string, cb: (answer: string) => void) => cb(promptAnswer), + close: () => {}, + }), })) -const { buildAliases, portlessBaseUrl, registerAliases, removeAliases } = await import('../portless.js') +const { buildAliases, ensurePortless, portlessBaseUrl, registerAliases, removeAliases } = await import('../portless.js') beforeEach(() => { spawnCalls.length = 0 spawnResults = [] + execCalls = 0 + execShouldThrow = false + promptAnswer = '' }) describe('buildAliases', () => { @@ -40,12 +60,12 @@ describe('registerAliases', () => { it('rolls back already-registered aliases when one fails', () => { spawnResults = [{ status: 0 }, { status: 1 }] - expect(() => + expect(() => { registerAliases([ { name: 'kakao.emulate', port: 4000 }, { name: 'naver.emulate', port: 4001 }, - ]), - ).toThrow('Failed to register portless alias: naver.emulate -> 4001') + ]) + }).toThrow('Failed to register portless alias: naver.emulate -> 4001') expect(spawnCalls.map(c => c.args)).toEqual([ ['alias', 'kakao.emulate', '4000', '--force'], ['alias', 'naver.emulate', '4001', '--force'], @@ -68,6 +88,109 @@ describe('removeAliases', () => { }) }) +describe('ensurePortless', () => { + const savedIsTTY = process.stdin.isTTY + const savedCI = process.env.CI + + function mockExit(): { calls: number[], restore: () => void } { + const calls: number[] = [] + const original = process.exit + process.exit = ((code: number) => { + calls.push(code) + throw new Error(`exit ${code}`) + }) as typeof process.exit + return { + calls, + restore: () => { + process.exit = original + }, + } + } + + afterEach(() => { + Object.defineProperty(process.stdin, 'isTTY', { value: savedIsTTY, configurable: true }) + if (savedCI === undefined) { + delete process.env.CI + } + else { + process.env.CI = savedCI + } + }) + + it('resolves when portless and the proxy are available', async () => { + spawnResults = [{ status: 0 }, { status: 0 }] + await ensurePortless() + expect(spawnCalls.map(c => c.args)).toEqual([['--version'], ['list']]) + }) + + it('exits when the proxy is not running', async () => { + spawnResults = [{ status: 0 }, { status: 1 }] + const exit = mockExit() + try { + await expect(ensurePortless()).rejects.toThrow('exit 1') + expect(exit.calls).toEqual([1]) + } + finally { + exit.restore() + } + }) + + it('exits when portless is missing in a non-interactive session', async () => { + process.env.CI = 'true' + spawnResults = [{ status: 1 }] + const exit = mockExit() + try { + await expect(ensurePortless()).rejects.toThrow('exit 1') + expect(exit.calls).toEqual([1]) + expect(execCalls).toBe(0) + } + finally { + exit.restore() + } + }) + + it('installs portless when the interactive prompt is accepted', async () => { + Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true }) + delete process.env.CI + promptAnswer = 'y' + // missing → (install) → found → proxy running + spawnResults = [{ status: 1 }, { status: 0 }, { status: 0 }] + await ensurePortless() + expect(execCalls).toBe(1) + }) + + it('exits when the interactive prompt is declined', async () => { + Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true }) + delete process.env.CI + promptAnswer = 'n' + spawnResults = [{ status: 1 }] + const exit = mockExit() + try { + await expect(ensurePortless()).rejects.toThrow('exit 1') + expect(execCalls).toBe(0) + } + finally { + exit.restore() + } + }) + + it('exits when the install fails', async () => { + Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true }) + delete process.env.CI + promptAnswer = '' + execShouldThrow = true + spawnResults = [{ status: 1 }] + const exit = mockExit() + try { + await expect(ensurePortless()).rejects.toThrow('exit 1') + expect(execCalls).toBe(1) + } + finally { + exit.restore() + } + }) +}) + describe('portlessBaseUrl', () => { it('builds the service.emulate.localhost HTTPS URL', () => { expect(portlessBaseUrl('kakao')).toBe('https://kakao.emulate.localhost') diff --git a/packages/emulate/src/portless.ts b/packages/emulate/src/portless.ts index dad6f26..06887bc 100644 --- a/packages/emulate/src/portless.ts +++ b/packages/emulate/src/portless.ts @@ -72,9 +72,7 @@ export function buildAliases(services: readonly string[], basePort: number): Por export function registerAliases(aliases: PortlessAlias[]): void { const registered: PortlessAlias[] = [] for (const { name, port } of aliases) { - const result = spawnSync('portless', ['alias', name, String(port), '--force'], { - stdio: 'inherit', - }) // NOSONAR: PATH lookup is intentional + const result = spawnSync('portless', ['alias', name, String(port), '--force'], { stdio: 'inherit' }) // NOSONAR: PATH lookup is intentional if (result.status !== 0) { if (registered.length > 0) { removeAliases(registered) diff --git a/sonar-project.properties b/sonar-project.properties index 2d6c2ad..8023315 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -10,5 +10,7 @@ sonar.test.inclusions=**/__tests__/** sonar.javascript.lcov.reportPaths=coverage/lcov.info sonar.sourceEncoding=UTF-8 -# Hand-authored static OpenAPI spec objects are data, not logic - exclude from coverage -sonar.coverage.exclusions=**/src/routes/openapi.ts +# Hand-authored static OpenAPI spec objects are data, not logic - exclude from coverage. +# CLI wiring (commander setup, console/process.exit command flow) is exercised +# end-to-end via the running CLI, not unit-tested - exclude from coverage too. +sonar.coverage.exclusions=**/src/routes/openapi.ts,packages/emulate/src/index.ts,packages/emulate/src/commands/** From 700cde57488bcfbc0d8eee3961ffc437b380d941 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sun, 5 Jul 2026 12:53:16 +0900 Subject: [PATCH 11/15] docs: link the documentation site (emulate.pleaseai.dev) --- README.md | 2 ++ website/README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 6c0c6ab..6918fc5 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation for Korean services (Kakao, Naver, Toss Payments) and BaaS platforms (Firebase, Supabase). +**Documentation: [emulate.pleaseai.dev](https://emulate.pleaseai.dev)** + Built on the architecture of [vercel-labs/emulate](https://github.com/vercel-labs/emulate), using [`@emulators/core`](https://www.npmjs.com/package/@emulators/core). diff --git a/website/README.md b/website/README.md index fdb76ca..cb25a58 100644 --- a/website/README.md +++ b/website/README.md @@ -4,6 +4,8 @@ Documentation site for [pleaseai/emulate](https://github.com/pleaseai/emulate), built with [Astro Starlight](https://starlight.astro.build) and deployed to Cloudflare Pages. +**Live site: ** + ## Development ```bash From 03309008f61ddf7e3006a5d44fe08e3d6cf025f4 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sun, 5 Jul 2026 13:08:32 +0900 Subject: [PATCH 12/15] chore: apply AI code review suggestions - linear/naver skill docs: add port-slot note, fix invalid JSON example (regenerated website service pages) - implement EMULATE_PORT/PORT env fallback for the base port so the documented behavior is real (start-options.ts, tested) - move validateBaseUrlOptions out of commands/ so it stays under coverage gates - base-url.test.ts: clear env in beforeEach so ambient vars can't pollute the first test - website deploy script: sync service pages before building - docs.yml: staleness check now catches untracked generated pages; deploy step skips (instead of failing) when Cloudflare secrets are absent - normalize the conventions test example to repo code style --- .github/workflows/docs.yml | 15 +++++-- docs/EMULATOR-CONVENTIONS.md | 30 ++++++------- .../emulate/src/__tests__/base-url.test.ts | 43 ++++++++++++++----- packages/emulate/src/commands/start.ts | 11 +---- packages/emulate/src/index.ts | 3 +- packages/emulate/src/start-options.ts | 31 +++++++++++++ skills/linear/SKILL.md | 4 +- skills/naver/SKILL.md | 2 +- website/package.json | 2 +- .../src/content/docs/reference/conventions.md | 30 ++++++------- website/src/content/docs/services/linear.md | 4 +- website/src/content/docs/services/naver.md | 2 +- 12 files changed, 116 insertions(+), 61 deletions(-) create mode 100644 packages/emulate/src/start-options.ts diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 535725d..981c38a 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -11,6 +11,10 @@ on: jobs: build: runs-on: ubuntu-latest + env: + # Secrets are unavailable on fork PRs and may not be configured yet — + # skip deployment (and the preview comment) instead of failing the build. + HAS_CLOUDFLARE_SECRETS: ${{ secrets.CLOUDFLARE_API_TOKEN != '' && secrets.CLOUDFLARE_ACCOUNT_ID != '' }} permissions: contents: read deployments: write @@ -24,15 +28,20 @@ jobs: - run: bun install --frozen-lockfile - run: bun scripts/sync-services.ts - name: Check generated service pages are up to date + # git status --porcelain also catches untracked pages generated for + # newly added skills, which git diff alone would miss. run: | - git diff --exit-code -- src/content/docs/services \ - || { echo "::error::services/*.md is stale — run 'bun run sync:services' in website/ and commit"; exit 1; } + if [ -n "$(git status --porcelain -- src/content/docs/services)" ]; then + git status --short -- src/content/docs/services + echo "::error::services/*.md is stale — run 'bun run sync:services' in website/ and commit" + exit 1 + fi - run: bun run build # Pushes to main deploy to production; PR branches get a preview # deployment. Fork PRs are skipped (no access to secrets). - name: Deploy to Cloudflare Pages id: deploy - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + if: env.HAS_CLOUDFLARE_SECRETS == 'true' uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} diff --git a/docs/EMULATOR-CONVENTIONS.md b/docs/EMULATOR-CONVENTIONS.md index 1f4e114..a436c52 100644 --- a/docs/EMULATOR-CONVENTIONS.md +++ b/docs/EMULATOR-CONVENTIONS.md @@ -140,27 +140,27 @@ seedFromConfig. Send Requests directly through `app.fetch` without binding a port: ```ts -import { describe, it, expect, beforeEach } from "bun:test"; -import { createServer } from "@emulators/core"; -import { kakaoPlugin } from "../index.js"; +import { createServer } from '@emulators/core' +import { describe, expect, it } from 'bun:test' +import { kakaoPlugin } from '../index.js' function makeApp() { - const { app, store } = createServer(kakaoPlugin, { port: 4000 }); - kakaoPlugin.seed?.(store, "http://localhost:4000"); - return { app, store }; + const { app, store } = createServer(kakaoPlugin, { port: 4000 }) + kakaoPlugin.seed?.(store, 'http://localhost:4000') + return { app, store } } -it("issues token for authorization code", async () => { - const { app } = makeApp(); +it('issues token for authorization code', async () => { + const { app } = makeApp() const res = await app.fetch( - new Request("http://localhost:4000/oauth/token", { - method: "POST", - headers: { "content-type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ grant_type: "authorization_code", code, client_id }), + new Request('http://localhost:4000/oauth/token', { + method: 'POST', + headers: { 'content-type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ grant_type: 'authorization_code', code, client_id }), }), - ); - expect(res.status).toBe(200); -}); + ) + expect(res.status).toBe(200) +}) ``` - Cover the happy path plus the main error paths (bad code/token/key, missing diff --git a/packages/emulate/src/__tests__/base-url.test.ts b/packages/emulate/src/__tests__/base-url.test.ts index bcc823a..c302e20 100644 --- a/packages/emulate/src/__tests__/base-url.test.ts +++ b/packages/emulate/src/__tests__/base-url.test.ts @@ -1,18 +1,24 @@ import process from 'node:process' -import { afterEach, describe, expect, it } from 'bun:test' +import { afterAll, beforeEach, describe, expect, it } from 'bun:test' import { resolveBaseUrl } from '../base-url.js' -import { validateBaseUrlOptions } from '../commands/start.js' +import { defaultBasePort, validateBaseUrlOptions } from '../start-options.js' -const savedEnv = { EMULATE_BASE_URL: process.env.EMULATE_BASE_URL, PORTLESS_URL: process.env.PORTLESS_URL } +const ENV_VARS = ['EMULATE_BASE_URL', 'PORTLESS_URL', 'EMULATE_PORT', 'PORT'] as const +const savedEnv = Object.fromEntries(ENV_VARS.map(name => [name, process.env[name]])) -afterEach(() => { - delete process.env.EMULATE_BASE_URL - delete process.env.PORTLESS_URL - if (savedEnv.EMULATE_BASE_URL !== undefined) { - process.env.EMULATE_BASE_URL = savedEnv.EMULATE_BASE_URL +// Clear before each test (not just after) so ambient shell/CI env vars +// cannot leak into the first test case. +beforeEach(() => { + for (const name of ENV_VARS) { + delete process.env[name] } - if (savedEnv.PORTLESS_URL !== undefined) { - process.env.PORTLESS_URL = savedEnv.PORTLESS_URL +}) + +afterAll(() => { + for (const name of ENV_VARS) { + if (savedEnv[name] !== undefined) { + process.env[name] = savedEnv[name] + } } }) @@ -62,3 +68,20 @@ describe('validateBaseUrlOptions', () => { expect(validateBaseUrlOptions({ baseUrl: 'https://x.test' }, 1)).toBeNull() }) }) + +describe('defaultBasePort', () => { + it('defaults to 4000 without env vars', () => { + expect(defaultBasePort()).toBe(4000) + }) + + it('prefers EMULATE_PORT over PORT and ignores non-numeric values', () => { + process.env.PORT = '5000' + expect(defaultBasePort()).toBe(5000) + + process.env.EMULATE_PORT = '6000' + expect(defaultBasePort()).toBe(6000) + + process.env.EMULATE_PORT = 'not-a-port' + expect(defaultBasePort()).toBe(5000) + }) +}) diff --git a/packages/emulate/src/commands/start.ts b/packages/emulate/src/commands/start.ts index f1f58cb..00b1de1 100644 --- a/packages/emulate/src/commands/start.ts +++ b/packages/emulate/src/commands/start.ts @@ -9,6 +9,7 @@ import { parse as parseYaml } from 'yaml' import { createEmulator } from '../api.js' import { buildAliases, ensurePortless, portlessBaseUrl, registerAliases, removeAliases } from '../portless.js' import { SERVICE_NAMES, SERVICE_REGISTRY } from '../registry.js' +import { validateBaseUrlOptions } from '../start-options.js' export interface StartOptions { port: number @@ -88,16 +89,6 @@ function resolveServices(options: StartOptions, seedConfig: SeedConfig | null): return services } -export function validateBaseUrlOptions(options: Pick, serviceCount: number): string | null { - if (options.portless && options.baseUrl) { - return '--portless and --base-url are mutually exclusive.' - } - if (options.baseUrl && serviceCount > 1 && !options.baseUrl.includes('{service}')) { - return '--base-url with multiple services requires a {service} placeholder, e.g. https://{service}.myproxy.test' - } - return null -} - async function setupPortless(services: ServiceName[], basePort: number): Promise { await ensurePortless() const aliases: PortlessAlias[] = buildAliases(services, basePort) diff --git a/packages/emulate/src/index.ts b/packages/emulate/src/index.ts index 9c44168..1ca6fa4 100644 --- a/packages/emulate/src/index.ts +++ b/packages/emulate/src/index.ts @@ -4,6 +4,7 @@ import pkg from '../package.json' with { type: 'json' } import { initCommand } from './commands/init.js' import { listCommand } from './commands/list.js' import { startCommand } from './commands/start.js' +import { defaultBasePort } from './start-options.js' const program = new Command() @@ -15,7 +16,7 @@ program program .command('start', { isDefault: true }) .description('Start service emulators (default command)') - .option('-p, --port ', 'base port (services use port, port+1, ...)', v => Number.parseInt(v, 10), 4000) + .option('-p, --port ', 'base port (services use port, port+1, ...; falls back to EMULATE_PORT or PORT)', v => Number.parseInt(v, 10), defaultBasePort()) .option('-s, --service ', `comma-separated services to start`) .option('--seed ', 'seed config file (yaml or json)') .option('--base-url ', 'override base URL (supports {service} interpolation)') diff --git a/packages/emulate/src/start-options.ts b/packages/emulate/src/start-options.ts new file mode 100644 index 0000000..d8d0f06 --- /dev/null +++ b/packages/emulate/src/start-options.ts @@ -0,0 +1,31 @@ +import process from 'node:process' + +export interface BaseUrlOptions { + baseUrl?: string + portless?: boolean +} + +/** Base port fallback: --port flag > EMULATE_PORT > PORT > 4000. */ +export function defaultBasePort(): number { + for (const name of ['EMULATE_PORT', 'PORT']) { + const raw = process.env[name] + if (raw) { + const port = Number.parseInt(raw, 10) + if (!Number.isNaN(port)) { + return port + } + } + } + return 4000 +} + +/** Returns an error message when the base URL flags are inconsistent, else null. */ +export function validateBaseUrlOptions(options: BaseUrlOptions, serviceCount: number): string | null { + if (options.portless && options.baseUrl) { + return '--portless and --base-url are mutually exclusive.' + } + if (options.baseUrl && serviceCount > 1 && !options.baseUrl.includes('{service}')) { + return '--base-url with multiple services requires a {service} placeholder, e.g. https://{service}.myproxy.test' + } + return null +} diff --git a/skills/linear/SKILL.md b/skills/linear/SKILL.md index 978b4e8..cfe9311 100644 --- a/skills/linear/SKILL.md +++ b/skills/linear/SKILL.md @@ -29,8 +29,8 @@ npx @pleaseai/emulate --service linear ``` A single service starts on the base port (default `4000`). Use `-p ` to -change it, e.g. `--service linear -p 4012`. When started alongside other -services, ports are assigned sequentially from the base port. +change it. When started alongside other services, ports are assigned +sequentially from the base port (linear's slot is `4006`). Default URL (linear alone): diff --git a/skills/naver/SKILL.md b/skills/naver/SKILL.md index 1e4f6c5..e83aec6 100644 --- a/skills/naver/SKILL.md +++ b/skills/naver/SKILL.md @@ -34,7 +34,7 @@ sequentially from the base port (naver's slot is `4001`). if configured). - Profile calls use `Authorization: Bearer `. - OAuth/token errors are returned with HTTP 200 and an - `{ "error", "error_description" }` body (Naver convention). + `{"error":"...","error_description":"..."}` body (Naver convention). ## Flow diff --git a/website/package.json b/website/package.json index 38b31de..ba3666a 100644 --- a/website/package.json +++ b/website/package.json @@ -9,7 +9,7 @@ "build": "astro build", "preview": "astro preview", "sync:services": "bun scripts/sync-services.ts", - "deploy": "astro build && wrangler pages deploy dist" + "deploy": "bun scripts/sync-services.ts && astro build && wrangler pages deploy dist" }, "dependencies": { "@astrojs/starlight": "^0.36.0", diff --git a/website/src/content/docs/reference/conventions.md b/website/src/content/docs/reference/conventions.md index bf5f410..2a56dd9 100644 --- a/website/src/content/docs/reference/conventions.md +++ b/website/src/content/docs/reference/conventions.md @@ -146,27 +146,27 @@ seedFromConfig. Send Requests directly through `app.fetch` without binding a port: ```ts -import { describe, it, expect, beforeEach } from "bun:test"; -import { createServer } from "@emulators/core"; -import { kakaoPlugin } from "../index.js"; +import { createServer } from '@emulators/core' +import { describe, expect, it } from 'bun:test' +import { kakaoPlugin } from '../index.js' function makeApp() { - const { app, store } = createServer(kakaoPlugin, { port: 4000 }); - kakaoPlugin.seed?.(store, "http://localhost:4000"); - return { app, store }; + const { app, store } = createServer(kakaoPlugin, { port: 4000 }) + kakaoPlugin.seed?.(store, 'http://localhost:4000') + return { app, store } } -it("issues token for authorization code", async () => { - const { app } = makeApp(); +it('issues token for authorization code', async () => { + const { app } = makeApp() const res = await app.fetch( - new Request("http://localhost:4000/oauth/token", { - method: "POST", - headers: { "content-type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ grant_type: "authorization_code", code, client_id }), + new Request('http://localhost:4000/oauth/token', { + method: 'POST', + headers: { 'content-type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ grant_type: 'authorization_code', code, client_id }), }), - ); - expect(res.status).toBe(200); -}); + ) + expect(res.status).toBe(200) +}) ``` - Cover the happy path plus the main error paths (bad code/token/key, missing diff --git a/website/src/content/docs/services/linear.md b/website/src/content/docs/services/linear.md index ec936f4..35c9e8c 100644 --- a/website/src/content/docs/services/linear.md +++ b/website/src/content/docs/services/linear.md @@ -33,8 +33,8 @@ npx @pleaseai/emulate --service linear ``` A single service starts on the base port (default `4000`). Use `-p ` to -change it, e.g. `--service linear -p 4012`. When started alongside other -services, ports are assigned sequentially from the base port. +change it. When started alongside other services, ports are assigned +sequentially from the base port (linear's slot is `4006`). Default URL (linear alone): diff --git a/website/src/content/docs/services/naver.md b/website/src/content/docs/services/naver.md index 4b7d9a0..70760a8 100644 --- a/website/src/content/docs/services/naver.md +++ b/website/src/content/docs/services/naver.md @@ -38,7 +38,7 @@ sequentially from the base port (naver's slot is `4001`). if configured). - Profile calls use `Authorization: Bearer `. - OAuth/token errors are returned with HTTP 200 and an - `{ "error", "error_description" }` body (Naver convention). + `{"error":"...","error_description":"..."}` body (Naver convention). ## Flow From ecf88b80dcae95040efef0a47dd06f209393a4fc Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sun, 5 Jul 2026 13:13:23 +0900 Subject: [PATCH 13/15] chore: apply second-round review feedback - docs.yml: add concurrency group (repo convention) and persist-credentials: false on checkout - clarify linear port-slot wording (positional assignment) - avoid dynamically computed env keys (Codacy object-injection) --- .github/workflows/docs.yml | 6 ++++++ .../emulate/src/__tests__/base-url.test.ts | 21 ++++++++++++------- packages/emulate/src/start-options.ts | 3 +-- skills/linear/SKILL.md | 3 ++- website/src/content/docs/services/linear.md | 3 ++- 5 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 981c38a..895bc79 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -8,6 +8,10 @@ on: paths: ['website/**', 'skills/**', 'docs/**'] workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + jobs: build: runs-on: ubuntu-latest @@ -24,6 +28,8 @@ jobs: working-directory: website steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false - uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 - run: bun install --frozen-lockfile - run: bun scripts/sync-services.ts diff --git a/packages/emulate/src/__tests__/base-url.test.ts b/packages/emulate/src/__tests__/base-url.test.ts index c302e20..0a5b520 100644 --- a/packages/emulate/src/__tests__/base-url.test.ts +++ b/packages/emulate/src/__tests__/base-url.test.ts @@ -3,21 +3,26 @@ import { afterAll, beforeEach, describe, expect, it } from 'bun:test' import { resolveBaseUrl } from '../base-url.js' import { defaultBasePort, validateBaseUrlOptions } from '../start-options.js' -const ENV_VARS = ['EMULATE_BASE_URL', 'PORTLESS_URL', 'EMULATE_PORT', 'PORT'] as const -const savedEnv = Object.fromEntries(ENV_VARS.map(name => [name, process.env[name]])) +const savedEnv = { + EMULATE_BASE_URL: process.env.EMULATE_BASE_URL, + PORTLESS_URL: process.env.PORTLESS_URL, + EMULATE_PORT: process.env.EMULATE_PORT, + PORT: process.env.PORT, +} // Clear before each test (not just after) so ambient shell/CI env vars // cannot leak into the first test case. beforeEach(() => { - for (const name of ENV_VARS) { - delete process.env[name] - } + delete process.env.EMULATE_BASE_URL + delete process.env.PORTLESS_URL + delete process.env.EMULATE_PORT + delete process.env.PORT }) afterAll(() => { - for (const name of ENV_VARS) { - if (savedEnv[name] !== undefined) { - process.env[name] = savedEnv[name] + for (const [name, value] of Object.entries(savedEnv)) { + if (value !== undefined) { + Object.assign(process.env, { [name]: value }) } } }) diff --git a/packages/emulate/src/start-options.ts b/packages/emulate/src/start-options.ts index d8d0f06..905e921 100644 --- a/packages/emulate/src/start-options.ts +++ b/packages/emulate/src/start-options.ts @@ -7,8 +7,7 @@ export interface BaseUrlOptions { /** Base port fallback: --port flag > EMULATE_PORT > PORT > 4000. */ export function defaultBasePort(): number { - for (const name of ['EMULATE_PORT', 'PORT']) { - const raw = process.env[name] + for (const raw of [process.env.EMULATE_PORT, process.env.PORT]) { if (raw) { const port = Number.parseInt(raw, 10) if (!Number.isNaN(port)) { diff --git a/skills/linear/SKILL.md b/skills/linear/SKILL.md index cfe9311..2da3c4c 100644 --- a/skills/linear/SKILL.md +++ b/skills/linear/SKILL.md @@ -30,7 +30,8 @@ npx @pleaseai/emulate --service linear A single service starts on the base port (default `4000`). Use `-p ` to change it. When started alongside other services, ports are assigned -sequentially from the base port (linear's slot is `4006`). +sequentially from the base port in the order the services are selected — +when every service starts (the default), linear's is `4006`. Default URL (linear alone): diff --git a/website/src/content/docs/services/linear.md b/website/src/content/docs/services/linear.md index 35c9e8c..d4e0c6a 100644 --- a/website/src/content/docs/services/linear.md +++ b/website/src/content/docs/services/linear.md @@ -34,7 +34,8 @@ npx @pleaseai/emulate --service linear A single service starts on the base port (default `4000`). Use `-p ` to change it. When started alongside other services, ports are assigned -sequentially from the base port (linear's slot is `4006`). +sequentially from the base port in the order the services are selected — +when every service starts (the default), linear's is `4006`. Default URL (linear alone): From e1152544c13119cbee3b21344c65885355fac4e1 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sun, 5 Jul 2026 13:16:20 +0900 Subject: [PATCH 14/15] chore: fix remaining Codacy nits Add braces to a void-returning arrow in the readline mock and hoist the defaultBasePort() call to a const (Codacy misreads the inline call as an HTML-context sink). --- packages/emulate/src/__tests__/portless.test.ts | 4 +++- packages/emulate/src/index.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/emulate/src/__tests__/portless.test.ts b/packages/emulate/src/__tests__/portless.test.ts index 95dd2c4..12491e0 100644 --- a/packages/emulate/src/__tests__/portless.test.ts +++ b/packages/emulate/src/__tests__/portless.test.ts @@ -28,7 +28,9 @@ mock.module('node:child_process', () => ({ mock.module('node:readline', () => ({ createInterface: () => ({ - question: (_q: string, cb: (answer: string) => void) => cb(promptAnswer), + question: (_q: string, cb: (answer: string) => void) => { + cb(promptAnswer) + }, close: () => {}, }), })) diff --git a/packages/emulate/src/index.ts b/packages/emulate/src/index.ts index 1ca6fa4..06df79f 100644 --- a/packages/emulate/src/index.ts +++ b/packages/emulate/src/index.ts @@ -6,6 +6,8 @@ import { listCommand } from './commands/list.js' import { startCommand } from './commands/start.js' import { defaultBasePort } from './start-options.js' +const basePort = defaultBasePort() + const program = new Command() program @@ -16,7 +18,7 @@ program program .command('start', { isDefault: true }) .description('Start service emulators (default command)') - .option('-p, --port ', 'base port (services use port, port+1, ...; falls back to EMULATE_PORT or PORT)', v => Number.parseInt(v, 10), defaultBasePort()) + .option('-p, --port ', 'base port (services use port, port+1, ...; falls back to EMULATE_PORT or PORT)', v => Number.parseInt(v, 10), basePort) .option('-s, --service ', `comma-separated services to start`) .option('--seed ', 'seed config file (yaml or json)') .option('--base-url ', 'override base URL (supports {service} interpolation)') From b645fdb7e936e097613bc430b59dca6cf7d54a9f Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Sun, 5 Jul 2026 13:19:53 +0900 Subject: [PATCH 15/15] chore: move EMULATE_PORT/PORT fallback into startCommand Codacy misflags any variable near the '' help text as an HTML sink; resolving the default inside startCommand keeps index.ts free of the pattern and the behavior identical (flag > env > 4000). --- packages/emulate/src/commands/start.ts | 6 +++--- packages/emulate/src/index.ts | 5 +---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/emulate/src/commands/start.ts b/packages/emulate/src/commands/start.ts index 00b1de1..1241bfa 100644 --- a/packages/emulate/src/commands/start.ts +++ b/packages/emulate/src/commands/start.ts @@ -9,10 +9,10 @@ import { parse as parseYaml } from 'yaml' import { createEmulator } from '../api.js' import { buildAliases, ensurePortless, portlessBaseUrl, registerAliases, removeAliases } from '../portless.js' import { SERVICE_NAMES, SERVICE_REGISTRY } from '../registry.js' -import { validateBaseUrlOptions } from '../start-options.js' +import { defaultBasePort, validateBaseUrlOptions } from '../start-options.js' export interface StartOptions { - port: number + port?: number service?: string seed?: string baseUrl?: string @@ -99,7 +99,7 @@ async function setupPortless(services: ServiceName[], basePort: number): Promise } export async function startCommand(options: StartOptions): Promise { - const { port: basePort } = options + const basePort = options.port ?? defaultBasePort() const loaded = loadSeedConfig(options.seed) const seedConfig = loaded?.config ?? null diff --git a/packages/emulate/src/index.ts b/packages/emulate/src/index.ts index 06df79f..d992b09 100644 --- a/packages/emulate/src/index.ts +++ b/packages/emulate/src/index.ts @@ -4,9 +4,6 @@ import pkg from '../package.json' with { type: 'json' } import { initCommand } from './commands/init.js' import { listCommand } from './commands/list.js' import { startCommand } from './commands/start.js' -import { defaultBasePort } from './start-options.js' - -const basePort = defaultBasePort() const program = new Command() @@ -18,7 +15,7 @@ program program .command('start', { isDefault: true }) .description('Start service emulators (default command)') - .option('-p, --port ', 'base port (services use port, port+1, ...; falls back to EMULATE_PORT or PORT)', v => Number.parseInt(v, 10), basePort) + .option('-p, --port ', 'base port, default 4000 (services use port, port+1, ...; falls back to EMULATE_PORT or PORT)', v => Number.parseInt(v, 10)) .option('-s, --service ', `comma-separated services to start`) .option('--seed ', 'seed config file (yaml or json)') .option('--base-url ', 'override base URL (supports {service} interpolation)')