Compare commits

..

39 Commits

Author SHA1 Message Date
Samy Pessé 27532a5f41 Version Packages (#2988)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-24 17:43:16 +01:00
Nolann B. 48c18c03b9 Add option to pass scalar plugins (#3027) 2025-03-24 14:11:14 +01:00
Samy Pessé bba2e52e24 Fix support for site redirects that include section/variant paths (#3024) 2025-03-24 10:21:26 +01:00
Valentino Hudhra 54ee0149e1 Add support for custom fonts (#2981) 2025-03-24 08:20:58 +01:00
Greg Bergé 8fda9d2fab Update Argos (#3025) 2025-03-22 23:00:25 +01:00
Samy Pessé 77a72d79ce Update next.js to latest v14 and v15 (#3026) 2025-03-22 20:02:15 +01:00
Nolann B. 73e2b472ef Fix write only properties in request example (#3023) 2025-03-21 19:09:25 +01:00
Samy Pessé 2f28cc1323 v2: improve link and navigation between content in a same site (#3020) 2025-03-21 18:18:34 +01:00
Greg Bergé f5516d4e21 Improve OpenAPI TOC display (#3022) 2025-03-21 14:29:12 +00:00
Greg Bergé d2facb2db1 Mark OpenAPI properties as optional if not required (#3021) 2025-03-21 14:28:53 +00:00
Greg Bergé 6eae764b7c Add support for multiple body content types / examples (#2991) 2025-03-21 14:23:42 +01:00
Greg Bergé 970ef8f886 Add missing token (#3019) 2025-03-21 11:04:57 +01:00
Nolann B. ed0720699e Fix OpenAPI path overflow on mobile (#3018) 2025-03-21 11:02:32 +01:00
Greg Bergé ad94d00d5f Improve markdown parsing (#3017) 2025-03-21 10:53:01 +01:00
Greg Bergé c7843f7f52 Remove bun cache (#3016) 2025-03-21 10:42:57 +01:00
Greg Bergé 1fe3286bdb Fix OpenAPI overflow issue (#3015) 2025-03-21 10:42:09 +01:00
Samy Pessé ce030fdf76 v2: Fix redirect to strip jwt_token in url-host mode (#3014) 2025-03-21 00:46:39 +01:00
Samy Pessé 9ba8783952 v2: Fix the redirection when stripping the jwt_token from url (#3013) 2025-03-21 00:25:34 +01:00
Greg Bergé 2f3966cc53 Fix icons flaky (#3012) 2025-03-20 21:08:46 +00:00
Samy Pessé 8f65a22e13 Accept API token in query parameters for normal v2 routes (#3011) 2025-03-20 19:54:55 +01:00
Greg Bergé 2787d884cd Stabilize images & icons (#3009) 2025-03-20 19:40:42 +01:00
Nolann B. fc00b51683 Remove default value in generateSchemaExample (#3007) 2025-03-20 17:24:23 +01:00
Samy Pessé f80c3a7b37 v2: Fix display of integration blocks (#3010) 2025-03-20 15:56:31 +00:00
Samy Pessé 4e5d6c7487 v2: use static route for VA / adaptive content (#2998) 2025-03-20 15:41:17 +01:00
Greg Bergé 96f84b5917 Fix visual tests v2 (#3006) 2025-03-20 11:34:04 +01:00
Greg Bergé 6b8a5fdd1b Trigger tests (#3005) 2025-03-20 10:00:09 +00:00
Greg Bergé 89083ac790 Stabilize visual tests (#3001) 2025-03-20 09:22:09 +01:00
Nolann B. 70be2c6b2b Stringify default value (#3003) 2025-03-19 21:28:40 +01:00
Samy Pessé bcf877841b Update to latest version of the GitBook API (#3002) 2025-03-19 20:11:44 +01:00
Nolann B. a84b06bb19 Update resolveDescription and add minItems/maxItems (#3000) 2025-03-19 17:21:35 +01:00
Zeno Kapitein 24b7808d9a Fix prominent search bar width on md screens (#2996) 2025-03-19 17:14:07 +01:00
Greg Bergé 8364ee988e Fix Argos flakies (#2999) 2025-03-19 16:04:45 +01:00
Samy Pessé 07f6e2d233 Pass page.computedSeed to getComputedDocument (#2995) 2025-03-19 15:02:49 +01:00
Greg Bergé 7212973f34 Update Scalar client (#2993) 2025-03-18 16:02:40 +01:00
Greg Bergé af44e0436b fix typo (#2992) 2025-03-18 14:20:23 +01:00
Greg Bergé 3f9c35d505 Fix emoji display on other platforms than Mac OS (#2990) 2025-03-17 17:49:13 +01:00
Zeno Kapitein 2c13f8b95c Fix horizontal overscroll issue on large screens (#2989) 2025-03-17 15:38:44 +01:00
Zeno Kapitein 4dab1c56e5 Fix alignment of prominent search bar on full-width pages (#2987) 2025-03-17 15:02:23 +01:00
Zeno Kapitein 4723f03ef1 Restyle section group dropdown (#2985) 2025-03-17 13:22:54 +01:00
119 changed files with 3085 additions and 1542 deletions
@@ -50,6 +50,7 @@ runs:
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
- name: Build worker
run: bun run turbo build:v2:cloudflare
shell: bash
@@ -55,6 +55,7 @@ runs:
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
- name: Build Project Artifacts
run: bun run vercel build --target=${{ inputs.environment }} --token=${{ inputs.vercelToken }}
shell: bash
@@ -74,3 +75,4 @@ runs:
shell: bash
run: |
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
-9
View File
@@ -3,15 +3,6 @@ description: 'Install Bun and cache dependencies'
runs:
using: 'composite'
steps:
# We cache dependencies even if Bun is fast to avoid downloading fontawesome too many times
# and impacting bandwidth there.
- name: Cache bun
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
+3
View File
@@ -5,6 +5,9 @@ on:
branches:
- main
env:
NPM_TOKEN_READONLY: ${{ secrets.NPM_TOKEN_READONLY }}
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
+3
View File
@@ -13,5 +13,8 @@
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit",
"source.fixAll.biome": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
+230 -104
View File
@@ -24,7 +24,7 @@
},
"packages/cache-tags": {
"name": "@gitbook/cache-tags",
"version": "0.1.0",
"version": "0.2.0",
"dependencies": {
"@gitbook/api": "*",
"assert-never": "^1.2.1",
@@ -35,7 +35,7 @@
},
"packages/colors": {
"name": "@gitbook/colors",
"version": "0.2.0",
"version": "0.3.0",
"devDependencies": {
"typescript": "^5.5.3",
},
@@ -49,7 +49,7 @@
},
"packages/gitbook": {
"name": "gitbook",
"version": "0.7.3",
"version": "0.8.1",
"dependencies": {
"@gitbook/api": "*",
"@gitbook/cache-do": "workspace:*",
@@ -81,9 +81,7 @@
"mathjax": "^3.2.2",
"mdast-util-to-markdown": "^2.1.2",
"memoizee": "^0.4.15",
"micromark": "^4.0.1",
"micromark-extension-gfm": "^3.0.0",
"next": "14.2.23",
"next": "14.2.25",
"next-themes": "^0.2.1",
"nuqs": "^2.2.3",
"object-hash": "^3.0.0",
@@ -93,19 +91,25 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"react-hotkeys-hook": "^4.4.1",
"rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
"rison": "^0.1.1",
"server-only": "^0.0.1",
"shiki": "^3.2.0",
"tailwind-merge": "^2.2.0",
"tailwind-shades": "^1.1.2",
"unified": "^11.0.5",
"url-join": "^5.0.0",
"usehooks-ts": "^3.1.0",
},
"devDependencies": {
"@argos-ci/playwright": "^3.10.0",
"@argos-ci/playwright": "^4.3.0",
"@cloudflare/next-on-pages": "1.13.7",
"@cloudflare/workers-types": "^4.20241230.0",
"@playwright/test": "^1.49.1",
"@playwright/test": "^1.51.1",
"@types/js-cookie": "^3.0.6",
"@types/jsontoxml": "^1.0.5",
"@types/jsonwebtoken": "^9.0.6",
@@ -123,6 +127,7 @@
"jsonwebtoken": "^9.0.2",
"postcss": "^8",
"psi": "^4.1.0",
"stylelint": "^16.16.0",
"tailwindcss": "^3.4.0",
"ts-essentials": "^10.0.1",
"typescript": "^5.5.3",
@@ -131,19 +136,22 @@
},
"packages/gitbook-v2": {
"name": "gitbook-v2",
"version": "0.2.0",
"version": "0.2.1",
"dependencies": {
"@gitbook/api": "*",
"@gitbook/cache-tags": "workspace:*",
"@sindresorhus/fnv1a": "^3.1.0",
"next": "^15.2.1",
"jwt-decode": "^4.0.0",
"next": "^15.2.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"rison": "^0.1.1",
"server-only": "^0.0.1",
"warn-once": "^0.1.1",
},
"devDependencies": {
"@opennextjs/cloudflare": "^0.5.10",
"@types/rison": "^0.0.9",
"gitbook": "*",
"postcss": "^8",
"tailwindcss": "^3.4.0",
@@ -153,7 +161,7 @@
"name": "@gitbook/icons",
"version": "0.2.0",
"bin": {
"gitbook-icons": "./bin/gitbook-icons.js",
"gitbook-icons": "./bin/gitbook-icons.js"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.6.0",
@@ -173,7 +181,7 @@
"name": "@gitbook/openapi-parser",
"version": "2.1.0",
"dependencies": {
"@scalar/openapi-parser": "^0.10.9",
"@scalar/openapi-parser": "^0.10.10",
"@scalar/openapi-types": "^0.1.9",
},
"devDependencies": {
@@ -193,7 +201,7 @@
},
"packages/react-contentkit": {
"name": "@gitbook/react-contentkit",
"version": "0.6.2",
"version": "0.7.0",
"dependencies": {
"@gitbook/api": "*",
"@gitbook/icons": "workspace:*",
@@ -210,7 +218,7 @@
"name": "@gitbook/react-math",
"version": "0.6.0",
"bin": {
"gitbook-math": "./bin/gitbook-math.js",
"gitbook-math": "./bin/gitbook-math.js"
},
"dependencies": {
"object-hash": "^3.0.0",
@@ -225,11 +233,11 @@
},
"packages/react-openapi": {
"name": "@gitbook/react-openapi",
"version": "1.1.3",
"version": "1.1.5",
"dependencies": {
"@gitbook/openapi-parser": "workspace:*",
"@scalar/api-client-react": "^1.1.36",
"@scalar/oas-utils": "^0.2.110",
"@scalar/api-client-react": "^1.2.5",
"@scalar/oas-utils": "^0.2.120",
"clsx": "^2.1.1",
"flatted": "^3.2.9",
"json-xml-parse": "^1.3.0",
@@ -253,7 +261,7 @@
},
"overrides": {
"@codemirror/state": "6.4.1",
"@gitbook/api": "0.99.0",
"@gitbook/api": "0.102.0",
"react": "18.3.1",
"react-dom": "18.3.1",
},
@@ -268,15 +276,15 @@
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@argos-ci/api-client": ["@argos-ci/api-client@0.7.2", "", { "dependencies": { "debug": "^4.4.0", "openapi-fetch": "0.13.4" } }, "sha512-ojKOhb4CXugJcHNzGH4Gp7G6gqqBEbhO2B5FNfU/ieBCDsYY+urE0hQ5L7+4TfmMoKb/xVpvtu2mQdEpMt4JVg=="],
"@argos-ci/api-client": ["@argos-ci/api-client@0.8.0", "", { "dependencies": { "debug": "^4.4.0", "openapi-fetch": "0.13.4" } }, "sha512-UHa1vAf8gwHVpkqM/RaSryrFe1juqWH6dHpPeMtT4e/ZMB9hNYwYFinaGq/KRWe88JEi2WeAu776YdoeUSZQkQ=="],
"@argos-ci/browser": ["@argos-ci/browser@2.2.2", "", {}, "sha512-pxoUKAVA/3whUKZU1BV93vI4JBYD6utKOd8yfMdZk70kRBL+ZEY+wIPDgQCXiDk02/od64xTn3YiD+N20V45RA=="],
"@argos-ci/browser": ["@argos-ci/browser@3.2.0", "", {}, "sha512-mHLUamfywbdzrM6SRV4KB4kEQs2lrFxC1yS+QWB/9lME7FKzr7MJW/Q++iWcQli8IbmtLsZ+PqP2eqLaDiRsUw=="],
"@argos-ci/core": ["@argos-ci/core@3.0.0", "", { "dependencies": { "@argos-ci/api-client": "0.7.2", "@argos-ci/util": "2.2.2", "axios": "^1.7.9", "convict": "^6.2.4", "debug": "^4.4.0", "fast-glob": "^3.3.3", "sharp": "^0.33.5", "tmp": "^0.2.3" } }, "sha512-00xNAPwE8O7sKCgn9oeiKGE0cyZYuC3ouCxN+DBAMqZLIG+8zoUZUuB+ZJsKwjMHA9un/bzFX9l8z2Z8rgkQ7w=="],
"@argos-ci/core": ["@argos-ci/core@3.1.0", "", { "dependencies": { "@argos-ci/api-client": "0.8.0", "@argos-ci/util": "2.3.0", "axios": "^1.7.9", "convict": "^6.2.4", "debug": "^4.4.0", "fast-glob": "^3.3.3", "sharp": "^0.33.5", "tmp": "^0.2.3" } }, "sha512-bo/pNKk6P0pz4NRdymgU1letwQrRbMPTeFyMsUEW8fhKNdesSFnFIWZBFGsGkkh05uw75PBjl2ZN4PvQ2TxSog=="],
"@argos-ci/playwright": ["@argos-ci/playwright@3.10.0", "", { "dependencies": { "@argos-ci/browser": "2.2.2", "@argos-ci/core": "3.0.0", "@argos-ci/util": "2.2.2", "chalk": "^5.4.1", "debug": "^4.4.0" } }, "sha512-kL83M8+KJ+A3bVbAWwPUMZNVlzP1FZMi3E5Y8Er4LnpqAutkbmL+YNeSamgX61WjOS8Zq3tIwf9923/rWvEkiQ=="],
"@argos-ci/playwright": ["@argos-ci/playwright@4.3.0", "", { "dependencies": { "@argos-ci/browser": "3.2.0", "@argos-ci/core": "3.1.0", "@argos-ci/util": "2.3.0", "chalk": "^5.4.1", "debug": "^4.4.0" } }, "sha512-UQBC78zWg+bWXDaevWOL4m6zkrNeprMfdqjy6zagvU9kq7sPQC7nN/qZQ15hs/FT+JpTCa8CbbSAq13eQrmxKQ=="],
"@argos-ci/util": ["@argos-ci/util@2.2.2", "", {}, "sha512-MpKAz/3dMNjsUO49sEMYfw+qZFUrEeji8EmSbr0rgL8bw+Q7hIYNesRtEIQnFPub0FgX1/AV3sRZ9yqWTgGgUw=="],
"@argos-ci/util": ["@argos-ci/util@2.3.0", "", {}, "sha512-tkxnCpaj7yN9nCFzo9MX0FJ5YjUepEOGYfdvF8COQqp+EdY1qubOPpc4Z0l1B60BlC8YtjQv/oRxHSh1XzxWFg=="],
"@ast-grep/napi": ["@ast-grep/napi@0.34.3", "", { "optionalDependencies": { "@ast-grep/napi-darwin-arm64": "0.34.3", "@ast-grep/napi-darwin-x64": "0.34.3", "@ast-grep/napi-linux-arm64-gnu": "0.34.3", "@ast-grep/napi-linux-arm64-musl": "0.34.3", "@ast-grep/napi-linux-x64-gnu": "0.34.3", "@ast-grep/napi-linux-x64-musl": "0.34.3", "@ast-grep/napi-win32-arm64-msvc": "0.34.3", "@ast-grep/napi-win32-ia32-msvc": "0.34.3", "@ast-grep/napi-win32-x64-msvc": "0.34.3" } }, "sha512-2yrnMrUw3NVm9hf+YKO+BOY3Aci/qau2vDo0lGtA7qGMma18XPUIOTdzm601k5gPHo4MfxPPZLoe9QdTUviANg=="],
@@ -508,8 +516,18 @@
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
"@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@3.0.4", "", { "peerDependencies": { "@csstools/css-tokenizer": "^3.0.3" } }, "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A=="],
"@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.3", "", {}, "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw=="],
"@csstools/media-query-list-parser": ["@csstools/media-query-list-parser@4.0.2", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3" } }, "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A=="],
"@csstools/selector-specificity": ["@csstools/selector-specificity@5.0.0", "", { "peerDependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw=="],
"@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.31.0", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^16.4.5", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.2", "which": "^4.0.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js", "git-dotenvx": "src/cli/dotenvx.js" } }, "sha512-GeDxvtjiRuoyWVU9nQneId879zIyNdL05bS7RKiqMkfBSKpHMWHLoRyRqjYWLaXmX/llKO1hTlqHDmatkQAjPA=="],
"@dual-bundle/import-meta-resolve": ["@dual-bundle/import-meta-resolve@4.1.0", "", {}, "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg=="],
"@ecies/ciphers": ["@ecies/ciphers@0.2.2", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg=="],
"@edge-runtime/format": ["@edge-runtime/format@2.2.1", "", {}, "sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g=="],
@@ -604,7 +622,7 @@
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
"@gitbook/api": ["@gitbook/api@0.99.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-qaHNCKul6rp0wqKRQ/2/2+X8mNEmy5ZK4vWuZoI9eL306TTqFBWD4DSTHz0WV6X24FVe3HF7qy/mVRd4ja2I2A=="],
"@gitbook/api": ["@gitbook/api@0.102.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-9wPr5kyCHhTTwkaCYEMT4q3JjEl1q9mytgvpeusmeREfG4E0RZHYPEL8bmumO7T9wH/0vHjpfW2LSTUUs60AwQ=="],
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
@@ -704,6 +722,8 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@keyv/serialize": ["@keyv/serialize@1.0.3", "", { "dependencies": { "buffer": "^6.0.3" } }, "sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g=="],
"@lezer/common": ["@lezer/common@1.2.3", "", {}, "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="],
"@lezer/css": ["@lezer/css@1.1.9", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA=="],
@@ -730,25 +750,25 @@
"@msgpack/msgpack": ["@msgpack/msgpack@3.0.0-beta2", "", {}, "sha512-y+l1PNV0XDyY8sM3YtuMLK5vE3/hkfId+Do8pLo/OPxfxuFAUwcGz3oiiUuV46/aBpwTzZ+mRWVMtlSKbradhw=="],
"@next/env": ["@next/env@14.2.23", "", {}, "sha512-CysUC9IO+2Bh0omJ3qrb47S8DtsTKbFidGm6ow4gXIG6reZybqxbkH2nhdEm1tC8SmgzDdpq3BIML0PWsmyUYA=="],
"@next/env": ["@next/env@14.2.25", "", {}, "sha512-JnzQ2cExDeG7FxJwqAksZ3aqVJrHjFwZQAEJ9gQZSoEhIow7SNoKZzju/AwQ+PLIR4NY8V0rhcVozx/2izDO0w=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@14.2.23", "", { "os": "darwin", "cpu": "arm64" }, "sha512-WhtEntt6NcbABA8ypEoFd3uzq5iAnrl9AnZt9dXdO+PZLACE32z3a3qA5OoV20JrbJfSJ6Sd6EqGZTrlRnGxQQ=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@14.2.25", "", { "os": "darwin", "cpu": "arm64" }, "sha512-09clWInF1YRd6le00vt750s3m7SEYNehz9C4PUcSu3bAdCTpjIV4aTYQZ25Ehrr83VR1rZeqtKUPWSI7GfuKZQ=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@14.2.23", "", { "os": "darwin", "cpu": "x64" }, "sha512-vwLw0HN2gVclT/ikO6EcE+LcIN+0mddJ53yG4eZd0rXkuEr/RnOaMH8wg/sYl5iz5AYYRo/l6XX7FIo6kwbw1Q=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@14.2.25", "", { "os": "darwin", "cpu": "x64" }, "sha512-V+iYM/QR+aYeJl3/FWWU/7Ix4b07ovsQ5IbkwgUK29pTHmq+5UxeDr7/dphvtXEq5pLB/PucfcBNh9KZ8vWbug=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@14.2.23", "", { "os": "linux", "cpu": "arm64" }, "sha512-uuAYwD3At2fu5CH1wD7FpP87mnjAv4+DNvLaR9kiIi8DLStWSW304kF09p1EQfhcbUI1Py2vZlBO2VaVqMRtpg=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@14.2.25", "", { "os": "linux", "cpu": "arm64" }, "sha512-LFnV2899PJZAIEHQ4IMmZIgL0FBieh5keMnriMY1cK7ompR+JUd24xeTtKkcaw8QmxmEdhoE5Mu9dPSuDBgtTg=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@14.2.23", "", { "os": "linux", "cpu": "arm64" }, "sha512-Mm5KHd7nGgeJ4EETvVgFuqKOyDh+UMXHXxye6wRRFDr4FdVRI6YTxajoV2aHE8jqC14xeAMVZvLqYqS7isHL+g=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@14.2.25", "", { "os": "linux", "cpu": "arm64" }, "sha512-QC5y5PPTmtqFExcKWKYgUNkHeHE/z3lUsu83di488nyP0ZzQ3Yse2G6TCxz6nNsQwgAx1BehAJTZez+UQxzLfw=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@14.2.23", "", { "os": "linux", "cpu": "x64" }, "sha512-Ybfqlyzm4sMSEQO6lDksggAIxnvWSG2cDWnG2jgd+MLbHYn2pvFA8DQ4pT2Vjk3Cwrv+HIg7vXJ8lCiLz79qoQ=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@14.2.25", "", { "os": "linux", "cpu": "x64" }, "sha512-y6/ML4b9eQ2D/56wqatTJN5/JR8/xdObU2Fb1RBidnrr450HLCKr6IJZbPqbv7NXmje61UyxjF5kvSajvjye5w=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@14.2.23", "", { "os": "linux", "cpu": "x64" }, "sha512-OSQX94sxd1gOUz3jhhdocnKsy4/peG8zV1HVaW6DLEbEmRRtUCUQZcKxUD9atLYa3RZA+YJx+WZdOnTkDuNDNA=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@14.2.25", "", { "os": "linux", "cpu": "x64" }, "sha512-sPX0TSXHGUOZFvv96GoBXpB3w4emMqKeMgemrSxI7A6l55VBJp/RKYLwZIB9JxSqYPApqiREaIIap+wWq0RU8w=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@14.2.23", "", { "os": "win32", "cpu": "arm64" }, "sha512-ezmbgZy++XpIMTcTNd0L4k7+cNI4ET5vMv/oqNfTuSXkZtSA9BURElPFyarjjGtRgZ9/zuKDHoMdZwDZIY3ehQ=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@14.2.25", "", { "os": "win32", "cpu": "arm64" }, "sha512-ReO9S5hkA1DU2cFCsGoOEp7WJkhFzNbU/3VUF6XxNGUCQChyug6hZdYL/istQgfT/GWE6PNIg9cm784OI4ddxQ=="],
"@next/swc-win32-ia32-msvc": ["@next/swc-win32-ia32-msvc@14.2.23", "", { "os": "win32", "cpu": "ia32" }, "sha512-zfHZOGguFCqAJ7zldTKg4tJHPJyJCOFhpoJcVxKL9BSUHScVDnMdDuOU1zPPGdOzr/GWxbhYTjyiEgLEpAoFPA=="],
"@next/swc-win32-ia32-msvc": ["@next/swc-win32-ia32-msvc@14.2.25", "", { "os": "win32", "cpu": "ia32" }, "sha512-DZ/gc0o9neuCDyD5IumyTGHVun2dCox5TfPQI/BJTYwpSNYM3CZDI4i6TOdjeq1JMo+Ug4kPSMuZdwsycwFbAw=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@14.2.23", "", { "os": "win32", "cpu": "x64" }, "sha512-xCtq5BD553SzOgSZ7UH5LH+OATQihydObTrCTvVzOro8QiWYKdBVwcB2Mn2MLMo6DGW9yH1LSPw7jS7HhgJgjw=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@14.2.25", "", { "os": "win32", "cpu": "x64" }, "sha512-KSznmS6eFjQ9RJ1nEc66kJvtGIL1iZMYmGEXsZPh2YtnLtqrgdVvKXJY2ScjjoFnG6nGLyPFR0UiEvDwVah4Tw=="],
"@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="],
@@ -776,7 +796,7 @@
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@playwright/test": ["@playwright/test@1.49.1", "", { "dependencies": { "playwright": "1.49.1" }, "bin": { "playwright": "cli.js" } }, "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g=="],
"@playwright/test": ["@playwright/test@1.51.1", "", { "dependencies": { "playwright": "1.51.1" }, "bin": { "playwright": "cli.js" } }, "sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q=="],
"@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="],
@@ -1048,39 +1068,39 @@
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "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-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
"@scalar/api-client": ["@scalar/api-client@2.2.56", "", { "dependencies": { "@headlessui/tailwindcss": "^0.2.0", "@headlessui/vue": "^1.7.20", "@scalar/components": "0.13.28", "@scalar/draggable": "0.1.11", "@scalar/icons": "0.1.3", "@scalar/import": "0.2.30", "@scalar/oas-utils": "0.2.110", "@scalar/object-utils": "1.1.13", "@scalar/openapi-parser": "0.10.9", "@scalar/openapi-types": "0.1.9", "@scalar/postman-to-openapi": "0.1.33", "@scalar/snippetz": "0.2.15", "@scalar/themes": "0.9.71", "@scalar/types": "0.0.36", "@scalar/use-codemirror": "0.11.73", "@scalar/use-hooks": "0.1.25", "@scalar/use-toasts": "0.7.9", "@scalar/use-tooltip": "1.0.6", "@vueuse/core": "^10.10.0", "@vueuse/integrations": "^11.2.0", "focus-trap": "^7", "fuse.js": "^7.0.0", "microdiff": "^1.4.0", "nanoid": "^5.0.9", "pretty-bytes": "^6.1.1", "pretty-ms": "^8.0.0", "shell-quote": "^1.8.1", "vue": "^3.5.12", "vue-router": "^4.3.0", "whatwg-mimetype": "^4.0.0", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-ilY0geYUg90xVameaQ3IJm1/gO9MOmvcQEm98/IfizoYJ5P9aFnXphwEvR+K0xeSD6QYXdaz4ir5UB31Jkuueg=="],
"@scalar/api-client": ["@scalar/api-client@2.3.5", "", { "dependencies": { "@headlessui/tailwindcss": "^0.2.0", "@headlessui/vue": "^1.7.20", "@scalar/components": "0.13.37", "@scalar/draggable": "0.1.11", "@scalar/icons": "0.1.3", "@scalar/import": "0.3.2", "@scalar/oas-utils": "0.2.120", "@scalar/object-utils": "1.1.13", "@scalar/openapi-parser": "0.10.10", "@scalar/openapi-types": "0.1.9", "@scalar/postman-to-openapi": "0.1.43", "@scalar/snippetz": "0.2.16", "@scalar/themes": "0.9.79", "@scalar/types": "0.1.1", "@scalar/use-codemirror": "0.11.82", "@scalar/use-hooks": "0.1.33", "@scalar/use-toasts": "0.7.9", "@scalar/use-tooltip": "1.0.6", "@vueuse/core": "^10.10.0", "@vueuse/integrations": "^11.2.0", "focus-trap": "^7", "fuse.js": "^7.0.0", "microdiff": "^1.4.0", "nanoid": "^5.0.9", "pretty-bytes": "^6.1.1", "pretty-ms": "^8.0.0", "shell-quote": "^1.8.1", "vue": "^3.5.12", "vue-router": "^4.3.0", "whatwg-mimetype": "^4.0.0", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-bVBP8H3laa4VG3dExv4Ak/Kf+q9z8aK/Glpv6CPE0wxgZJWxwkJoSrvwxUE+46JktSObUrhu4xQiqSCmBz8esQ=="],
"@scalar/api-client-react": ["@scalar/api-client-react@1.1.36", "", { "dependencies": { "@scalar/api-client": "2.2.56", "vue": "^3.5.12" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-PRogjHFP0bGe6Q1AOxHOctdMpehWLdL8Wo7GtGsFVKXPqcO/tgAZN8x61Qls3SYT5SDqENk8cwAmSaivLzfD2Q=="],
"@scalar/api-client-react": ["@scalar/api-client-react@1.2.5", "", { "dependencies": { "@scalar/api-client": "2.3.5", "@scalar/types": "0.1.1", "vue": "^3.5.12" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-DpVDWb1rtNeU8ZqLX2Duos018q/++b3qIOYs7MzB2cXNg1802ZO7tPywXbYyizxLuy/dziI3UwGkjBuOKKXMGQ=="],
"@scalar/code-highlight": ["@scalar/code-highlight@0.0.23", "", { "dependencies": { "hast-util-to-text": "^4.0.2", "highlight.js": "^11.9.0", "highlightjs-curl": "^1.3.0", "highlightjs-vue": "^1.0.0", "lowlight": "^3.1.0", "rehype-external-links": "^3.0.0", "rehype-format": "^5.0.0", "rehype-parse": "^9.0.0", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "rehype-stringify": "^10.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", "remark-stringify": "^11.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-hDHoUgjq/8ntXzmD1hbO3nvem1ny+qcWRH7L2nOflwkXhXaDM1aOPNp9zyeCxLoWwC9db4wLFP3QW+eYS0OTiQ=="],
"@scalar/code-highlight": ["@scalar/code-highlight@0.0.25", "", { "dependencies": { "hast-util-to-text": "^4.0.2", "highlight.js": "^11.9.0", "highlightjs-curl": "^1.3.0", "highlightjs-vue": "^1.0.0", "lowlight": "^3.1.0", "rehype-external-links": "^3.0.0", "rehype-format": "^5.0.0", "rehype-parse": "^9.0.0", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "rehype-stringify": "^10.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", "remark-stringify": "^11.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-rmiXaAoL3Zl+OycIO1CMj8apaeAU/p41EmCpHTxInZiFVW0++iClce2fun1lK6qjTMZneR6UwE4qBKiUUVLCpg=="],
"@scalar/components": ["@scalar/components@0.13.28", "", { "dependencies": { "@floating-ui/utils": "^0.2.2", "@floating-ui/vue": "^1.0.2", "@headlessui/vue": "^1.7.20", "@scalar/code-highlight": "0.0.23", "@scalar/themes": "0.9.71", "@scalar/use-hooks": "0.1.25", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "cva": "1.0.0-beta.2", "nanoid": "^5.0.9", "pretty-bytes": "^6.1.1", "radix-vue": "^1.9.3", "tailwind-merge": "^2.5.5", "vue": "^3.5.12" } }, "sha512-trG/VuXSw/Y/q3vuE2xRrFXBQ1eJcCQPQUNsfyKXoMUzwJnuWn/LNqdYvq8rnQUwVrGWOReKjYqJ14XUUALr8Q=="],
"@scalar/components": ["@scalar/components@0.13.37", "", { "dependencies": { "@floating-ui/utils": "^0.2.2", "@floating-ui/vue": "^1.0.2", "@headlessui/vue": "^1.7.20", "@scalar/code-highlight": "0.0.25", "@scalar/themes": "0.9.79", "@scalar/use-hooks": "0.1.33", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "cva": "1.0.0-beta.2", "nanoid": "^5.0.9", "pretty-bytes": "^6.1.1", "radix-vue": "^1.9.3", "tailwind-merge": "^2.5.5", "vue": "^3.5.12" } }, "sha512-bhJxg0I63nUH0qoZgb8nyHKCSzL8L9widP2WIYymIvXpCFLwCvF64Z0CAbihwgXxq0YblPvNM+g5N3dRtmXqdA=="],
"@scalar/draggable": ["@scalar/draggable@0.1.11", "", { "dependencies": { "vue": "^3.5.12" } }, "sha512-EQW9N1+mDORhsbjdtCI3XDvmUKsuKw1uf6r3kT1Mm2zQKT+rWwA0ChsAkEN6OG62C0YumMuXpH71h1seAWptxw=="],
"@scalar/icons": ["@scalar/icons@0.1.3", "", { "dependencies": { "vue": "^3.5.12" } }, "sha512-Bl46u7WsJ7NYjW1Fva7SMvw9c/92pGBP8B68tvDc+QevQ04DVNxw6+ny1NU/PnLtpuu1rUpPdtSCAkV1OdQGZQ=="],
"@scalar/import": ["@scalar/import@0.2.30", "", { "dependencies": { "@scalar/oas-utils": "0.2.110", "@scalar/openapi-parser": "0.10.9", "yaml": "^2.4.5" } }, "sha512-K2Gnbpo4aHHcadY+4i3gzmG3XGs6AbhvbP6wOPm8ubb+ZeFkRs79AUJcNTXQ6IWq0TuGNsFUmMxmc1Bvxns4uQ=="],
"@scalar/import": ["@scalar/import@0.3.2", "", { "dependencies": { "@scalar/oas-utils": "0.2.120", "@scalar/openapi-parser": "0.10.10", "yaml": "^2.4.5" } }, "sha512-de7IDZgEYOhhgaq8lFFbfiJ4Hx/ITIXvuLA8SfSl5UQKg+LxiOnnn/5PSKF+pnjWmUf7Kz/ds0eHOXpZv5iMkw=="],
"@scalar/oas-utils": ["@scalar/oas-utils@0.2.110", "", { "dependencies": { "@hyperjump/json-schema": "^1.9.6", "@scalar/object-utils": "1.1.13", "@scalar/openapi-types": "0.1.9", "@scalar/themes": "0.9.71", "@scalar/types": "0.0.36", "flatted": "^3.3.1", "microdiff": "^1.4.0", "nanoid": "^5.0.9", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-fjysWW4VIjR0+rlTOoii2g9+oVbgSVx4TaKGDOAN6S9YxQLz3dgu6RwfpLA9tomdiTZ5EuNMyu4o+X316jaPlw=="],
"@scalar/oas-utils": ["@scalar/oas-utils@0.2.120", "", { "dependencies": { "@hyperjump/json-schema": "^1.9.6", "@scalar/object-utils": "1.1.13", "@scalar/openapi-types": "0.1.9", "@scalar/themes": "0.9.79", "@scalar/types": "0.1.1", "flatted": "^3.3.1", "microdiff": "^1.4.0", "nanoid": "^5.0.9", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-npu0uLClqqXVZfxMdKBWxkWCmONK0jKaUcfmVhGza9Jij5aJyvdfDw6vH/Hh+DghgECwAvQLQeIBZTxjr9ufzg=="],
"@scalar/object-utils": ["@scalar/object-utils@1.1.13", "", { "dependencies": { "flatted": "^3.3.1", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1" } }, "sha512-311eTykIXgOtjCs4VTELj9UMT97jHTWc5qkGNoIzZ5nxjCcvOVe7kDQobIkE8dGT+ybOgHz5qly02Eu7nVHeZQ=="],
"@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.9", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-wsKZtL4B3Fmbv24B1zzeWdsEr6F60fLmToOFLA1b9QGQ6TAtIvLgKQWQ65eh5Vfx93nQb/iekamfErqHRHtEhA=="],
"@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.10", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-6MSgvpNKu/anZy96dn8tXQZo1PuDCoeB4m2ZLLDS4vC2zaTnuNBvvQHx+gjwXNKWhTbIVy8bQpYBzlMAYnFNcQ=="],
"@scalar/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="],
"@scalar/postman-to-openapi": ["@scalar/postman-to-openapi@0.1.33", "", { "dependencies": { "@scalar/oas-utils": "0.2.110", "@scalar/openapi-types": "0.1.9" } }, "sha512-nHZpR5WG+I8BPQMgHyeiS6q/SVt/5FnnAwU9RDxRwlBb9RQqNepAs0sNKc6RRMLgpHYPalRqNwneW5Vu8hlOaA=="],
"@scalar/postman-to-openapi": ["@scalar/postman-to-openapi@0.1.43", "", { "dependencies": { "@scalar/oas-utils": "0.2.120", "@scalar/openapi-types": "0.1.9" } }, "sha512-gLOkYYPCTKYFBOwyBOKZDc0seZjntmwPTchJUr3oxGQmLB1Y5VBJ+8fXJCTp5TwKiiztjALJs79y9s7jXBdWMA=="],
"@scalar/snippetz": ["@scalar/snippetz@0.2.15", "", { "dependencies": { "stringify-object": "^5.0.0" } }, "sha512-2Tq+Ot09dz7ioKNUXW5z6eBqZdMLaYUw5eDONpdvmBP4yZ8hrFKpq0YV54lnMQALQebSi/DPLsnsWgo+DTkfbw=="],
"@scalar/snippetz": ["@scalar/snippetz@0.2.16", "", { "dependencies": { "stringify-object": "^5.0.0" } }, "sha512-xtIY4kvV619IF2uXg6fDw7emtXwuJeWzLunGAaUTIOwNRw5mGSKqJnLcSnIiSdH54YmN8D2CtdJRo2VxPP9/Wg=="],
"@scalar/themes": ["@scalar/themes@0.9.71", "", { "dependencies": { "@scalar/types": "0.0.36" } }, "sha512-sgXCEV1ZS9X5Wxs3ZjCFqUAQ11dW3fupf5tVt0A3sBQI3LbKp52aCa8Z6UqVOoohwD5zVGSFcoWD5iAZEgldWg=="],
"@scalar/themes": ["@scalar/themes@0.9.79", "", { "dependencies": { "@scalar/types": "0.1.1" } }, "sha512-zWiHCZAIjPGa8X9o/NORBPRMTMblLEz2+2RcfW9yIKNO/8H4Gz0rltiGGlJ6vX0o+qHwx7AdgfY+7njmWQR4ng=="],
"@scalar/types": ["@scalar/types@0.0.36", "", { "dependencies": { "@scalar/openapi-types": "0.1.9", "@unhead/schema": "^1.11.11" } }, "sha512-Mfv9IptUoGqDJC40xg9nfI9/3brov59tKw2lq8tPpbmXItyG9EplckHPRUf2bYKGOOQPfySUATpEgHqOBiJt7g=="],
"@scalar/types": ["@scalar/types@0.1.1", "", { "dependencies": { "@scalar/openapi-types": "0.1.9", "@unhead/schema": "^1.11.11", "zod": "^3.23.8" } }, "sha512-LlUX6AmOOGoRqOMoO835V2FezM1KiO5UlvQC3poT/s7oqD6ranqwRNFxyrPz/IxClPYR+SV1yBUSNKely4ZQhQ=="],
"@scalar/use-codemirror": ["@scalar/use-codemirror@0.11.73", "", { "dependencies": { "@codemirror/autocomplete": "^6.18.3", "@codemirror/commands": "^6.7.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.8", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.10.7", "@codemirror/lint": "^6.8.4", "@codemirror/state": "^6.5.0", "@codemirror/view": "^6.35.3", "@lezer/common": "^1.2.3", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", "@replit/codemirror-css-color-picker": "^6.3.0", "@scalar/components": "0.13.28", "codemirror": "^6.0.0", "style-mod": "^4.1.2", "vue": "^3.5.12" } }, "sha512-WPB01ZD8cWpJhNU6irPRIUc8Xn2qkFQpgiP3xxjYIcALEJ+EFzamftAsXe+7/AI8jF3z4mPuCimntpvs0jsRvA=="],
"@scalar/use-codemirror": ["@scalar/use-codemirror@0.11.82", "", { "dependencies": { "@codemirror/autocomplete": "^6.18.3", "@codemirror/commands": "^6.7.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.8", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.10.7", "@codemirror/lint": "^6.8.4", "@codemirror/state": "^6.5.0", "@codemirror/view": "^6.35.3", "@lezer/common": "^1.2.3", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", "@replit/codemirror-css-color-picker": "^6.3.0", "@scalar/components": "0.13.37", "codemirror": "^6.0.0", "style-mod": "^4.1.2", "vue": "^3.5.12" } }, "sha512-zFECln7aWKRf6iJO9oovByD59EsrOMenNLfLhneH6L+K1CrBoHFVr4czSDlom1wlr3HPg3xwpZrukoAteHYILQ=="],
"@scalar/use-hooks": ["@scalar/use-hooks@0.1.25", "", { "dependencies": { "@scalar/themes": "0.9.71", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "vue": "^3.5.12", "zod": "^3.23.8" } }, "sha512-6AmILMqbgWHM+3OWDWawG4mO8/xD1/tbZpWDNm4L8Zm5GZASUjipA1AmYFtRI5Gf3l4S+6Q4wL58Li2D6e/3iA=="],
"@scalar/use-hooks": ["@scalar/use-hooks@0.1.33", "", { "dependencies": { "@scalar/themes": "0.9.79", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "vue": "^3.5.12", "zod": "^3.23.8" } }, "sha512-ENm0bWwRdAWWF/S6TbE+fFx0vP2mgEpG5APqQBomm0a41/6L2HJ/TN+9ajAvrJXGi0ULWuxihNS4Jue6tpEssA=="],
"@scalar/use-toasts": ["@scalar/use-toasts@0.7.9", "", { "dependencies": { "nanoid": "^5.0.9", "vue": "^3.5.12", "vue-sonner": "^1.0.3" } }, "sha512-EcUDJY8VozLS9sfoQKvvipStQJ9RuH/nKOzf0BBr+mZDmumi1WFZ1iIJnHVXIN3iSLcSAr5ej6rOqa6jIv4bCQ=="],
@@ -1412,6 +1432,8 @@
"ast-types": ["ast-types@0.14.2", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA=="],
"astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="],
"async-listen": ["async-listen@1.2.0", "", {}, "sha512-CcEtRh/oc9Jc4uWeUwdpG/+Mb2YUHKmdaTf0gUr7Wa+bfp4xx70HOb3RuSTJMvqKNB1TkdTfjLdrcz2X4rkkZA=="],
"async-sema": ["async-sema@3.1.1", "", {}, "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg=="],
@@ -1426,7 +1448,7 @@
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"balanced-match": ["balanced-match@2.0.0", "", {}, "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
@@ -1452,6 +1474,8 @@
"browserslist": ["browserslist@4.24.0", "", { "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A=="],
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
@@ -1464,10 +1488,14 @@
"bytes": ["bytes@3.1.0", "", {}, "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="],
"cacheable": ["cacheable@1.8.9", "", { "dependencies": { "hookified": "^1.7.1", "keyv": "^5.3.1" } }, "sha512-FicwAUyWnrtnd4QqYAoRlNs44/a1jTL7XDKqm5gJ90wz1DQPlC7U2Rd1Tydpv+E7WAr4sQHuw8Q8M3nZMAyecQ=="],
"cacheable-request": ["cacheable-request@6.1.0", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^3.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^4.1.0", "responselike": "^1.0.2" } }, "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg=="],
"call-bind": ["call-bind@1.0.7", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.1" } }, "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
"camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
@@ -1520,6 +1548,8 @@
"color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
"colord": ["colord@2.9.3", "", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
@@ -1546,6 +1576,8 @@
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
"cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="],
"create-require": ["create-require@1.1.1", "", {}, "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="],
"crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="],
@@ -1556,6 +1588,10 @@
"crypto-random-string": ["crypto-random-string@2.0.0", "", {}, "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA=="],
"css-functions-list": ["css-functions-list@3.2.3", "", {}, "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA=="],
"css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
@@ -1650,6 +1686,8 @@
"env-cmd": ["env-cmd@10.1.0", "", { "dependencies": { "commander": "^4.0.0", "cross-spawn": "^7.0.0" }, "bin": { "env-cmd": "bin/env-cmd.js" } }, "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA=="],
"env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
"error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
"es-define-property": ["es-define-property@1.0.0", "", { "dependencies": { "get-intrinsic": "^1.2.4" } }, "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ=="],
@@ -1750,7 +1788,7 @@
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
@@ -1760,12 +1798,16 @@
"fast-xml-parser": ["fast-xml-parser@4.2.5", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g=="],
"fastest-levenshtein": ["fastest-levenshtein@1.0.16", "", {}, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="],
"fastq": ["fastq@1.17.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w=="],
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
"fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="],
"file-entry-cache": ["file-entry-cache@10.0.7", "", { "dependencies": { "flat-cache": "^6.1.7" } }, "sha512-txsf5fu3anp2ff3+gOJJzRImtrtm/oa9tYLN0iTuINZ++EyVR/nRrg2fKYwvG/pXDofcrvvb0scEbX3NyW/COw=="],
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
@@ -1774,7 +1816,9 @@
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
"flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
"flat-cache": ["flat-cache@6.1.7", "", { "dependencies": { "cacheable": "^1.8.9", "flatted": "^3.3.3", "hookified": "^1.7.1" } }, "sha512-qwZ4xf1v1m7Rc9XiORly31YaChvKt6oNVHuqqZcoED/7O+ToyNVGobKsIAopY9ODcWpEDKEBAbrSOCBHtNQvew=="],
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"focus-trap": ["focus-trap@7.6.1", "", { "dependencies": { "tabbable": "^6.2.0" } }, "sha512-nB8y4nQl8PshahLpGKZOq1sb0xrMVFSn6at7u/qOsBZTlZRzaapISGENcB6mOkoezbClZyiMwEF/dGY8AZ00rA=="],
@@ -1832,8 +1876,14 @@
"global-dirs": ["global-dirs@2.1.0", "", { "dependencies": { "ini": "1.3.7" } }, "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ=="],
"global-modules": ["global-modules@2.0.0", "", { "dependencies": { "global-prefix": "^3.0.0" } }, "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A=="],
"global-prefix": ["global-prefix@3.0.0", "", { "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", "which": "^1.3.1" } }, "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg=="],
"globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
"globjoin": ["globjoin@0.1.4", "", {}, "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg=="],
"google-auth-library": ["google-auth-library@5.10.1", "", { "dependencies": { "arrify": "^2.0.0", "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "fast-text-encoding": "^1.0.0", "gaxios": "^2.1.0", "gcp-metadata": "^3.4.0", "gtoken": "^4.1.0", "jws": "^4.0.0", "lru-cache": "^5.0.0" } }, "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg=="],
"google-p12-pem": ["google-p12-pem@2.0.5", "", { "dependencies": { "node-forge": "^0.10.0" }, "bin": { "gp12-pem": "build/src/bin/gp12-pem.js" } }, "sha512-7RLkxwSsMsYh9wQ5Vb2zRtkAHvqPvfoMGag+nugl1noYO7gf0844Yr9TIFA5NEBMAeVt2Z+Imu7CQMp3oNatzQ=="],
@@ -1890,7 +1940,7 @@
"hast-util-sanitize": ["hast-util-sanitize@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.2.0", "unist-util-position": "^5.0.0" } }, "sha512-IGrgWLuip4O2nq5CugXy4GI2V8kx4sFVy5Hd4vF7AR2gxS0N9s7nEAVUyeMtZKZvzrxVsHt73XdTsno1tClIkQ=="],
"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-html": ["hast-util-to-html@9.0.3", "", { "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": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg=="],
"hast-util-to-parse5": ["hast-util-to-parse5@8.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw=="],
@@ -1908,8 +1958,12 @@
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
"hookified": ["hookified@1.8.1", "", {}, "sha512-GrO2l93P8xCWBSTBX9l2BxI78VU/MAAYag+pG8curS3aBGy0++ZlxrQ7PdUOUVMbn5BwkGb6+eRrnf43ipnFEA=="],
"hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="],
"html-tags": ["html-tags@3.3.1", "", {}, "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ=="],
"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=="],
@@ -1928,7 +1982,11 @@
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"ignore": ["ignore@7.0.3", "", {}, "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"import-lazy": ["import-lazy@2.1.0", "", {}, "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A=="],
@@ -1940,7 +1998,7 @@
"inherits": ["inherits@2.0.1", "", {}, "sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA=="],
"ini": ["ini@1.3.7", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="],
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"intl-messageformat": ["intl-messageformat@10.7.14", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.2", "@formatjs/fast-memoize": "2.2.6", "@formatjs/icu-messageformat-parser": "2.11.0", "tslib": "2" } }, "sha512-mMGnE4E1otdEutV5vLUdCxRJygHB5ozUBxsPB5qhitewssrS/qGruq9bmvIRkkGsNeK5ZWLfYRld18UHGTIifQ=="],
@@ -1950,7 +2008,7 @@
"is-absolute-url": ["is-absolute-url@4.0.1", "", {}, "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A=="],
"is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
@@ -1974,7 +2032,9 @@
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
"is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="],
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
"is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="],
"is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="],
@@ -2042,10 +2102,12 @@
"katex": ["katex@0.16.11", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ=="],
"keyv": ["keyv@3.1.0", "", { "dependencies": { "json-buffer": "3.0.0" } }, "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA=="],
"keyv": ["keyv@5.3.2", "", { "dependencies": { "@keyv/serialize": "^1.0.3" } }, "sha512-Lji2XRxqqa5Wg+CHLVfFKBImfJZ4pCSccu9eVWK6w4c2SDFLd8JAn1zqTuSFnsxb7ope6rMsnIHfp+eBbRBRZQ=="],
"kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="],
"known-css-properties": ["known-css-properties@0.35.0", "", {}, "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A=="],
"latest-version": ["latest-version@5.1.0", "", { "dependencies": { "package-json": "^6.3.0" } }, "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA=="],
"leven": ["leven@4.0.0", "", {}, "sha512-puehA3YKku3osqPlNuzGDUHq8WpwXupUg1V6NXdV38G+gr+gkBwFC8g1b/+YcIvp8gnqVIus+eJCH/eGsRmJNw=="],
@@ -2080,6 +2142,8 @@
"lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="],
"lodash.truncate": ["lodash.truncate@4.4.2", "", {}, "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw=="],
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
@@ -2106,6 +2170,8 @@
"mathjax": ["mathjax@3.2.2", "", {}, "sha512-Bt+SSVU8eBG27zChVewOicYs7Xsdt40qm4+UpHyX7k0/O9NliPc+x77k1/FEsPsjKPZGJvtRZM1vO+geW0OhGw=="],
"mathml-tag-names": ["mathml-tag-names@2.1.3", "", {}, "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg=="],
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.1", "", { "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-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA=="],
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.1", "", { "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-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA=="],
@@ -2130,6 +2196,8 @@
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
"mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"memoizee": ["memoizee@0.4.17", "", { "dependencies": { "d": "^1.0.2", "es5-ext": "^0.10.64", "es6-weak-map": "^2.0.3", "event-emitter": "^0.3.5", "is-promise": "^2.2.2", "lru-queue": "^0.1.0", "next-tick": "^1.1.0", "timers-ext": "^0.1.7" } }, "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA=="],
@@ -2148,7 +2216,7 @@
"microdiff": ["microdiff@1.4.0", "", {}, "sha512-OBKBOa1VBznvLPb/3ljeJaENVe0fO0lnWl77lR4vhPlQD71UpjEoRV5P0KdQkcjbFlBu1Oy2mEUBMU3wxcBAGg=="],
"micromark": ["micromark@4.0.1", "", { "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-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw=="],
"micromark": ["micromark@4.0.0", "", { "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-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ=="],
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.1", "", { "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-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA=="],
@@ -2250,7 +2318,7 @@
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"next": ["next@14.2.23", "", { "dependencies": { "@next/env": "14.2.23", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", "postcss": "8.4.31", "styled-jsx": "5.1.1" }, "optionalDependencies": { "@next/swc-darwin-arm64": "14.2.23", "@next/swc-darwin-x64": "14.2.23", "@next/swc-linux-arm64-gnu": "14.2.23", "@next/swc-linux-arm64-musl": "14.2.23", "@next/swc-linux-x64-gnu": "14.2.23", "@next/swc-linux-x64-musl": "14.2.23", "@next/swc-win32-arm64-msvc": "14.2.23", "@next/swc-win32-ia32-msvc": "14.2.23", "@next/swc-win32-x64-msvc": "14.2.23" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-mjN3fE6u/tynneLiEg56XnthzuYw+kD7mCujgVqioxyPqbmiotUCGJpIZGS/VaPg3ZDT1tvWxiVyRzeqJFm/kw=="],
"next": ["next@14.2.25", "", { "dependencies": { "@next/env": "14.2.25", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", "postcss": "8.4.31", "styled-jsx": "5.1.1" }, "optionalDependencies": { "@next/swc-darwin-arm64": "14.2.25", "@next/swc-darwin-x64": "14.2.25", "@next/swc-linux-arm64-gnu": "14.2.25", "@next/swc-linux-arm64-musl": "14.2.25", "@next/swc-linux-x64-gnu": "14.2.25", "@next/swc-linux-x64-musl": "14.2.25", "@next/swc-win32-arm64-msvc": "14.2.25", "@next/swc-win32-ia32-msvc": "14.2.25", "@next/swc-win32-x64-msvc": "14.2.25" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-N5M7xMc4wSb4IkPvEV5X2BRRXUmhVHNyaXwEM86+voXthSZz8ZiRyQW4p9mwAoAPIm6OzuVZtn7idgEJeAJN3Q=="],
"next-themes": ["next-themes@0.2.1", "", { "peerDependencies": { "next": "*", "react": "*", "react-dom": "*" } }, "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A=="],
@@ -2334,6 +2402,8 @@
"package-manager-manager": ["package-manager-manager@0.2.0", "", { "dependencies": { "js-yaml": "^4.1.0", "shellac": "^0.8.0" } }, "sha512-V02gl0bafXJ2gcY6j+5IHM7UdnYwmF+2OsFZuqVcha6iMSStD4dpIOBOsypnUIwOi4jLcPz6RQuyifmAE3mG8g=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"parse-cache-control": ["parse-cache-control@1.0.1", "", {}, "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg=="],
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
@@ -2378,11 +2448,11 @@
"pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
"playwright": ["playwright@1.49.1", "", { "dependencies": { "playwright-core": "1.49.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA=="],
"playwright": ["playwright@1.51.1", "", { "dependencies": { "playwright-core": "1.51.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw=="],
"playwright-core": ["playwright-core@1.49.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg=="],
"playwright-core": ["playwright-core@1.51.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw=="],
"postcss": ["postcss@8.4.47", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", "source-map-js": "^1.2.1" } }, "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ=="],
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
"postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="],
@@ -2392,7 +2462,11 @@
"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.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"postcss-resolve-nested-selector": ["postcss-resolve-nested-selector@0.1.6", "", {}, "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw=="],
"postcss-safe-parser": ["postcss-safe-parser@7.0.1", "", { "peerDependencies": { "postcss": "^8.4.31" } }, "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A=="],
"postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="],
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
@@ -2408,7 +2482,7 @@
"promisepipe": ["promisepipe@3.0.0", "", {}, "sha512-V6TbZDJ/ZswevgkDNpGt/YqNCiZP9ASfgU+p83uJE6NrGtvSGoOcHLiDCqkMs2+yg7F5qHdLV8d0aS8O26G/KA=="],
"property-information": ["property-information@7.0.0", "", {}, "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg=="],
"property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
@@ -2492,7 +2566,7 @@
"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-gfm": ["remark-gfm@4.0.0", "", { "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-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="],
"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-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=="],
@@ -2568,6 +2642,8 @@
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="],
"sort-on": ["sort-on@4.1.1", "", { "dependencies": { "arrify": "^2.0.1", "dot-prop": "^5.0.0" } }, "sha512-nj8myvTCEErLMMWnye61z1pV5osa7njoosoQNdylD8WyPYHoHCBQx/xn7mGJL6h4oThvGpYSIAxfm8VUr75qTQ=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@@ -2632,18 +2708,24 @@
"styled-jsx": ["styled-jsx@5.1.1", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" } }, "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw=="],
"stylelint": ["stylelint@16.16.0", "", { "dependencies": { "@csstools/css-parser-algorithms": "^3.0.4", "@csstools/css-tokenizer": "^3.0.3", "@csstools/media-query-list-parser": "^4.0.2", "@csstools/selector-specificity": "^5.0.0", "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.3", "css-tree": "^3.1.0", "debug": "^4.3.7", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^10.0.7", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", "ignore": "^7.0.3", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", "known-css-properties": "^0.35.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.5.3", "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.0", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", "supports-hyperlinks": "^3.2.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^5.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" } }, "sha512-40X5UOb/0CEFnZVEHyN260HlSSUxPES+arrUphOumGWgXERHfwCD0kNBVILgQSij8iliYVwlc0V7M5bcLP9vPg=="],
"sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"supports-hyperlinks": ["supports-hyperlinks@2.3.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA=="],
"supports-hyperlinks": ["supports-hyperlinks@3.2.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"svg-tags": ["svg-tags@1.0.0", "", {}, "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA=="],
"swr": ["swr@2.3.2", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-RosxFpiabojs75IwQ316DGoDRmOqtiAj0tg8wCcbEu4CiLZBs/a9QNtHV7TUfDXmmlgqij/NqzKq/eLelyv9xA=="],
"tabbable": ["tabbable@6.2.0", "", {}, "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="],
"table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="],
"tailwind-merge": ["tailwind-merge@2.5.5", "", {}, "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA=="],
"tailwind-shades": ["tailwind-shades@1.1.2", "", {}, "sha512-gsyyr9NtfPS1QNWV/YQMcoO5tLStTwp/So2I6jnPWmCFmqY8JEM1csBoq0nbFtuRsjYypC/Bm09pprUyLL/Zjg=="],
@@ -2836,7 +2918,7 @@
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"write-file-atomic": ["write-file-atomic@3.0.3", "", { "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", "signal-exit": "^3.0.2", "typedarray-to-buffer": "^3.1.5" } }, "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q=="],
"write-file-atomic": ["write-file-atomic@5.0.1", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw=="],
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
@@ -2874,8 +2956,6 @@
"@ai-sdk/provider-utils/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
"@argos-ci/core/fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"@argos-ci/core/tmp": ["tmp@0.2.3", "", {}, "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w=="],
"@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
@@ -3510,6 +3590,8 @@
"@dotenvx/dotenvx/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
"@dotenvx/dotenvx/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"@dotenvx/dotenvx/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
"@dotenvx/dotenvx/which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
@@ -3604,6 +3686,14 @@
"@scalar/api-client/pretty-ms": ["pretty-ms@8.0.0", "", { "dependencies": { "parse-ms": "^3.0.0" } }, "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q=="],
"@scalar/code-highlight/remark-gfm": ["remark-gfm@4.0.0", "", { "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-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="],
"@scalar/oas-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
"@scalar/object-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
"@shikijs/core/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=="],
"@smithy/abort-controller/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@smithy/chunked-blob-reader/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@@ -3772,6 +3862,8 @@
"@tailwindcss/typography/postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
"@ts-morph/common/fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="],
"@ts-morph/common/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"@ts-morph/common/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
@@ -3816,6 +3908,8 @@
"@vercel/static-config/ajv": ["ajv@8.6.3", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw=="],
"@vue/compiler-sfc/postcss": ["postcss@8.4.47", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", "source-map-js": "^1.2.1" } }, "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ=="],
"@vueuse/integrations/@vueuse/core": ["@vueuse/core@11.2.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "11.2.0", "@vueuse/shared": "11.2.0", "vue-demi": ">=0.14.10" } }, "sha512-JIUwRcOqOWzcdu1dGlfW04kaJhW3EXnnjJJfLTtddJanymTL7lF1C0+dVVZ/siLfc73mWn+cGP1PE1PKPruRSA=="],
"@vueuse/integrations/@vueuse/shared": ["@vueuse/shared@11.2.0", "", { "dependencies": { "vue-demi": ">=0.14.10" } }, "sha512-VxFjie0EanOudYSgMErxXfq6fo8vhr5ICI+BuE3I9FnX7ePllEsVrRQ7O6Q1TLgApeLuPKcHQxAXpP+KnlrJsg=="],
@@ -3838,8 +3932,12 @@
"boxen/type-fest": ["type-fest@0.8.1", "", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="],
"brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"bun-types/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
"cacheable-request/keyv": ["keyv@3.1.0", "", { "dependencies": { "json-buffer": "3.0.0" } }, "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA=="],
"cacheable-request/lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="],
"capnp-ts/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
@@ -3854,6 +3952,8 @@
"codemirror/@codemirror/view": ["@codemirror/view@6.34.1", "", { "dependencies": { "@codemirror/state": "^6.4.0", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-t1zK/l9UiRqwUNPm+pdIT0qzJlzuVckbTEMVNFhfWkGiBQClstzg+78vedCvLSX0xJEZ6lwZbPpnljL7L6iwMQ=="],
"configstore/write-file-atomic": ["write-file-atomic@3.0.3", "", { "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", "signal-exit": "^3.0.2", "typedarray-to-buffer": "^3.1.5" } }, "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q=="],
"convict/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
"decamelize-keys/map-obj": ["map-obj@1.0.1", "", {}, "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg=="],
@@ -3870,8 +3970,6 @@
"env-cmd/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
"error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
"execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
@@ -3894,7 +3992,15 @@
"gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"gitbook-v2/next": ["next@15.2.1", "", { "dependencies": { "@next/env": "15.2.1", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.2.1", "@next/swc-darwin-x64": "15.2.1", "@next/swc-linux-arm64-gnu": "15.2.1", "@next/swc-linux-arm64-musl": "15.2.1", "@next/swc-linux-x64-gnu": "15.2.1", "@next/swc-linux-x64-musl": "15.2.1", "@next/swc-win32-arm64-msvc": "15.2.1", "@next/swc-win32-x64-msvc": "15.2.1", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-zxbsdQv3OqWXybK5tMkPCBKyhIz63RstJ+NvlfkaLMc/m5MwXgz2e92k+hSKcyBpyADhMk2C31RIiaDjUZae7g=="],
"gitbook-v2/next": ["next@15.2.3", "", { "dependencies": { "@next/env": "15.2.3", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.2.3", "@next/swc-darwin-x64": "15.2.3", "@next/swc-linux-arm64-gnu": "15.2.3", "@next/swc-linux-arm64-musl": "15.2.3", "@next/swc-linux-x64-gnu": "15.2.3", "@next/swc-linux-x64-musl": "15.2.3", "@next/swc-win32-arm64-msvc": "15.2.3", "@next/swc-win32-x64-msvc": "15.2.3", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-x6eDkZxk2rPpu46E1ZVUWIBhYCLszmUY6fvHBFcbzJ9dD+qRX6vcHusaqqDlnY+VngKzKbAiG2iRCkPbmi8f7w=="],
"global-dirs/ini": ["ini@1.3.7", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="],
"global-prefix/which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="],
"globby/fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="],
"globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"google-auth-library/jws": ["jws@4.0.0", "", { "dependencies": { "jwa": "^2.0.0", "safe-buffer": "^5.0.1" } }, "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg=="],
@@ -3908,14 +4014,10 @@
"gtoken/mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="],
"hast-util-from-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
"hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
"hastscript/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
"http-errors/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="],
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"intl-messageformat/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"is-ci/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="],
@@ -3930,8 +4032,6 @@
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"mdast-util-from-markdown/micromark": ["micromark@4.0.0", "", { "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-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ=="],
"mdast-util-gfm/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "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-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
"mdast-util-gfm-footnote/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "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-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
@@ -3946,6 +4046,8 @@
"micro/content-type": ["content-type@1.0.4", "", {}, "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="],
"micromark/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"miniflare/undici": ["undici@5.28.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA=="],
@@ -3954,6 +4056,8 @@
"minimist-options/arrify": ["arrify@1.0.1", "", {}, "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA=="],
"minimist-options/is-plain-obj": ["is-plain-obj@1.1.0", "", {}, "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg=="],
"minizlib/minipass": ["minipass@2.9.0", "", { "dependencies": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg=="],
"mlly/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
@@ -3978,12 +4082,16 @@
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
"postcss/nanoid": ["nanoid@3.3.7", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="],
"postcss/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
"postcss/picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"postcss-load-config/lilconfig": ["lilconfig@3.1.2", "", {}, "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow=="],
"postcss-load-config/yaml": ["yaml@2.6.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ=="],
"postcss-nested/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"psi/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
"pump/end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="],
@@ -3996,8 +4104,6 @@
"raw-body/http-errors": ["http-errors@1.7.3", "", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.1.1", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" } }, "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw=="],
"rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"read-cache/pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
"read-pkg/type-fest": ["type-fest@0.6.0", "", {}, "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg=="],
@@ -4008,8 +4114,6 @@
"read-yaml-file/pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="],
"rehype-stringify/hast-util-to-html": ["hast-util-to-html@9.0.3", "", { "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": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg=="],
"remark-stringify/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "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-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
"rimraf/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
@@ -4032,18 +4136,32 @@
"send/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"simple-swizzle/is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
"spawndamnit/cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"stringify-object/is-obj": ["is-obj@3.0.0", "", {}, "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ=="],
"stylelint/meow": ["meow@13.2.0", "", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="],
"stylelint/picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
"sucrase/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"tailwindcss/fast-glob": ["fast-glob@3.3.2", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow=="],
"tailwindcss/postcss": ["postcss@8.4.47", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.0", "source-map-js": "^1.2.1" } }, "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ=="],
"tailwindcss/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"tar/minipass": ["minipass@2.9.0", "", { "dependencies": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg=="],
"terminal-link/supports-hyperlinks": ["supports-hyperlinks@2.3.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA=="],
"terser/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
@@ -4052,8 +4170,6 @@
"ts-node/arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="],
"unified/is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
"update-notifier/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
"url-parse-lax/prepend-http": ["prepend-http@2.0.0", "", {}, "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA=="],
@@ -4064,10 +4180,6 @@
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"@argos-ci/core/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw=="],
"@aws-crypto/crc32c/@aws-sdk/types/@smithy/types": ["@smithy/types@4.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw=="],
@@ -4586,6 +4698,8 @@
"@scalar/api-client/pretty-ms/parse-ms": ["parse-ms@3.0.0", "", {}, "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw=="],
"@shikijs/core/hast-util-to-html/property-information": ["property-information@7.0.0", "", {}, "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg=="],
"@smithy/chunked-blob-reader-native/@smithy/util-base64/@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.0.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug=="],
"@smithy/chunked-blob-reader-native/@smithy/util-base64/@smithy/util-utf8": ["@smithy/util-utf8@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow=="],
@@ -4610,6 +4724,8 @@
"@smithy/util-endpoints/@smithy/node-config-provider/@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "tslib": "^2.6.2" } }, "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw=="],
"@ts-morph/common/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"@ts-morph/common/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"@vercel/fun/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
@@ -4704,6 +4820,8 @@
"@vercel/routing-utils/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"@vue/compiler-sfc/postcss/nanoid": ["nanoid@3.3.7", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="],
"@vueuse/integrations/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@11.2.0", "", {}, "sha512-L0ZmtRmNx+ZW95DmrgD6vn484gSpVeRbgpWevFKXwqqQxW9hnSi2Ppuh2BzMjnbv4aJRiIw8tQatXT9uOB23dQ=="],
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
@@ -4724,6 +4842,8 @@
"codemirror/@codemirror/language/@lezer/common": ["@lezer/common@1.2.2", "", {}, "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw=="],
"configstore/write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"express/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
"express/http-errors/inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
@@ -4736,34 +4856,34 @@
"gaxios/https-proxy-agent/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"gitbook-v2/next/@next/env": ["@next/env@15.2.1", "", {}, "sha512-JmY0qvnPuS2NCWOz2bbby3Pe0VzdAQ7XpEB6uLIHmtXNfAsAO0KLQLkuAoc42Bxbo3/jMC3dcn9cdf+piCcG2Q=="],
"gitbook-v2/next/@next/env": ["@next/env@15.2.3", "", {}, "sha512-a26KnbW9DFEUsSxAxKBORR/uD9THoYoKbkpFywMN/AFvboTt94b8+g/07T8J6ACsdLag8/PDU60ov4rPxRAixw=="],
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-aWXT+5KEREoy3K5AKtiKwioeblmOvFFjd+F3dVleLvvLiQ/mD//jOOuUcx5hzcO9ISSw4lrqtUPntTpK32uXXQ=="],
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.2.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uaBhA8aLbXLqwjnsHSkxs353WrRgQgiFjduDpc7YXEU0B54IKx3vU+cxQlYwPCyC8uYEEX7THhtQQsfHnvv8dw=="],
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-E/w8ervu4fcG5SkLhvn1NE/2POuDCDEy5gFbfhmnYXkyONZR68qbUlJlZwuN82o7BrBVAw+tkR8nTIjGiMW1jQ=="],
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.2.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-pVwKvJ4Zk7h+4hwhqOUuMx7Ib02u3gDX3HXPKIShBi9JlYllI0nU6TWLbPT94dt7FSi6mSBhfc2JrHViwqbOdw=="],
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gXDX5lIboebbjhiMT6kFgu4svQyjoSed6dHyjx5uZsjlvTwOAnZpn13w9XDaIMFFHw7K8CpBK7HfDKw0VZvUXQ=="],
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-50ibWdn2RuFFkOEUmo9NCcQbbV9ViQOrUfG48zHBCONciHjaUKtHcYFiCwBVuzD08fzvzkWuuZkd4AqbvKO7UQ=="],
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-3v0pF/adKZkBWfUffmB/ROa+QcNTrnmYG4/SS+r52HPwAK479XcWoES2I+7F7lcbqc7mTeVXrIvb4h6rR/iDKg=="],
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.2.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-2gAPA7P652D3HzR4cLyAuVYwYqjG0mt/3pHSWTCyKZq/N/dJcUAEoNQMyUmwTZWCJRKofB+JPuDVP2aD8w2J6Q=="],
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-RbsVq2iB6KFJRZ2cHrU67jLVLKeuOIhnQB05ygu5fCNgg8oTewxweJE8XlLV+Ii6Y6u4EHwETdUiRNXIAfpBww=="],
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-ODSKvrdMgAJOVU4qElflYy1KSZRM3M45JVbeZu42TINCMG3anp7YCBn80RkISV6bhzKwcUqLBAmOiWkaGtBA9w=="],
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-QHsMLAyAIu6/fWjHmkN/F78EFPKmhQlyX5C8pRIS2RwVA7z+t9cTb0IaYWC3EHLOTjsU7MNQW+n2xGXr11QPpg=="],
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.2.3", "", { "os": "linux", "cpu": "x64" }, "sha512-ZR9kLwCWrlYxwEoytqPi1jhPd1TlsSJWAc+H/CJHmHkf2nD92MQpSRIURR1iNgA/kuFSdxB8xIPt4p/T78kwsg=="],
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gk42XZXo1cE89i3hPLa/9KZ8OuupTjkDmhLaMKFohjf9brOeZVEa3BQy1J9s9TWUqPhgAEbwv6B2+ciGfe54Vw=="],
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.2.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-+G2FrDcfm2YDbhDiObDU/qPriWeiz/9cRR0yMWJeTLGGX6/x8oryO3tt7HhodA1vZ8r2ddJPCjtLcpaVl7TE2Q=="],
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-YjqXCl8QGhVlMR8uBftWk0iTmvtntr41PhG1kvzGp0sUP/5ehTM+cwx25hKE54J0CRnHYjSGjSH3gkHEaHIN9g=="],
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.2.3", "", { "os": "win32", "cpu": "x64" }, "sha512-gHYS9tc+G2W0ZC8rBL+H6RdtXIyk40uLiaos0yj5US85FNhbFEndMA2nW3z47nzOWiSvXTZ5kBClc3rD0zJg0w=="],
"gitbook-v2/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"gitbook-v2/next/styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
"globby/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"google-auth-library/jws/jwa": ["jwa@2.0.0", "", { "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA=="],
"gtoken/jws/jwa": ["jwa@2.0.0", "", { "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA=="],
"mdast-util-from-markdown/micromark/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"next/postcss/nanoid": ["nanoid@3.3.7", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="],
"raw-body/http-errors/depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="],
@@ -4778,8 +4898,6 @@
"read-yaml-file/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"rehype-stringify/hast-util-to-html/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
"rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
@@ -4800,6 +4918,10 @@
"tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"tailwindcss/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"tailwindcss/postcss/nanoid": ["nanoid@3.3.7", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="],
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
@@ -4962,6 +5084,8 @@
"@smithy/core/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.1", "", { "dependencies": { "@smithy/types": "^4.1.0", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg=="],
"@ts-morph/common/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"@vercel/nft/glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"body-parser/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
@@ -5008,6 +5132,8 @@
"@smithy/core/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
"@vercel/nft/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
"@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
+2 -2
View File
@@ -7,12 +7,12 @@
"turbo": "^2.4.4",
"vercel": "^39.3.0"
},
"packageManager": "bun@1.2.4",
"packageManager": "bun@1.2.5",
"overrides": {
"@codemirror/state": "6.4.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"@gitbook/api": "0.99.0"
"@gitbook/api": "0.102.0"
},
"private": true,
"scripts": {
+7
View File
@@ -1,5 +1,12 @@
# gitbook-v2
## 0.2.2
### Patch Changes
- 54ee014: Add initial support for loading custom fonts
- bba2e52: Fix site redirects when it includes a section/variant path
## 0.2.1
### Patch Changes
+11
View File
@@ -5,7 +5,17 @@
*/
const nextConfig = {
experimental: {
// This is needed to throw "forbidden" when the api token expired during revalidation
authInterrupts: true,
// This is needed to use 'use cache'
useCache: true,
// Content is fully static, we can cache it in the session memory cache for a long time
staleTimes: {
dynamic: 3600, // 1 hour
static: 3600, // 1 hour
},
},
env: {
@@ -23,6 +33,7 @@ const nextConfig = {
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
GITBOOK_SECRET: process.env.GITBOOK_SECRET,
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY,
GITBOOK_FONTS_URL: process.env.GITBOOK_FONTS_URL,
// Next.js envs
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY,
+6 -3
View File
@@ -1,20 +1,23 @@
{
"name": "gitbook-v2",
"version": "0.2.1",
"version": "0.2.2",
"private": true,
"dependencies": {
"next": "^15.2.1",
"next": "^15.2.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@gitbook/api": "*",
"@gitbook/cache-tags": "workspace:*",
"@sindresorhus/fnv1a": "^3.1.0",
"server-only": "^0.0.1",
"warn-once": "^0.1.1"
"warn-once": "^0.1.1",
"rison": "^0.1.1",
"jwt-decode": "^4.0.0"
},
"devDependencies": {
"gitbook": "*",
"@opennextjs/cloudflare": "^0.5.10",
"@types/rison": "^0.0.9",
"tailwindcss": "^3.4.0",
"postcss": "^8"
},
@@ -13,20 +13,20 @@ type PageProps = {
export default async function Page(props: PageProps) {
const params = await props.params;
const context = await getDynamicSiteContext(params);
const { context } = await getDynamicSiteContext(params);
const pathname = getPagePathFromParams(params);
return <SitePage context={context} pageParams={{ pathname }} />;
}
export async function generateViewport(props: PageProps): Promise<Viewport> {
const context = await getDynamicSiteContext(await props.params);
const { context } = await getDynamicSiteContext(await props.params);
return generateSitePageViewport(context);
}
export async function generateMetadata(props: PageProps): Promise<Metadata> {
const params = await props.params;
const context = await getDynamicSiteContext(params);
const { context } = await getDynamicSiteContext(params);
const pathname = getPagePathFromParams(params);
return generateSitePageMetadata({
@@ -6,7 +6,7 @@ import {
} from '@/components/SiteLayout';
import { type RouteLayoutParams, getDynamicSiteContext } from '@v2/app/utils';
import { GITBOOK_DISABLE_TRACKING } from '@v2/lib/env';
import { getThemeFromMiddleware, getVisitorAuthTokenFromMiddleware } from '@v2/lib/middleware';
import { getThemeFromMiddleware } from '@v2/lib/middleware';
interface SiteDynamicLayoutProps {
params: Promise<RouteLayoutParams>;
@@ -16,9 +16,8 @@ export default async function SiteDynamicLayout({
params,
children,
}: React.PropsWithChildren<SiteDynamicLayoutProps>) {
const context = await getDynamicSiteContext(await params);
const { context, visitorAuthClaims } = await getDynamicSiteContext(await params);
const forcedTheme = await getThemeFromMiddleware();
const visitorAuthToken = await getVisitorAuthTokenFromMiddleware();
return (
<CustomizationRootLayout customization={context.customization}>
@@ -26,7 +25,7 @@ export default async function SiteDynamicLayout({
context={context}
forcedTheme={forcedTheme}
withTracking={!GITBOOK_DISABLE_TRACKING}
visitorAuthToken={visitorAuthToken}
visitorAuthClaims={visitorAuthClaims}
>
{children}
</SiteLayout>
@@ -35,11 +34,11 @@ export default async function SiteDynamicLayout({
}
export async function generateViewport({ params }: SiteDynamicLayoutProps) {
const context = await getDynamicSiteContext(await params);
const { context } = await getDynamicSiteContext(await params);
return generateSiteLayoutViewport(context);
}
export async function generateMetadata({ params }: SiteDynamicLayoutProps) {
const context = await getDynamicSiteContext(await params);
const { context } = await getDynamicSiteContext(await params);
return generateSiteLayoutMetadata(context);
}
@@ -7,6 +7,6 @@ export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const context = await getDynamicSiteContext(await params);
const { context } = await getDynamicSiteContext(await params);
return serveLLMsTxt(context);
}
@@ -7,6 +7,6 @@ export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const context = await getDynamicSiteContext(await params);
const { context } = await getDynamicSiteContext(await params);
return serveRobotsTxt(context);
}
@@ -7,6 +7,6 @@ export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const context = await getDynamicSiteContext(await params);
const { context } = await getDynamicSiteContext(await params);
return servePagesSitemap(context);
}
@@ -7,6 +7,6 @@ export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const context = await getDynamicSiteContext(await params);
const { context } = await getDynamicSiteContext(await params);
return serveRootSitemap(context);
}
@@ -7,6 +7,6 @@ export async function GET(
request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const context = await getDynamicSiteContext(await params);
const { context } = await getDynamicSiteContext(await params);
return serveIcon(context, request);
}
@@ -8,6 +8,6 @@ export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams & PageIdParams> }
) {
const context = await getDynamicSiteContext(await params);
const { context } = await getDynamicSiteContext(await params);
return serveOGImage(context, await params);
}
@@ -6,7 +6,7 @@ export default async function RootLayout(props: {
children: React.ReactNode;
}) {
const { params, children } = props;
const context = await getDynamicSiteContext(await params);
const { context } = await getDynamicSiteContext(await params);
return <PDFRootLayout context={context}>{children}</PDFRootLayout>;
}
@@ -6,7 +6,7 @@ export async function generateMetadata({
}: {
params: Promise<RouteLayoutParams>;
}) {
const context = await getDynamicSiteContext(await params);
const { context } = await getDynamicSiteContext(await params);
return generatePDFMetadata(context);
}
@@ -15,6 +15,6 @@ export default async function Page(props: {
searchParams: Promise<{ [key: string]: string }>;
}) {
const { params, searchParams } = props;
const context = await getDynamicSiteContext(await params);
const { context } = await getDynamicSiteContext(await params);
return <PDFPage context={context} searchParams={await searchParams} />;
}
@@ -18,7 +18,7 @@ export default async function Page(props: PageProps) {
'use cache';
const params = await props.params;
const context = await getStaticSiteContext(params);
const { context } = await getStaticSiteContext(params);
const pathname = getPagePathFromParams(params);
cacheTag(
@@ -32,13 +32,13 @@ export default async function Page(props: PageProps) {
}
export async function generateViewport(props: PageProps): Promise<Viewport> {
const context = await getStaticSiteContext(await props.params);
const { context } = await getStaticSiteContext(await props.params);
return generateSitePageViewport(context);
}
export async function generateMetadata(props: PageProps): Promise<Metadata> {
const params = await props.params;
const context = await getStaticSiteContext(params);
const { context } = await getStaticSiteContext(params);
const pathname = getPagePathFromParams(params);
return generateSitePageMetadata({
@@ -19,7 +19,7 @@ export default async function SiteStaticLayout({
}: React.PropsWithChildren<SiteStaticLayoutProps>) {
'use cache';
const context = await getStaticSiteContext(await params);
const { context, visitorAuthClaims } = await getStaticSiteContext(await params);
cacheTag(
getCacheTag({
@@ -33,7 +33,7 @@ export default async function SiteStaticLayout({
<SiteLayout
context={context}
withTracking={!GITBOOK_DISABLE_TRACKING}
visitorAuthToken={null}
visitorAuthClaims={visitorAuthClaims}
>
{children}
</SiteLayout>
@@ -42,11 +42,11 @@ export default async function SiteStaticLayout({
}
export async function generateViewport({ params }: SiteStaticLayoutProps) {
const context = await getStaticSiteContext(await params);
const { context } = await getStaticSiteContext(await params);
return generateSiteLayoutViewport(context);
}
export async function generateMetadata({ params }: SiteStaticLayoutProps) {
const context = await getStaticSiteContext(await params);
const { context } = await getStaticSiteContext(await params);
return generateSiteLayoutMetadata(context);
}
@@ -9,6 +9,6 @@ export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const context = await getStaticSiteContext(await params);
const { context } = await getStaticSiteContext(await params);
return serveLLMsTxt(context);
}
@@ -9,6 +9,6 @@ export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const context = await getStaticSiteContext(await params);
const { context } = await getStaticSiteContext(await params);
return serveRobotsTxt(context);
}
@@ -9,6 +9,6 @@ export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const context = await getStaticSiteContext(await params);
const { context } = await getStaticSiteContext(await params);
return servePagesSitemap(context);
}
@@ -9,6 +9,6 @@ export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const context = await getStaticSiteContext(await params);
const { context } = await getStaticSiteContext(await params);
return serveRootSitemap(context);
}
@@ -9,6 +9,6 @@ export async function GET(
request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const context = await getStaticSiteContext(await params);
const { context } = await getStaticSiteContext(await params);
return serveIcon(context, request);
}
@@ -10,6 +10,6 @@ export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams & PageIdParams> }
) {
const context = await getStaticSiteContext(await params);
const { context } = await getStaticSiteContext(await params);
return serveOGImage(context, await params);
}
+43 -15
View File
@@ -1,9 +1,9 @@
import {
fetchSiteContextByURL,
fetchSiteContextByURLLookup,
getBaseContext,
} from '@v2/lib/context';
import { getSiteURLDataFromMiddleware } from '@v2/lib/middleware';
import { getVisitorAuthClaims, getVisitorAuthClaimsFromToken } from '@/lib/adaptive';
import type { PublishedSiteContent, SiteAPIToken } from '@gitbook/api';
import { fetchSiteContextByURLLookup, getBaseContext } from '@v2/lib/context';
import { jwtDecode } from 'jwt-decode';
import { forbidden } from 'next/navigation';
import rison from 'rison';
export type RouteParamMode = 'url-host' | 'url';
@@ -12,6 +12,9 @@ export type RouteLayoutParams = {
/** URL encoded site URL */
siteURL: string;
/** URL and Rison encoded site data from getPublishedContentByUrl */
siteData: string;
};
export type RouteParams = RouteLayoutParams & {
@@ -21,19 +24,30 @@ export type RouteParams = RouteLayoutParams & {
/**
* Get the static context when rendering statically a site.
*/
export function getStaticSiteContext(params: RouteLayoutParams) {
export async function getStaticSiteContext(params: RouteLayoutParams) {
const siteURL = getSiteURLFromParams(params);
return fetchSiteContextByURL(
const siteURLData = getSiteURLDataFromParams(params);
// For static routes, we check the expiration of the JWT token
// as the route might be revalidated after expiration
const decoded = jwtDecode<SiteAPIToken & { exp: number }>(siteURLData.apiToken);
if (decoded.exp && decoded.exp < Date.now() / 1000 + 120) {
forbidden();
}
const context = await fetchSiteContextByURLLookup(
getBaseContext({
siteURL,
siteURLData,
urlMode: getModeFromParams(params.mode),
}),
{
url: siteURL.toString(),
visitorAuthToken: null,
redirectOnError: false,
}
siteURLData
);
return {
context,
visitorAuthClaims: getVisitorAuthClaimsFromToken(decoded),
};
}
/**
@@ -42,15 +56,21 @@ export function getStaticSiteContext(params: RouteLayoutParams) {
*/
export async function getDynamicSiteContext(params: RouteLayoutParams) {
const siteURL = getSiteURLFromParams(params);
const siteURLData = await getSiteURLDataFromMiddleware();
const siteURLData = getSiteURLDataFromParams(params);
return fetchSiteContextByURLLookup(
const context = await fetchSiteContextByURLLookup(
getBaseContext({
siteURL,
siteURLData,
urlMode: getModeFromParams(params.mode),
}),
siteURLData
);
return {
context,
visitorAuthClaims: getVisitorAuthClaims(siteURLData),
};
}
/**
@@ -74,3 +94,11 @@ function getModeFromParams(mode: string): RouteParamMode {
return 'url';
}
/**
* Get the decoded site data from the params.
*/
function getSiteURLDataFromParams(params: RouteLayoutParams): PublishedSiteContent {
const decoded = decodeURIComponent(params.siteData);
return rison.decode(decoded);
}
+2
View File
@@ -7,6 +7,7 @@ import {
GITBOOK_APP_URL,
GITBOOK_ASSETS_URL,
GITBOOK_DISABLE_TRACKING,
GITBOOK_FONTS_URL,
GITBOOK_ICONS_URL,
GITBOOK_IMAGE_RESIZE_SIGNING_KEY,
GITBOOK_INTEGRATIONS_HOST,
@@ -25,6 +26,7 @@ export async function GET(_req: NextRequest) {
GITBOOK_API_URL,
GITBOOK_API_PUBLIC_URL,
GITBOOK_ASSETS_URL,
GITBOOK_FONTS_URL,
GITBOOK_ICONS_URL,
GITBOOK_USER_AGENT,
GITBOOK_INTEGRATIONS_HOST,
@@ -20,8 +20,10 @@ export async function getSpacePDFContext(
const apiToken = await getAPITokenFromMiddleware();
const basePath = getPDFRoutePath(params);
const linker = createLinker({
pathname: getPDFRoutePath(params),
spaceBasePath: basePath,
siteBasePath: basePath,
});
const dataFetcher = createDataFetcher({
apiToken: apiToken,
+42 -94
View File
@@ -1,7 +1,7 @@
import { getSiteStructureSections } from '@/lib/sites';
import type {
ChangeRequest,
PublishedSiteContentLookup,
PublishedSiteContent,
RevisionPage,
RevisionPageDocument,
Site,
@@ -14,11 +14,10 @@ import type {
Space,
} from '@gitbook/api';
import { type GitBookDataFetcher, createDataFetcher, throwIfDataError } from '@v2/lib/data';
import { redirect } from 'next/navigation';
import { assert } from 'ts-essentials';
import { GITBOOK_URL } from './env';
import { type ImageResizer, createImageResizer } from './images';
import { type GitBookSpaceLinker, createLinker } from './links';
import { type GitBookLinker, createLinker } from './links';
/**
* Generic context when rendering content.
@@ -32,7 +31,7 @@ export type GitBookBaseContext = {
/**
* Linker to generate links in the current space.
*/
linker: GitBookSpaceLinker;
linker: GitBookLinker;
/**
* Image resizer to resize images.
@@ -102,27 +101,45 @@ export type GitBookPageContext = (GitBookSpaceContext | GitBookSiteContext) & {
};
/**
* Get the base context for a request.
* Get the base context for a request on a site.
*/
export function getBaseContext(input: {
siteURL: URL | string;
siteURLData: PublishedSiteContent;
urlMode: 'url' | 'url-host';
apiToken?: string | null;
}) {
const url = typeof input.siteURL === 'string' ? new URL(input.siteURL) : input.siteURL;
const urlMode = input.urlMode;
const { urlMode, siteURLData } = input;
const siteURL = typeof input.siteURL === 'string' ? new URL(input.siteURL) : input.siteURL;
const dataFetcher = createDataFetcher({
apiToken: input.apiToken ?? null,
apiToken: siteURLData.apiToken ?? null,
});
const linker = getLinkerForSiteURL({
siteURL: url,
urlMode,
});
const gitbookURL = GITBOOK_URL ? new URL(GITBOOK_URL) : undefined;
const linker =
urlMode === 'url-host'
? createLinker({
host: siteURL.host,
siteBasePath: siteURLData.siteBasePath,
spaceBasePath: siteURLData.basePath,
})
: createLinker({
protocol: gitbookURL?.protocol,
host: gitbookURL?.host,
siteBasePath: `/url/${siteURL.host}${siteURLData.siteBasePath}`,
spaceBasePath: `/url/${siteURL.host}${siteURLData.basePath}`,
});
if (urlMode === 'url') {
// Create link in the same format for links to other sites/sections.
linker.toLinkForContent = (rawURL: string) => {
const urlObject = new URL(rawURL);
return `/url/${urlObject.host}${urlObject.pathname}${urlObject.search}`;
};
}
const imageResizer = createImageResizer({
host: url.host,
host: siteURL.host,
// To ensure image resizing work for proxied sites,
// we serve images from the root of the site.
linker: linker,
@@ -135,92 +152,23 @@ export function getBaseContext(input: {
};
}
/**
* Get the linker for a given site URL.
*/
export function getLinkerForSiteURL(input: {
siteURL: URL;
urlMode: 'url' | 'url-host';
}) {
const { siteURL, urlMode } = input;
const gitbookURL = GITBOOK_URL ? new URL(GITBOOK_URL) : undefined;
const linker =
urlMode === 'url-host'
? createLinker({
host: siteURL.host,
pathname: siteURL.pathname,
})
: createLinker({
protocol: gitbookURL?.protocol,
host: gitbookURL?.host,
pathname: `/url/${siteURL.host}${siteURL.pathname}`,
});
if (urlMode === 'url') {
// Create link in the same format for links to other sites/sections.
linker.toLinkForContent = (rawURL: string) => {
const urlObject = new URL(rawURL);
return `/url/${urlObject.host}${urlObject.pathname}${urlObject.search}`;
};
}
return linker;
}
/**
* Fetch the context of a site for a given URL and a base context.
*/
export async function fetchSiteContextByURL(
baseContext: GitBookBaseContext,
input: {
url: string;
visitorAuthToken: string | null;
redirectOnError: boolean;
}
): Promise<GitBookSiteContext> {
const { dataFetcher } = baseContext;
const data = await throwIfDataError(
dataFetcher.getPublishedContentByUrl({
url: input.url,
visitorAuthToken: input.visitorAuthToken,
redirectOnError: input.redirectOnError,
})
);
return fetchSiteContextByURLLookup(baseContext, data);
}
/**
* Fetch the context of a site using the resolution of a URL
*/
export async function fetchSiteContextByURLLookup(
baseContext: GitBookBaseContext,
data: PublishedSiteContentLookup
data: PublishedSiteContent
): Promise<GitBookSiteContext> {
const { dataFetcher } = baseContext;
if ('redirect' in data) {
redirect(data.redirect);
}
return await fetchSiteContextByIds(
{
...baseContext,
dataFetcher: dataFetcher.withToken({
apiToken: data.apiToken,
}),
},
{
organization: data.organization,
site: data.site,
siteSection: data.siteSection,
siteSpace: data.siteSpace,
space: data.space,
shareKey: data.shareKey,
changeRequest: data.changeRequest,
revision: data.revision,
}
);
return await fetchSiteContextByIds(baseContext, {
organization: data.organization,
site: data.site,
siteSection: data.siteSection,
siteSpace: data.siteSpace,
space: data.space,
shareKey: data.shareKey,
changeRequest: data.changeRequest,
revision: data.revision,
});
}
/**
+35 -53
View File
@@ -3,12 +3,9 @@ import {
type ComputedContentSource,
GitBookAPI,
type GitBookAPIServiceBinding,
type RenderIntegrationUI,
} from '@gitbook/api';
import {
getCacheTag,
getCacheTagForURL,
getComputedContentSourceCacheTags,
} from '@gitbook/cache-tags';
import { getCacheTag, getComputedContentSourceCacheTags } from '@gitbook/cache-tags';
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_USER_AGENT } from '@v2/lib/env';
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache';
import { wrapDataFetcherError } from './errors';
@@ -144,6 +141,7 @@ export function createDataFetcher(
organizationId: params.organizationId,
spaceId: params.spaceId,
source: params.source,
seed: params.seed,
})
);
},
@@ -159,6 +157,14 @@ export function createDataFetcher(
return trace('searchSiteContent', () => searchSiteContent(input, params));
},
renderIntegrationUi(params) {
return trace('renderIntegrationUi', () =>
renderIntegrationUi(input, {
integrationName: params.integrationName,
request: params.request,
})
);
},
//
// API that are not tied to the token
// where the data is the same for all users
@@ -166,18 +172,6 @@ export function createDataFetcher(
getUserById(userId) {
return trace('getUserById', () => getUserById({ apiToken: null }, { userId }));
},
getPublishedContentByUrl(params) {
return trace('getPublishedContentByUrl', () =>
getPublishedContentByUrl(
{ apiToken: null },
{
url: params.url,
visitorAuthToken: params.visitorAuthToken,
redirectOnError: params.redirectOnError,
}
)
);
},
};
}
@@ -360,6 +354,7 @@ async function getComputedDocument(
spaceId: string;
organizationId: string;
source: ComputedContentSource;
seed: string;
}
) {
'use cache';
@@ -380,6 +375,7 @@ async function getComputedDocument(
const api = await apiClient(input);
const res = await api.spaces.getComputedDocument(params.spaceId, {
source: params.source,
seed: params.seed,
});
return res.data;
});
@@ -436,42 +432,6 @@ async function getLatestOpenAPISpecVersionContent(
});
}
async function getPublishedContentByUrl(
input: DataFetcherInput,
params: {
url: string;
visitorAuthToken: string | null;
redirectOnError: boolean;
}
) {
'use cache';
const { url, visitorAuthToken, redirectOnError } = params;
cacheTag(getCacheTagForURL(url));
cacheLife('days');
return wrapDataFetcherError(async () => {
const api = await apiClient(input);
const res = await api.urls.getPublishedContentByUrl({
url,
visitorAuthToken: visitorAuthToken ?? undefined,
redirectOnError,
});
if ('site' in res.data) {
cacheTag(
getCacheTag({
tag: 'site',
site: res.data.site,
})
);
}
return res.data;
});
}
async function getPublishedContentSite(
input: DataFetcherInput,
params: {
@@ -567,6 +527,28 @@ async function searchSiteContent(
});
}
async function renderIntegrationUi(
input: DataFetcherInput,
params: {
integrationName: string;
request: RenderIntegrationUI;
}
) {
'use cache';
cacheTag(getCacheTag({ tag: 'integration', integration: params.integrationName }));
cacheLife('days');
return wrapDataFetcherError(async () => {
const api = await apiClient(input);
const res = await api.integrations.renderIntegrationUiWithPost(
params.integrationName,
params.request
);
return res.data;
});
}
let loggedServiceBinding = false;
/**
+7 -3
View File
@@ -15,13 +15,14 @@ export async function getPublishedContentByURL(input: {
url: string;
visitorAuthToken: string | null;
redirectOnError: boolean;
apiToken: string | null;
}): Promise<DataFetcherResponse<PublishedSiteContentLookup>> {
const lookupURL = new URL(input.url);
const url = stripURLSearch(lookupURL);
const lookup = getURLLookupAlternatives(url);
const result = await race(lookup.urls, async (alternative, { signal }) => {
const api = await apiClient();
const api = await apiClient({ apiToken: input.apiToken });
const callResult = await trace(
{
@@ -94,12 +95,15 @@ export async function getPublishedContentByURL(input: {
* In both cases, the idea is to use the deepest/longest/most inclusive path to resolve the content.
*/
if (alternative.primary || ('site' in data && data.complete)) {
const changeRequest = data.changeRequest ?? lookup.changeRequest;
const revision = data.revision ?? lookup.revision;
const siteResult: PublishedSiteContentLookup = {
...data,
changeRequest: data.changeRequest ?? lookup.changeRequest,
revision: data.revision ?? lookup.revision,
basePath: joinPath(data.basePath, lookup.basePath ?? ''),
pathname: joinPath(data.pathname, alternative.extraPath),
...(changeRequest ? { changeRequest } : {}),
...(revision ? { revision } : {}),
};
return { data: siteResult };
}
+2 -1
View File
@@ -15,12 +15,13 @@ export async function getPageDocument(
dataFetcher.getDocument({ spaceId: space.id, documentId: page.documentId })
);
}
if (page.computed) {
if ('computed' in page && page.computed) {
return getDataOrNull(
dataFetcher.getComputedDocument({
organizationId: space.organization,
spaceId: space.id,
source: page.computed,
seed: page.computedSeed,
})
);
}
+9 -9
View File
@@ -37,15 +37,6 @@ export interface GitBookDataFetcher {
*/
getUserById(userId: string): Promise<DataFetcherResponse<api.User>>;
/**
* Get a published content by its URL.
*/
getPublishedContentByUrl(params: {
url: string;
visitorAuthToken: string | null;
redirectOnError: boolean;
}): Promise<DataFetcherResponse<api.PublishedSiteContentLookup>>;
/**
* Get a published content site by its organization ID and site ID.
*/
@@ -120,6 +111,7 @@ export interface GitBookDataFetcher {
organizationId: string;
spaceId: string;
source: api.ComputedContentSource;
seed: string;
}): Promise<DataFetcherResponse<api.JSONDocument>>;
/**
@@ -170,4 +162,12 @@ export interface GitBookDataFetcher {
/** Cache bust to ensure the search results are fresh when the space is updated. */
cacheBust?: string;
}): Promise<DataFetcherResponse<api.SearchSpaceResult[]>>;
/**
* Render an integration UI.
*/
renderIntegrationUi(params: {
integrationName: string;
request: api.RenderIntegrationUI;
}): Promise<DataFetcherResponse<api.ContentKitRenderOutput>>;
}
+5
View File
@@ -64,6 +64,11 @@ export const GITBOOK_DISABLE_TRACKING = Boolean(
export const GITBOOK_INTEGRATIONS_HOST =
process.env.GITBOOK_INTEGRATIONS_HOST || 'integrations.gitbook.com';
/**
* Hostname for fonts.
*/
export const GITBOOK_FONTS_URL = process.env.GITBOOK_FONTS_URL || 'https://fonts.gitbook.com';
/**
* Endpoint to use for resizing images.
* It should be a Cloudflare domain with image resizing enabled.
@@ -1,7 +1,7 @@
import 'server-only';
import { GITBOOK_IMAGE_RESIZE_SIGNING_KEY, GITBOOK_IMAGE_RESIZE_URL } from '../env';
import type { GitBookSpaceLinker } from '../links';
import type { GitBookLinker } from '../links';
import { type SignatureVersion, generateImageSignature } from './signatures';
import type { ImageResizer } from './types';
@@ -37,7 +37,7 @@ export function createImageResizer({
linker,
}: {
/** The linker to use to create URLs. */
linker: GitBookSpaceLinker;
linker: GitBookLinker;
/** The host name of the current site. */
host: string;
}): ImageResizer {
@@ -62,7 +62,7 @@ export function createImageResizer({
url: urlInput,
});
const url = linker.toAbsoluteURL(linker.toPathInContent('/~gitbook/image'));
const url = linker.toAbsoluteURL(linker.toPathInSite('/~gitbook/image'));
const searchParams = new URLSearchParams();
searchParams.set('url', getImageAPIUrl(urlInput));
+43 -27
View File
@@ -1,25 +1,51 @@
import { describe, expect, it } from 'bun:test';
import { appendBasePathToLinker, createLinker } from './links';
import { createLinker } from './links';
const root = createLinker({
host: 'docs.company.com',
pathname: '/',
spaceBasePath: '/',
siteBasePath: '/',
});
const variantInSection = createLinker({
host: 'docs.company.com',
pathname: '/section/variant',
spaceBasePath: '/section/variant',
siteBasePath: '/',
});
const siteGitBookIO = createLinker({
host: 'org.gitbook.io',
spaceBasePath: '/sitename/variant/',
siteBasePath: '/sitename/',
});
describe('toPathInContent', () => {
it('should return the correct path', () => {
expect(root.toPathInContent('some/path')).toBe('/some/path');
expect(variantInSection.toPathInContent('some/path')).toBe('/section/variant/some/path');
expect(root.toPathInSpace('some/path')).toBe('/some/path');
expect(variantInSection.toPathInSpace('some/path')).toBe('/section/variant/some/path');
});
it('should handle leading slash', () => {
expect(root.toPathInContent('/some/path')).toBe('/some/path');
expect(variantInSection.toPathInContent('/some/path')).toBe('/section/variant/some/path');
expect(root.toPathInSpace('/some/path')).toBe('/some/path');
expect(variantInSection.toPathInSpace('/some/path')).toBe('/section/variant/some/path');
});
});
describe('toPathInSite', () => {
it('should return the correct path', () => {
expect(root.toPathInSite('some/path')).toBe('/some/path');
expect(siteGitBookIO.toPathInSite('some/path')).toBe('/sitename/some/path');
});
});
describe('toRelativePathInSite', () => {
it('should return the correct path', () => {
expect(root.toRelativePathInSite('/some/path')).toBe('some/path');
expect(siteGitBookIO.toRelativePathInSite('/sitename/some/path')).toBe('some/path');
});
it('should preserve absolute paths outside of the site', () => {
expect(siteGitBookIO.toRelativePathInSite('/outside/some/path')).toBe('/outside/some/path');
});
});
@@ -32,27 +58,17 @@ describe('toAbsoluteURL', () => {
});
});
describe('appendBasePathToLinker', () => {
const prefixedRoot = appendBasePathToLinker(root, '/section/variant');
const prefixedVariantInSection = appendBasePathToLinker(variantInSection, '/base');
describe('toPathInContent', () => {
it('should return the correct path', () => {
expect(prefixedRoot.toPathInContent('some/path')).toBe('/section/variant/some/path');
expect(prefixedVariantInSection.toPathInContent('some/path')).toBe(
'/section/variant/base/some/path'
);
});
describe('toLinkForContent', () => {
it('should return the correct path', () => {
expect(root.toLinkForContent('https://docs.company.com/some/path')).toBe('/some/path');
expect(siteGitBookIO.toLinkForContent('https://org.gitbook.io/sitename/some/path')).toBe(
'/sitename/some/path'
);
});
describe('toAbsoluteURL', () => {
it('should return the correct path', () => {
expect(prefixedRoot.toAbsoluteURL('some/path')).toBe(
'https://docs.company.com/some/path'
);
expect(prefixedVariantInSection.toAbsoluteURL('some/path')).toBe(
'https://docs.company.com/some/path'
);
});
it('should preserve an absolute URL if the site is not the same', () => {
expect(siteGitBookIO.toLinkForContent('https://org.gitbook.io/anothersite/some/path')).toBe(
'https://org.gitbook.io/anothersite/some/path'
);
});
});
+55 -44
View File
@@ -1,4 +1,5 @@
import { getPagePath } from '@/lib/pages';
import { withLeadingSlash, withTrailingSlash } from '@/lib/paths';
import type { RevisionPage, RevisionPageDocument, RevisionPageGroup } from '@gitbook/api';
import warnOnce from 'warn-once';
@@ -7,17 +8,28 @@ import warnOnce from 'warn-once';
*
* URL levels:
*
* https://docs.company.com/section/variant/page
* https://docs.company.com/basename/section/variant/page
*
* toPathInContent('some/path') => /section/variant/some/path
* toPathForPage({ pages, page }) => /section/variant/some/path
* toPathInSpace('some/path') => /basename/section/variant/some/path
* toPathInSite('some/path') => /basename/some/path
* toPathForPage({ pages, page }) => /basename/section/variant/some/path
* toAbsoluteURL('some/path') => https://docs.company.com/some/path
*/
export interface GitBookSpaceLinker {
export interface GitBookLinker {
/**
* Generate an absolute path for a relative path to the current content.
*/
toPathInContent(relativePath: string): string;
toPathInSpace(relativePath: string): string;
/**
* Generate an absolute path for a relative path to the current site.
*/
toPathInSite(relativePath: string): string;
/**
* Transform an absolute path in a site, to a relative path from the root of the site.
*/
toRelativePathInSite(absolutePath: string): string;
/**
* Generate an absolute path for a page in the current content.
@@ -48,14 +60,36 @@ export function createLinker(
servedOn: {
protocol?: string;
host?: string;
pathname: string;
/** The base path of the space */
spaceBasePath: string;
/** The base path of the site */
siteBasePath: string;
}
): GitBookSpaceLinker {
): GitBookLinker {
warnOnce(!servedOn.host, 'No host provided to createLinker. It can lead to issues with links.');
const linker: GitBookSpaceLinker = {
toPathInContent(relativePath: string): string {
return joinPaths(servedOn.pathname, relativePath);
const siteBasePath = withTrailingSlash(withLeadingSlash(servedOn.siteBasePath));
const spaceBasePath = withTrailingSlash(withLeadingSlash(servedOn.spaceBasePath));
const linker: GitBookLinker = {
toPathInSpace(relativePath: string): string {
return joinPaths(spaceBasePath, relativePath);
},
toPathInSite(relativePath: string): string {
return joinPaths(siteBasePath, relativePath);
},
toRelativePathInSite(absolutePath: string): string {
const normalizedPath = withLeadingSlash(absolutePath);
if (!normalizedPath.startsWith(servedOn.siteBasePath)) {
return normalizedPath;
}
return normalizedPath.slice(servedOn.siteBasePath.length);
},
toAbsoluteURL(absolutePath: string): string {
@@ -67,48 +101,25 @@ export function createLinker(
},
toPathForPage({ pages, page, anchor }) {
return linker.toPathInContent(getPagePath(pages, page)) + (anchor ? `#${anchor}` : '');
return linker.toPathInSpace(getPagePath(pages, page)) + (anchor ? `#${anchor}` : '');
},
toLinkForContent(url: string): string {
return url;
toLinkForContent(rawURL: string): string {
const url = new URL(rawURL);
// If the link points to a content in the same site, we return an absolute path
// instead of a full URL; it makes it possible to use router navigation
if (url.hostname === servedOn.host && url.pathname.startsWith(servedOn.siteBasePath)) {
return url.pathname;
}
return rawURL;
},
};
return linker;
}
/**
* Append a prefix to a linker.
*/
export function appendBasePathToLinker(
linker: GitBookSpaceLinker,
basePath: string
): GitBookSpaceLinker {
const linkerWithPrefix: GitBookSpaceLinker = {
toPathInContent(relativePath: string): string {
return linker.toPathInContent(joinPaths(basePath, relativePath));
},
toAbsoluteURL(absolutePath: string): string {
return linker.toAbsoluteURL(absolutePath);
},
toPathForPage({ pages, page, anchor }) {
return (
linkerWithPrefix.toPathInContent(getPagePath(pages, page)) +
(anchor ? `#${anchor}` : '')
);
},
toLinkForContent(url: string): string {
return linker.toLinkForContent(url);
},
};
return linkerWithPrefix;
}
function joinPaths(prefix: string, path: string): string {
const prefixPath = prefix.endsWith('/') ? prefix : `${prefix}/`;
const suffixPath = path.startsWith('/') ? path.slice(1) : path;
-19
View File
@@ -32,11 +32,6 @@ export enum MiddlewareHeaders {
*/
Customization = 'x-gitbook-customization',
/**
* The visitor token used to access this content
*/
VisitorAuthToken = 'x-gitbook-visitor-token',
/**
* The API token used to fetch the content.
* This should only be passed for non-site dynamic routes.
@@ -105,20 +100,6 @@ export async function getThemeFromMiddleware() {
: CustomizationThemeMode.Dark;
}
/**
* Get the visitor auth token from the middleware headers.
* This function should only be called in a dynamic route.
*/
export async function getVisitorAuthTokenFromMiddleware(): Promise<string | null> {
const headersList = await headers();
const visitorAuthToken = headersList.get(MiddlewareHeaders.VisitorAuthToken);
if (!visitorAuthToken) {
return null;
}
return visitorAuthToken;
}
/**
* Get the API token from the middleware headers.
* This function should only be called in a dynamic route.
@@ -16,8 +16,8 @@ export async function getServerActionBaseContext() {
return getBaseContext({
siteURL,
siteURLData,
urlMode,
apiToken: siteURLData.apiToken,
});
}
+197 -156
View File
@@ -1,6 +1,7 @@
import { CustomizationThemeMode } from '@gitbook/api';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import rison from 'rison';
import { getContentSecurityPolicy } from '@/lib/csp';
import { validateSerializedCustomization } from '@/lib/customization';
@@ -13,7 +14,6 @@ import {
normalizeVisitorAuthURL,
} from '@/lib/visitor-token';
import { serveResizedImage } from '@/routes/image';
import { getLinkerForSiteURL } from '@v2/lib/context';
import {
DataFetcherError,
getPublishedContentByURL,
@@ -41,7 +41,7 @@ export async function middleware(request: NextRequest) {
return NextResponse.redirect(normalized.toString());
}
for (const handler of [serveSiteRoutes, servePreviewRoutes]) {
for (const handler of [serveSiteRoutes, serveSpacePDFRoutes]) {
const result = await handler(requestURL, request);
if (result) {
return result;
@@ -83,181 +83,182 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
// Detect and extract the visitor authentication token from the request
//
// @ts-ignore - request typing
const visitorToken = getVisitorToken(request, siteURL);
const visitorToken = getVisitorToken({
cookies: request.cookies.getAll(),
url: siteURL,
});
const data = await throwIfDataError(
getPublishedContentByURL({
url: siteURL.toString(),
visitorAuthToken: visitorToken?.token ?? null,
// When the visitor auth token is pulled from the cookie, set redirectOnError when calling getPublishedContentByUrl to allow
// redirecting when the token is invalid as we could be dealing with stale token stored in the cookie.
// For example when the VA backend signature has changed but the token stored in the cookie is not yet expired.
redirectOnError: visitorToken?.source === 'visitor-auth-cookie',
})
);
let cookies: ResponseCookies = {};
const withAPIToken = async (apiToken: string | null) => {
const siteURLData = await throwIfDataError(
getPublishedContentByURL({
url: siteURL.toString(),
visitorAuthToken: visitorToken?.token ?? null,
// When the visitor auth token is pulled from the cookie, set redirectOnError when calling getPublishedContentByUrl to allow
// redirecting when the token is invalid as we could be dealing with stale token stored in the cookie.
// For example when the VA backend signature has changed but the token stored in the cookie is not yet expired.
redirectOnError: visitorToken?.source === 'visitor-auth-cookie',
//
// Handle redirects
//
if ('redirect' in data) {
// biome-ignore lint/suspicious/noConsole: we want to log the redirect
console.log('redirect', data.redirect);
if (data.target === 'content') {
// For content redirects, we use the linker to redirect the optimal URL
// during development and testing in 'url' mode.
const linker = getLinkerForSiteURL({
siteURL,
urlMode: mode,
});
// Use the API token passed in the request, if any
// as it could be used for .preview hostnames
apiToken,
})
);
const cookies: ResponseCookies = [];
const contentRedirect = new URL(linker.toLinkForContent(data.redirect), request.url);
//
// Handle redirects
//
if ('redirect' in siteURLData) {
// biome-ignore lint/suspicious/noConsole: we want to log the redirect
console.log('redirect', siteURLData.redirect);
if (siteURLData.target === 'content') {
let contentRedirect = new URL(siteURLData.redirect, request.url);
// Keep the same search params as the original request
// as it might contain a VA token
contentRedirect.search = request.nextUrl.search;
// For content redirects, we redirect using the /url/:url format
// during development and testing in 'url' mode.
if (mode === 'url') {
const urlObject = new URL(siteURLData.redirect);
contentRedirect = new URL(
`/url/${urlObject.host}${urlObject.pathname}${urlObject.search}`,
request.url
);
}
return NextResponse.redirect(contentRedirect);
// Keep the same search params as the original request
// as it might contain a VA token
contentRedirect.search = request.nextUrl.search;
return NextResponse.redirect(contentRedirect);
}
return NextResponse.redirect(siteURLData.redirect);
}
return NextResponse.redirect(data.redirect);
}
cookies.push(...getResponseCookiesForVisitorAuth(siteURLData.siteBasePath, visitorToken));
cookies = {
...cookies,
...getResponseCookiesForVisitorAuth(data.basePath, visitorToken),
//
// Make sure the URL is clean of any va token after a successful lookup
// The token is stored in a cookie that is set on the redirect response
//
const incomingURL = mode === 'url' ? requestURL : siteURL;
const requestURLWithoutToken = normalizeVisitorAuthURL(incomingURL);
if (
requestURLWithoutToken !== incomingURL &&
requestURLWithoutToken.toString() !== incomingURL.toString()
) {
return writeResponseCookies(
NextResponse.redirect(requestURLWithoutToken.toString()),
cookies
);
}
//
// Render and serve the content
//
// The route is static, except when using dynamic parameters from query params
// (customization override, theme, etc)
let routeType: 'dynamic' | 'static' = 'static';
const requestHeaders = new Headers(request.headers);
requestHeaders.set(MiddlewareHeaders.RouteType, routeType);
requestHeaders.set(MiddlewareHeaders.URLMode, mode);
requestHeaders.set(MiddlewareHeaders.SiteURL, `${siteURL.origin}${siteURLData.basePath}`);
requestHeaders.set(MiddlewareHeaders.SiteURLData, JSON.stringify(siteURLData));
// Preview of customization/theme
const customization = siteURL.searchParams.get('customization');
if (customization && validateSerializedCustomization(customization)) {
routeType = 'dynamic';
requestHeaders.set(MiddlewareHeaders.Customization, customization);
}
const theme = siteURL.searchParams.get('theme');
if (theme === CustomizationThemeMode.Dark || theme === CustomizationThemeMode.Light) {
routeType = 'dynamic';
requestHeaders.set(MiddlewareHeaders.Theme, theme);
}
// We support forcing dynamic routes by setting a `gitbook-dynamic-route` cookie
// This is useful for testing dynamic routes.
if (request.cookies.has('gitbook-dynamic-route')) {
routeType = 'dynamic';
}
// Pass a x-forwarded-host and origin that are equal to ensure Next doesn't block server actions when proxied
requestHeaders.set('x-forwarded-host', request.nextUrl.host);
requestHeaders.set('origin', request.nextUrl.origin);
const siteURLWithoutProtocol = `${siteURL.host}${siteURLData.basePath}`;
const { pathname, routeType: routeTypeFromPathname } = encodePathInSiteContent(
siteURLData.pathname
);
routeType = routeTypeFromPathname ?? routeType;
const route = [
'sites',
routeType,
mode,
encodeURIComponent(siteURLWithoutProtocol),
encodeURIComponent(rison.encode(siteURLData)),
pathname,
].join('/');
console.log(`rewriting ${request.nextUrl.toString()} to ${route}`);
const rewrittenURL = new URL(`/${route}`, request.nextUrl.toString());
const response = NextResponse.rewrite(rewrittenURL, {
request: {
headers: requestHeaders,
},
});
// Add Content Security Policy header
response.headers.set('content-security-policy', getContentSecurityPolicy());
// Basic security headers
response.headers.set('strict-transport-security', 'max-age=31536000');
response.headers.set('referrer-policy', 'no-referrer-when-downgrade');
response.headers.set('x-content-type-options', 'nosniff');
// Debug header
response.headers.set('x-gitbook-route-type', routeType);
response.headers.set('x-gitbook-route-site', siteURLWithoutProtocol);
return writeResponseCookies(response, cookies);
};
//
// Make sure the URL is clean of any va token after a successful lookup
// The token is stored in a cookie that is set on the redirect response
//
const requestURLWithoutToken = normalizeVisitorAuthURL(requestURL);
if (requestURLWithoutToken.toString() !== requestURL.toString()) {
return writeResponseCookies(
NextResponse.redirect(requestURLWithoutToken.toString()),
cookies
// For https://preview/<siteURL> requests,
if (siteURL.hostname === 'preview') {
return serveWithQueryAPIToken(
// We scope the API token to the site ID.
`${siteURL.hostname}/${requestURL.pathname.slice(1).split('/')[0]}`,
request,
withAPIToken
);
}
//
// Render and serve the content
//
// When visitor has authentication (adaptive content or VA), we serve dynamic routes.
let routeType = visitorToken ? 'dynamic' : 'static';
const requestHeaders = new Headers(request.headers);
requestHeaders.set(MiddlewareHeaders.RouteType, routeType);
requestHeaders.set(MiddlewareHeaders.URLMode, mode);
requestHeaders.set(MiddlewareHeaders.SiteURL, `${siteURL.origin}${data.basePath}`);
requestHeaders.set(MiddlewareHeaders.SiteURLData, JSON.stringify(data));
// Preview of customization/theme
const customization = siteURL.searchParams.get('customization');
if (customization && validateSerializedCustomization(customization)) {
routeType = 'dynamic';
requestHeaders.set(MiddlewareHeaders.Customization, customization);
}
const theme = siteURL.searchParams.get('theme');
if (theme === CustomizationThemeMode.Dark || theme === CustomizationThemeMode.Light) {
routeType = 'dynamic';
requestHeaders.set(MiddlewareHeaders.Theme, theme);
}
// We support forcing dynamic routes by setting a `gitbook-dynamic-route` cookie
// This is useful for testing dynamic routes.
if (request.cookies.has('gitbook-dynamic-route')) {
routeType = 'dynamic';
}
// Pass a x-forwarded-host and origin that are equal to ensure Next doesn't block server actions when proxied
requestHeaders.set('x-forwarded-host', request.nextUrl.host);
requestHeaders.set('origin', request.nextUrl.origin);
const siteURLWithoutProtocol = `${siteURL.host}${data.basePath}`;
const { pathname, routeType: routeTypeFromPathname } = encodePathInSiteContent(data.pathname);
routeType = routeTypeFromPathname ?? routeType;
const route = [
'sites',
routeType,
mode,
encodeURIComponent(siteURLWithoutProtocol),
pathname,
].join('/');
console.log(`rewriting ${request.nextUrl.toString()} to ${route}`);
const rewrittenURL = new URL(`/${route}`, request.nextUrl.toString());
const response = NextResponse.rewrite(rewrittenURL, {
request: {
headers: requestHeaders,
},
});
// Add Content Security Policy header
response.headers.set('content-security-policy', getContentSecurityPolicy());
// Basic security headers
response.headers.set('strict-transport-security', 'max-age=31536000');
response.headers.set('referrer-policy', 'no-referrer-when-downgrade');
response.headers.set('x-content-type-options', 'nosniff');
// Debug header
response.headers.set('x-gitbook-route-type', routeType);
response.headers.set('x-gitbook-route-site', siteURLWithoutProtocol);
return writeResponseCookies(response, cookies);
return withAPIToken(null);
}
/**
* Serve routes for previewing unpublished content.
* Routes are:
* - PDF export for a space: /~space/:spaceId/~gitbook/pdf
* - Preview of an unpublished site: /~site/:siteId/
* Serve routes for PDF export for a space: /~space/:spaceId/~gitbook/pdf
*/
async function servePreviewRoutes(requestURL: URL, request: NextRequest) {
async function serveSpacePDFRoutes(requestURL: URL, request: NextRequest) {
const pathnameParts = requestURL.pathname.slice(1).split('/');
if (pathnameParts[0] !== '~space' && pathnameParts[0] !== '~site') {
return null;
}
// We store the API token in a cookie that is scoped to the specific preview route
// to avoid errors when multiple previews are opened in different tabs.
const cookieName = getPathScopedCookieName(
'gitbook-api-token',
pathnameParts.slice(0, 2).join('/')
);
// Extract a potential GitBook API token passed in the request
// If found, we redirect to the same URL but with the token in the cookie
const queryAPIToken = requestURL.searchParams.get('token');
if (queryAPIToken) {
requestURL.searchParams.delete('token');
return writeResponseCookies(NextResponse.redirect(requestURL.toString()), {
[cookieName]: {
value: queryAPIToken,
options: {
httpOnly: true,
sameSite: process.env.NODE_ENV === 'production' ? 'none' : undefined,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60, // 1 hour
return serveWithQueryAPIToken(
pathnameParts.slice(0, 2).join('/'),
request,
async (apiToken) => {
// Handle the rest with the router default logic
return NextResponse.next({
headers: {
[MiddlewareHeaders.APIToken]: apiToken,
},
},
});
}
const apiToken = request.cookies.get(cookieName)?.value;
if (!apiToken) {
throw new DataFetcherError('Missing API token', 400);
}
// Handle the rest with the router default logic
return NextResponse.next({
headers: {
[MiddlewareHeaders.APIToken]: apiToken,
},
});
});
}
);
}
/**
@@ -274,6 +275,45 @@ function serveErrorResponse(error: Error) {
throw error;
}
/**
* Server a response with an API token obtained from the query params.
*/
async function serveWithQueryAPIToken(
scopePath: string,
request: NextRequest,
serve: (apiToken: string) => Promise<NextResponse>
) {
// We store the API token in a cookie that is scoped to the specific route
// to avoid errors when multiple previews are opened in different tabs.
const cookieName = getPathScopedCookieName('gitbook-api-token', scopePath);
// Extract a potential GitBook API token passed in the request
// If found, we redirect to the same URL but with the token in the cookie
const queryAPIToken = request.nextUrl.searchParams.get('token');
if (queryAPIToken) {
request.nextUrl.searchParams.delete('token');
return writeResponseCookies(NextResponse.redirect(request.nextUrl.toString()), [
{
name: cookieName,
value: queryAPIToken,
options: {
httpOnly: true,
sameSite: process.env.NODE_ENV === 'production' ? 'none' : undefined,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60, // 1 hour
},
},
]);
}
const apiToken = request.cookies.get(cookieName)?.value;
if (!apiToken) {
throw new DataFetcherError('Missing API token', 400);
}
return serve(apiToken);
}
/**
* The URL of the GitBook content can be passed in 3 different ways (in order of priority):
* - The request has a `X-GitBook-URL` header:
@@ -346,6 +386,7 @@ function encodePathInSiteContent(rawPathname: string): {
case '~gitbook/icon':
case 'llms.txt':
case 'sitemap.xml':
case 'sitemap-pages.xml':
case 'robots.txt':
return { pathname };
case '~gitbook/pdf':
@@ -371,8 +412,8 @@ function appendQueryParams(url: URL, from: URLSearchParams) {
* Write the cookies to a response.
*/
function writeResponseCookies<R extends NextResponse>(response: R, cookies: ResponseCookies): R {
Object.entries(cookies).forEach(([key, { value, options }]) => {
response.cookies.set(key, value, options);
cookies.forEach((cookie) => {
response.cookies.set(cookie.name, cookie.value, cookie.options);
});
return response;
+24
View File
@@ -1,5 +1,29 @@
# gitbook
## 0.8.2
### Patch Changes
- ed07206: Fix OpenAPI path overflow on mobile
- 4dab1c5: Fix alignment of `prominent` search bar on full-width pages
- 6eae764: Support body examples
- 54ee014: Add initial support for loading custom fonts
- d2facb2: Mark properties as optional if not required
- bba2e52: Fix site redirects when it includes a section/variant path
- 4723f03: Restyle section group dropdown
- 24b7808: Fix `prominent` search bar width on `md` screens
- 1fe3286: Fix OpenAPI block overflow issue
- Updated dependencies [48c18c0]
- Updated dependencies [6eae764]
- Updated dependencies [7212973]
- Updated dependencies [d2facb2]
- Updated dependencies [73e2b47]
- Updated dependencies [70be2c6]
- Updated dependencies [fc00b51]
- Updated dependencies [a84b06b]
- @gitbook/openapi-parser@2.1.1
- @gitbook/react-openapi@1.1.6
## 0.8.1
### Patch Changes
+34 -20
View File
@@ -1,4 +1,4 @@
import { type TestsCase, runTestCases } from './util';
import { type TestsCase, runTestCases, waitForCookiesDialog } from './util';
/** A list of test cases to run on the customers' docs sites. */
const testCases: TestsCase[] = [
@@ -6,14 +6,21 @@ const testCases: TestsCase[] = [
name: 'Snyk',
contentBaseURL: 'https://docs.snyk.io',
tests: [
{ name: 'Home', url: '/' },
{ name: 'OpenAPI', url: '/snyk-api/reference/apps' },
{ name: 'Home', url: '/', run: waitForCookiesDialog },
{ name: 'OpenAPI', url: '/snyk-api/reference/apps', run: waitForCookiesDialog },
],
},
{
name: 'Nexthink',
contentBaseURL: 'https://docs.nexthink.com',
tests: [{ name: 'Home', url: '/', screenshot: { waitForTOCScrolling: false } }],
tests: [
{
name: 'Home',
url: '/',
screenshot: { waitForTOCScrolling: false },
run: waitForCookiesDialog,
},
],
},
{
name: 'asiksupport-stg.dto.kemkes.go.id',
@@ -33,7 +40,7 @@ const testCases: TestsCase[] = [
{
name: 'docs.dify.ai',
contentBaseURL: 'https://docs.dify.ai',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'seeddao.gitbook.io',
@@ -63,27 +70,27 @@ const testCases: TestsCase[] = [
{
name: 'docs.portainer.io',
contentBaseURL: 'https://docs.portainer.io',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.chirptoken.io',
contentBaseURL: 'https://docs.chirptoken.io',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.dexscreener.com',
contentBaseURL: 'https://docs.dexscreener.com',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.pancakeswap.finance',
contentBaseURL: 'https://docs.pancakeswap.finance',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'book.character.ai',
contentBaseURL: 'https://book.character.ai',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.tradeonnova.io',
@@ -103,7 +110,7 @@ const testCases: TestsCase[] = [
{
name: 'docs.keeper.io',
contentBaseURL: 'https://docs.keeper.io',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'adiblar.gitbook.io',
@@ -138,12 +145,12 @@ const testCases: TestsCase[] = [
{
name: 'meshnet.nordvpn.com',
contentBaseURL: 'https://meshnet.nordvpn.com',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'manual.bubble.io',
contentBaseURL: 'https://manual.bubble.io',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.tickettool.xyz',
@@ -158,12 +165,12 @@ const testCases: TestsCase[] = [
{
name: 'docs.cherry-ai.com',
contentBaseURL: 'https://docs.cherry-ai.com',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.snyk.io',
contentBaseURL: 'https://docs.snyk.io',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.realapp.link',
@@ -188,7 +195,14 @@ const testCases: TestsCase[] = [
{
name: 'docs.umbraco.com',
contentBaseURL: 'https://docs.umbraco.com',
tests: [{ name: 'Home', url: '/welcome', screenshot: { waitForTOCScrolling: false } }],
tests: [
{
name: 'Home',
url: '/welcome',
run: waitForCookiesDialog,
screenshot: { waitForTOCScrolling: false },
},
],
},
{
name: 'sosovalue-white-paper.gitbook.io',
@@ -198,12 +212,12 @@ const testCases: TestsCase[] = [
{
name: 'docs.revrobotics.com',
contentBaseURL: 'https://docs.revrobotics.com',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'chartschool.stockcharts.com',
contentBaseURL: 'https://chartschool.stockcharts.com',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.soniclabs.com',
@@ -218,7 +232,7 @@ const testCases: TestsCase[] = [
{
name: 'docs.thousandeyes.com',
contentBaseURL: 'https://docs.thousandeyes.com',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.raydium.io',
@@ -228,7 +242,7 @@ const testCases: TestsCase[] = [
{
name: 'docs.fluentbit.io',
contentBaseURL: 'https://docs.fluentbit.io',
tests: [{ name: 'Home', url: '/' }],
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
];
+45 -3
View File
@@ -367,6 +367,29 @@ const testCases: TestsCase[] = [
},
],
},
{
name: 'Site Preview',
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
tests: [
{
name: 'Main content',
url: async () => {
const data = await getSiteAPIToken(
'https://gitbook.gitbook.io/test-gitbook-open/'
);
const searchParams = new URLSearchParams();
searchParams.set('token', data.apiToken);
return `url/preview/${data.site}/?${searchParams.toString()}`;
},
screenshot: false,
run: async (page) => {
await expect(page.locator('[data-testid="table-of-contents"]')).toBeVisible();
},
},
],
},
{
name: 'Content tests',
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
@@ -386,7 +409,7 @@ const testCases: TestsCase[] = [
url: 'blocks/block-images',
run: waitForCookiesDialog,
fullPage: true,
screenshot: { threshold: 0.8 },
screenshot: { threshold: 0.9 },
},
{
name: 'Images (with zoom)',
@@ -397,7 +420,6 @@ const testCases: TestsCase[] = [
await zoomImage.first().click();
await expect(page.getByTestId('zoom-image-modal')).toBeVisible();
},
fullPage: true,
screenshot: { threshold: 0.8 },
},
{
@@ -432,7 +454,12 @@ const testCases: TestsCase[] = [
{
name: 'Integration Blocks',
url: 'blocks/integrations',
run: waitForCookiesDialog,
run: async (page) => {
await waitForCookiesDialog(page);
const mermaidIframe = page.locator('iframe[title*="mermaid"]').contentFrame();
await expect(mermaidIframe.getByText('Mermaid', { exact: true })).toBeVisible();
await expect(mermaidIframe.getByText('Diagram', { exact: true })).toBeVisible();
},
},
{
name: 'Tables',
@@ -781,6 +808,21 @@ const testCases: TestsCase[] = [
},
],
},
{
name: 'Site Redirects with sections',
contentBaseURL: 'https://gitbook-open-e2e-sites.gitbook.io/sections/',
tests: [
{
// This test that a redirect that incudes a section path works
name: 'Redirect to Quickstart page',
url: 'sections-2/redirect-test',
run: async (page) => {
await expect(page.locator('h1')).toHaveText('Quickstart');
},
screenshot: false,
},
],
},
{
name: 'Share links',
contentBaseURL: 'https://gitbook.gitbook.io/gbo-tests-share-links/',
+71 -31
View File
@@ -63,6 +63,7 @@ export interface Test {
export type TestsCase = {
name: string;
skip?: boolean;
tests: Array<Test>;
contentBaseURL?: string;
};
@@ -133,11 +134,8 @@ export const headerLinks: CustomizationHeaderItem[] = [
];
export async function waitForCookiesDialog(page: Page) {
const dialog = page.getByRole('dialog', { name: 'Cookies' });
const accept = dialog.getByRole('button', { name: 'Accept' });
const reject = dialog.getByRole('button', { name: 'Reject' });
await expect(accept).toBeVisible();
await expect(reject).toBeVisible();
const dialog = page.getByTestId('cookies-dialog');
await expect(dialog).toBeVisible();
}
/**
@@ -145,6 +143,10 @@ export async function waitForCookiesDialog(page: Page) {
*/
export function runTestCases(testCases: TestsCase[]) {
for (const testCase of testCases) {
if (testCase.skip) {
continue;
}
test.describe(testCase.name, () => {
for (const testEntry of testCase.tests) {
const testFn = testEntry.only ? test.only : test;
@@ -190,14 +192,15 @@ export function runTestCases(testCases: TestsCase[]) {
await argosScreenshot(page, `${testCase.name} - ${testEntry.name}`, {
viewports: ['macbook-16', 'macbook-13', 'ipad-2', 'iphone-x'],
argosCSS: `
/* Hide Intercom */
.intercom-lightweight-app {
display: none !important;
/* Hide Intercom */
.intercom-lightweight-app {
display: none !important;
}
`,
threshold: screenshotOptions?.threshold ?? undefined,
fullPage: testEntry.fullPage ?? false,
beforeScreenshot: async () => {
beforeScreenshot: async ({ runStabilization }) => {
await runStabilization();
await waitForIcons(page);
if (screenshotOptions?.waitForTOCScrolling !== false) {
await waitForTOCScrolling(page);
@@ -319,29 +322,66 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
* Wait for all icons present on the page to be loaded.
*/
async function waitForIcons(page: Page) {
await page.waitForFunction(async () => {
function loadImage(src: string) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = (_error) => reject(new Error(`Failed to load image: ${src}`));
img.src = src;
});
}
await page.waitForFunction(() => {
const urls = new Set<string>();
const icons = Array.from(document.querySelectorAll('svg.gb-icon'));
await Promise.all(
icons.map(async (icon) => {
// url("https://ka-p.fontawesome.com/releases/v6.6.0/svgs/light/moon.svg?v=2&token=a463935e93")
const maskImage = window.getComputedStyle(icon).getPropertyValue('mask-image');
const urlMatch = maskImage.match(/url\("([^"]+)"\)/);
const url = urlMatch ? urlMatch[1] : null;
if (!url) {
throw new Error('No mask-image');
}
await loadImage(url);
})
);
const results = icons.map((icon) => {
if (!(icon instanceof SVGElement)) {
throw new Error('Icon is not an SVGElement');
}
// If loaded, good it passes the test.
if (icon.dataset.loadingState === 'loaded') {
return true;
}
// If not loaded yet, we need to load it.
if (icon.dataset.loadingState === 'pending') {
return false;
}
// Ignore icons that are not visible.
if (!icon.checkVisibility()) {
return true;
}
// url("https://ka-p.fontawesome.com/releases/v6.6.0/svgs/light/moon.svg?v=2&token=a463935e93")
const maskImage = window.getComputedStyle(icon).getPropertyValue('mask-image');
const urlMatch = maskImage.match(/url\("([^"]+)"\)/);
const url = urlMatch ? urlMatch[1] : null;
// If URL is invalid we throw an error.
if (!url) {
throw new Error('No mask-image');
}
// If the URL is already loaded, we just mark it as loaded.
if (urls.has(url)) {
icon.dataset.loadingState = 'loaded';
return true;
}
// Mark the icon as pending and load the image.
icon.dataset.loadingState = 'pending';
// Mark the URL as seen.
urls.add(url);
const img = new Image();
img.src = url;
img.decode().then(() => {
// Wait two frames to let the time to the icon to repaint.
requestAnimationFrame(() => {
requestAnimationFrame(() => {
icon.dataset.loadingState = 'loaded';
});
});
});
return false;
});
return results.every((x) => x);
});
}
+16 -11
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "0.8.1",
"version": "0.8.2",
"private": true,
"scripts": {
"dev": "env-cmd --silent -f ../../.env.local next dev",
@@ -33,6 +33,7 @@
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.16",
"@upstash/redis": "^1.27.1",
"ai": "^4.1.46",
"ajv": "^8.12.0",
"assert-never": "^1.2.1",
"bun-types": "^1.1.20",
@@ -40,13 +41,12 @@
"framer-motion": "^10.16.14",
"js-cookie": "^3.0.5",
"jsontoxml": "^1.0.1",
"jwt-decode": "^4.0.0",
"katex": "^0.16.9",
"mathjax": "^3.2.2",
"mdast-util-to-markdown": "^2.1.2",
"memoizee": "^0.4.15",
"micromark": "^4.0.1",
"micromark-extension-gfm": "^3.0.0",
"next": "14.2.23",
"next": "14.2.25",
"next-themes": "^0.2.1",
"nuqs": "^2.2.3",
"object-hash": "^3.0.0",
@@ -56,22 +56,25 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"react-hotkeys-hook": "^4.4.1",
"rehype-sanitize": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.1",
"rison": "^0.1.1",
"server-only": "^0.0.1",
"shiki": "^3.2.0",
"tailwind-merge": "^2.2.0",
"tailwind-shades": "^1.1.2",
"unified": "^11.0.5",
"url-join": "^5.0.0",
"usehooks-ts": "^3.1.0",
"ai": "^4.1.46",
"jwt-decode": "^4.0.0"
"usehooks-ts": "^3.1.0"
},
"devDependencies": {
"@argos-ci/playwright": "^3.10.0",
"@argos-ci/playwright": "^4.3.0",
"@cloudflare/next-on-pages": "1.13.7",
"vercel": "^39.3.0",
"@cloudflare/workers-types": "^4.20241230.0",
"@playwright/test": "^1.49.1",
"@playwright/test": "^1.51.1",
"@types/js-cookie": "^3.0.6",
"@types/jsontoxml": "^1.0.5",
"@types/jsonwebtoken": "^9.0.6",
@@ -89,8 +92,10 @@
"jsonwebtoken": "^9.0.2",
"postcss": "^8",
"psi": "^4.1.0",
"stylelint": "^16.16.0",
"tailwindcss": "^3.4.0",
"ts-essentials": "^10.0.1",
"typescript": "^5.5.3"
"typescript": "^5.5.3",
"vercel": "^39.3.0"
}
}
+3
View File
@@ -18,5 +18,8 @@ export default defineConfig({
use: {
trace: 'on-first-retry',
screenshot: 'only-on-failure',
contextOptions: {
reducedMotion: 'reduce',
},
},
});
@@ -1,4 +1,4 @@
import { getThemeFromMiddleware, getVisitorAuthTokenFromMiddleware } from '@v2/lib/middleware';
import { getSiteURLDataFromMiddleware, getThemeFromMiddleware } from '@v2/lib/middleware';
import type { Metadata, Viewport } from 'next';
import type React from 'react';
@@ -7,6 +7,7 @@ import {
generateSiteLayoutMetadata,
generateSiteLayoutViewport,
} from '@/components/SiteLayout';
import { getVisitorAuthClaims } from '@/lib/adaptive';
import { getSiteContentPointer } from '@/lib/pointer';
import { shouldTrackEvents } from '@/lib/tracking';
import { fetchV1ContextForSitePointer } from '@/lib/v1';
@@ -22,14 +23,14 @@ export default async function ContentLayout(props: { children: React.ReactNode }
const context = await fetchLayoutData();
const queryStringTheme = await getThemeFromMiddleware();
const visitorAuthToken = await getVisitorAuthTokenFromMiddleware();
const siteData = await getSiteURLDataFromMiddleware();
return (
<SiteLayout
context={context}
forcedTheme={queryStringTheme}
withTracking={await shouldTrackEvents()}
visitorAuthToken={visitorAuthToken}
visitorAuthClaims={getVisitorAuthClaims(siteData)}
>
{children}
</SiteLayout>
@@ -1,47 +1,13 @@
import type { ContentKitContext, DocumentBlockIntegration } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import {
ContentKit,
ContentKitOutput,
type ContentKitServerContext,
} from '@gitbook/react-contentkit';
import { renderIntegrationUi } from '@/lib/api';
import { parseMarkdown } from '@/lib/markdown';
import { tcls } from '@/lib/tailwind';
import type { DocumentBlockIntegration, RenderIntegrationUI } from '@gitbook/api';
import { ContentKit, ContentKitOutput } from '@gitbook/react-contentkit';
import { GITBOOK_INTEGRATIONS_HOST } from '@v2/lib/env';
import type { BlockProps } from '../Block';
import { PlainCodeBlock } from '../CodeBlock';
import './contentkit.css';
import { ignoreAllThrownError } from '@v2/lib/data';
const outputContext: ContentKitServerContext = {
icons: {
maximize: (props) => <Icon icon="maximize" {...props} />,
edit: (props) => <Icon icon="edit" {...props} />,
github: (props) => <Icon icon="github" {...props} />,
gitlab: (props) => <Icon icon="gitlab" {...props} />,
close: (props) => <Icon icon="x" {...props} />,
email: (props) => <Icon icon="envelope" {...props} />,
settings: (props) => <Icon icon="gear" {...props} />,
search: (props) => <Icon icon="magnifying-glass" {...props} />,
delete: (props) => <Icon icon="trash" {...props} />,
star: (props) => <Icon icon="star" {...props} />,
warning: (props) => <Icon icon="triangle-exclamation" {...props} />,
link: (props) => <Icon icon="link" {...props} />,
'link-external': (props) => <Icon icon="arrow-up-right-from-square" {...props} />,
eye: (props) => <Icon icon="eye" {...props} />,
lock: (props) => <Icon icon="lock" {...props} />,
},
codeBlock: (props) => {
return <PlainCodeBlock code={props.code} syntax={props.syntax} />;
},
markdown: async ({ className, markdown }) => {
const parsed = await parseMarkdown(markdown);
return <div className={className} dangerouslySetInnerHTML={{ __html: parsed }} />;
},
};
import { getDataOrNull } from '@v2/lib/data';
import { contentKitServerContext } from './contentkit';
import { renderIntegrationUi } from './server-actions';
export async function IntegrationBlock(props: BlockProps<DocumentBlockIntegration>) {
const { block, context, style } = props;
@@ -50,22 +16,25 @@ export async function IntegrationBlock(props: BlockProps<DocumentBlockIntegratio
throw new Error('integration block requires a content.spaceId');
}
const contentKitContext: ContentKitContext = {
type: 'document',
spaceId: context.contentContext?.space.id,
editable: false,
theme: 'light', // TODO: how to handle this without moving rendering to the client side?
};
const { dataFetcher } = context.contentContext;
const initialInput = {
const initialInput: RenderIntegrationUI = {
componentId: block.data.block,
props: block.data.props,
action: block.data.action,
context: contentKitContext,
context: {
type: 'document',
spaceId: context.contentContext?.space.id,
editable: false,
theme: 'light', // TODO: how to handle this without moving rendering to the client side?
},
};
const initialOutput = await ignoreAllThrownError(
renderIntegrationUi(block.data.integration, initialInput)
const initialOutput = await getDataOrNull(
dataFetcher.renderIntegrationUi({
integrationName: block.data.integration,
request: initialInput,
})
);
if (!initialOutput || initialOutput.type === 'complete') {
return null;
@@ -74,21 +43,15 @@ export async function IntegrationBlock(props: BlockProps<DocumentBlockIntegratio
return (
<div className={tcls(style)}>
<ContentKit
renderContext={{
integrationName: block.data.integration,
}}
security={{ firstPartyDomains: [GITBOOK_INTEGRATIONS_HOST] }}
initialInput={initialInput}
initialOutput={initialOutput}
render={async (request) => {
'use server';
const output = await renderIntegrationUi(block.data.integration, request);
return {
children: <ContentKitOutput output={output} context={outputContext} />,
output: output,
};
}}
render={renderIntegrationUi}
>
<ContentKitOutput output={initialOutput} context={outputContext} />
<ContentKitOutput output={initialOutput} context={contentKitServerContext} />
</ContentKit>
</div>
);
@@ -0,0 +1,31 @@
import { parseMarkdown } from '@/lib/markdown';
import { Icon } from '@gitbook/icons';
import type { ContentKitServerContext } from '@gitbook/react-contentkit';
import { PlainCodeBlock } from '../CodeBlock';
export const contentKitServerContext: ContentKitServerContext = {
icons: {
maximize: (props) => <Icon icon="maximize" {...props} />,
edit: (props) => <Icon icon="edit" {...props} />,
github: (props) => <Icon icon="github" {...props} />,
gitlab: (props) => <Icon icon="gitlab" {...props} />,
close: (props) => <Icon icon="x" {...props} />,
email: (props) => <Icon icon="envelope" {...props} />,
settings: (props) => <Icon icon="gear" {...props} />,
search: (props) => <Icon icon="magnifying-glass" {...props} />,
delete: (props) => <Icon icon="trash" {...props} />,
star: (props) => <Icon icon="star" {...props} />,
warning: (props) => <Icon icon="triangle-exclamation" {...props} />,
link: (props) => <Icon icon="link" {...props} />,
'link-external': (props) => <Icon icon="arrow-up-right-from-square" {...props} />,
eye: (props) => <Icon icon="eye" {...props} />,
lock: (props) => <Icon icon="lock" {...props} />,
},
codeBlock: (props) => {
return <PlainCodeBlock code={props.code} syntax={props.syntax} />;
},
markdown: async ({ className, markdown }) => {
const parsed = await parseMarkdown(markdown);
return <div className={className} dangerouslySetInnerHTML={{ __html: parsed }} />;
},
};
@@ -0,0 +1,37 @@
'use server';
import { getV1BaseContext } from '@/lib/v1';
import { isV2 } from '@/lib/v2';
import type { RenderIntegrationUI } from '@gitbook/api';
import { ContentKitOutput } from '@gitbook/react-contentkit';
import { throwIfDataError } from '@v2/lib/data';
import { getServerActionBaseContext } from '@v2/lib/server-actions';
import { contentKitServerContext } from './contentkit';
/**
* Server action to render an integration UI request from <ContentKit />.
* See `render` prop in <ContentKit /> for more details.
*/
export async function renderIntegrationUi({
renderContext,
request,
}: {
renderContext: {
integrationName: string;
};
request: RenderIntegrationUI;
}) {
const serverAction = isV2() ? await getServerActionBaseContext() : await getV1BaseContext();
const output = await throwIfDataError(
serverAction.dataFetcher.renderIntegrationUi({
integrationName: renderContext.integrationName,
request,
})
);
return {
children: <ContentKitOutput output={output} context={contentKitServerContext} />,
output: output,
};
}
@@ -20,7 +20,7 @@ import type { AnyOpenAPIOperationsBlock } from '@/lib/openapi/types';
export async function OpenAPIOperation(props: BlockProps<AnyOpenAPIOperationsBlock>) {
const { style } = props;
return (
<div className={tcls('flex w-full', style, 'max-w-full')}>
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
<OpenAPIOperationBody {...props} />
</div>
);
@@ -1,6 +1,6 @@
/* Layout Components */
.openapi-operation {
@apply flex-1 flex flex-col gap-8 mb-14;
@apply flex-1 flex flex-col gap-8 mb-14 min-w-0;
}
.openapi-schemas {
@@ -201,6 +201,10 @@
@apply text-warning-subtle text-[0.813rem];
}
.openapi-schema-optional {
@apply text-info-subtle text-[0.813rem];
}
.openapi-schema-readonly {
@apply text-primary-subtle/9 text-[0.813rem];
}
@@ -392,12 +396,18 @@
}
.openapi-codesample-footer {
@apply flex w-full justify-end;
@apply flex gap-3 w-full justify-between flex-wrap;
}
.openapi-codesample-selectors {
@apply flex flex-row items-center gap-3 flex-wrap;
}
/* Path */
.openapi-path {
@apply flex items-center text-sm gap-2 h-fit;
@apply flex items-start text-sm gap-2 h-fit overflow-x-auto min-w-0 max-w-full;
scrollbar-width: none;
-ms-overflow-style: none;
}
.openapi-path-variable {
@@ -409,14 +419,13 @@
}
.openapi-path .openapi-method {
@apply text-[0.813rem] m-0 h-full items-center flex px-2;
@apply text-[0.813rem] m-0 mt-0.5 items-center flex px-2;
}
.openapi-path-title {
@apply flex-1 relative font-normal whitespace-nowrap overflow-x-auto font-mono text-tint-strong/10;
@apply flex-1 relative font-normal text-left font-mono text-tint-strong/10;
@apply py-0.5 px-1 rounded hover:bg-tint cursor-pointer transition-colors;
scrollbar-width: none;
-ms-overflow-style: none;
@apply whitespace-nowrap md:whitespace-normal;
}
.openapi-path-title[data-deprecated="true"] {
@@ -42,11 +42,11 @@ export function CurrentContentIcon(
}
: {
light: {
src: linker.toPathInContent('~gitbook/icon?size=medium&theme=light'),
src: linker.toPathInSpace('~gitbook/icon?size=medium&theme=light'),
size: { width: 256, height: 256 },
},
dark: {
src: linker.toPathInContent('~gitbook/icon?size=medium&theme=dark'),
src: linker.toPathInSpace('~gitbook/icon?size=medium&theme=dark'),
size: { width: 256, height: 256 },
},
}
@@ -123,7 +123,6 @@ export function DropdownMenuItem(
return (
<Link
href={href}
prefetch={false}
insights={insights}
className={tcls(
'rounded straight-corners:rounded-sm px-3 py-1 text-sm',
@@ -102,12 +102,14 @@ export function Header(props: { context: GitBookSiteContext; withTopHeader?: boo
? [
'md:grow-[0.8]',
'lg:basis-40',
'max-w-lg',
'md:max-w-[40%]',
'lg:max-w-lg',
'lg:ml-[max(calc((100%-18rem-48rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - margin (3rem)
'xl:ml-[max(calc((100%-18rem-48rem-14rem-3rem)/2),1.5rem)]', // container (100%) - sidebar (18rem) - content (48rem) - outline (14rem) - margin (3rem)
'page-no-toc:lg:ml-[max(calc((100%-18rem-48rem-18rem-3rem)/2),0rem)]',
'page-full-width:lg:ml-6',
'lg:mr-auto',
'page-full-width:lg:ml-[max(calc((100%-18rem-103rem-3rem)/2),1.5rem)]',
'page-full-width:2xl:ml-[max(calc((100%-18rem-96rem-14rem+3rem)/2),1.5rem)]',
'md:mr-auto',
'order-last',
'md:order-[unset]',
]
@@ -179,44 +181,46 @@ export function Header(props: { context: GitBookSiteContext; withTopHeader?: boo
</div>
{sections || siteSpaces.length > 1 ? (
<div
className={tcls(
'w-full',
'overflow-x-scroll',
'overflow-y-hidden',
'hide-scroll',
'-mb-4 pb-4', // Positive padding / negative margin allows the navigation menu indicator to show in a scroll viewƒ
!sections ? ['hidden', 'page-no-toc:flex'] : 'flex'
)}
>
<div className="scroll-nojump">
<div
className={tcls(
CONTAINER_STYLE,
'page-default-width:max-w-[unset]',
'grow',
'flex',
'items-end',
'page-default-width:2xl:px-[calc((100%-1536px+4rem)/2)]'
'w-full',
'overflow-x-scroll',
'overflow-y-hidden',
'hide-scroll',
'-mb-4 pb-4', // Positive padding / negative margin allows the navigation menu indicator to show in a scroll viewƒ
!sections ? ['hidden', 'page-no-toc:flex'] : 'flex'
)}
>
{siteSpaces.length > 1 && (
<div
id="variants"
className="my-2 mr-5 page-no-toc:flex hidden grow border-tint border-r pr-5 *:grow only:mr-0 only:border-none only:pr-0 sm:max-w-64"
>
<SpacesDropdown
context={context}
siteSpace={siteSpace}
siteSpaces={siteSpaces}
className="w-full grow py-1"
<div
className={tcls(
CONTAINER_STYLE,
'page-default-width:max-w-[unset]',
'grow',
'flex',
'items-end',
'page-default-width:2xl:px-[calc((100%-1536px+4rem)/2)]'
)}
>
{siteSpaces.length > 1 && (
<div
id="variants"
className="my-2 mr-5 page-no-toc:flex hidden grow border-tint border-r pr-5 *:grow only:mr-0 only:border-none only:pr-0 sm:max-w-64"
>
<SpacesDropdown
context={context}
siteSpace={siteSpace}
siteSpaces={siteSpaces}
className="w-full grow py-1"
/>
</div>
)}
{sections && (
<SiteSectionTabs
sections={encodeClientSiteSections(context, sections)}
/>
</div>
)}
{sections && (
<SiteSectionTabs
sections={encodeClientSiteSections(context, sections)}
/>
)}
)}
</div>
</div>
</div>
) : null}
@@ -20,7 +20,7 @@ export async function HeaderLogo(props: HeaderLogoProps) {
return (
<Link
href={linker.toAbsoluteURL(linker.toPathInContent(''))}
href={linker.toAbsoluteURL(linker.toPathInSpace(''))}
className={tcls('group/headerlogo', 'min-w-0', 'shrink', 'flex', 'items-center')}
>
{customization.header.logo ? (
@@ -47,17 +47,11 @@ export function HeaderMobileMenu(props: Partial<React.ButtonHTMLAttributes<HTMLB
aria-label={tString(language, 'table_of_contents_button_label')}
onClick={toggleNavigation}
className={tcls(
'flex',
'flex-row',
'items-center',
'py-1',
'px-2',
'rounded',
'straight-corners:rounded-sm',
'flex flex-row items-center rounded straight-corners:rounded-sm px-2 py-1',
props.className
)}
>
<Icon icon="bars" className={tcls('size-4', 'text-inherit')} />
<Icon icon="bars" className="size-4 text-inherit" />
</button>
);
}
@@ -5,8 +5,8 @@ import { OpenAPIOperationContextProvider } from '@gitbook/react-openapi';
import * as React from 'react';
import { useDebounceCallback, useEventCallback } from 'usehooks-ts';
import * as cookies from '@/lib/cookies';
import type { VisitorAuthClaims } from '@/lib/adaptive';
import { getAllBrowserCookiesMap } from '@/lib/browser-cookies';
import { getSession } from './sessions';
import { getVisitorId } from './visitorId';
@@ -23,6 +23,7 @@ type InsightsEventContext = {
siteShareKey: string | null;
spaceId: string;
revisionId: string;
visitorAuthClaims: VisitorAuthClaims;
};
/**
@@ -65,7 +66,6 @@ interface InsightsProviderProps extends InsightsEventContext {
enabled: boolean;
appURL: string;
apiHost: string;
visitorAuthToken: string | null;
children: React.ReactNode;
}
@@ -73,7 +73,7 @@ interface InsightsProviderProps extends InsightsEventContext {
* Wrap the content of the app with the InsightsProvider to track events.
*/
export function InsightsProvider(props: InsightsProviderProps) {
const { enabled, appURL, apiHost, visitorAuthToken, children, ...context } = props;
const { enabled, appURL, apiHost, children, ...context } = props;
const visitorIdRef = React.useRef<string | null>(null);
const eventsRef = React.useRef<{
@@ -116,7 +116,6 @@ export function InsightsProvider(props: InsightsProviderProps) {
pageContext: eventsForPathname.pageContext,
visitorId,
sessionId: session.id,
visitorAuthToken,
})
);
@@ -253,16 +252,15 @@ function transformEvents(input: {
pageContext: InsightsEventPageContext;
visitorId: string;
sessionId: string;
visitorAuthToken: string | null;
}): api.SiteInsightsEvent[] {
const session: api.SiteInsightsEventSession = {
sessionId: input.sessionId,
visitorId: input.visitorId,
userAgent: window.navigator.userAgent,
language: window.navigator.language,
cookies: cookies.getAll(),
cookies: getAllBrowserCookiesMap(),
referrer: document.referrer || null,
visitorAuthToken: input.visitorAuthToken ?? null,
visitorAuthClaims: input.context.visitorAuthClaims,
};
const location: api.SiteInsightsEventLocation = {
@@ -1,6 +1,6 @@
'use client';
import * as cookies from '@/lib/cookies';
import { getBrowserCookie, setBrowserCookie } from '@/lib/browser-cookies';
const GRANTED_COOKIE = '__gitbook_cookie_granted';
@@ -8,7 +8,7 @@ const GRANTED_COOKIE = '__gitbook_cookie_granted';
* Accept or reject cookies.
*/
export function setCookiesTracking(enabled: boolean) {
cookies.set(GRANTED_COOKIE, enabled ? 'yes' : 'no', {
setBrowserCookie(GRANTED_COOKIE, enabled ? 'yes' : 'no', {
expires: 365,
sameSite: 'none',
secure: true,
@@ -20,7 +20,7 @@ export function setCookiesTracking(enabled: boolean) {
* Return `undefined` if state is not known.
*/
export function isCookiesTrackingDisabled() {
const state = cookies.get(GRANTED_COOKIE);
const state = getBrowserCookie(GRANTED_COOKIE);
if (state === 'yes') {
return false;
@@ -1,6 +1,6 @@
'use client';
import * as cookies from '@/lib/cookies';
import { getBrowserCookie } from '@/lib/browser-cookies';
import { isCookiesTrackingDisabled } from './cookies';
import { generateRandomId } from './utils';
@@ -37,7 +37,7 @@ async function fetchVisitorID(appURL: string): Promise<string> {
return generateRandomId();
}
const existingTrackingCookie = cookies.get(VISITORID_COOKIE);
const existingTrackingCookie = getBrowserCookie(VISITORID_COOKIE);
if (existingTrackingCookie) {
// If the cookie already exists, we'll just use that. Avoids a server request.
@@ -11,7 +11,7 @@ import {
import { Icon } from '@gitbook/icons';
import type { GitBookSiteContext, GitBookSpaceContext } from '@v2/lib/context';
import { getPageDocument } from '@v2/lib/data';
import type { GitBookSpaceLinker } from '@v2/lib/links';
import type { GitBookLinker } from '@v2/lib/links';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import * as React from 'react';
@@ -67,7 +67,7 @@ export async function PDFPage(props: {
);
// Build a linker that create anchor links for the pages rendered in the PDF page.
const linker: GitBookSpaceLinker = {
const linker: GitBookLinker = {
...baseContext.linker,
toPathForPage(input) {
if (pages.some((p) => p.page.id === input.page.id)) {
@@ -93,7 +93,7 @@ export async function PDFPage(props: {
<div className={tcls('fixed', 'left-12', 'top-12', 'print:hidden', 'z-50')}>
<a
title={tString(language, 'pdf_goback')}
href={pdfParams.back ?? linker.toAbsoluteURL(linker.toPathInContent(''))}
href={pdfParams.back ?? linker.toAbsoluteURL(linker.toPathInSpace(''))}
className={tcls(
'flex',
'flex-row',
@@ -35,7 +35,7 @@ export function PageAside(props: {
const { customization, site, space } = context;
const language = getSpaceLanguage(customization);
const pdfHref = context.linker.toPathInContent(
const pdfHref = context.linker.toPathInSpace(
`~gitbook/pdf?${getPDFURLSearchParams({
page: page.id,
only: true,
@@ -70,9 +70,6 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
href={`#${section.id}`}
className={tcls(
'relative',
'flex',
'flex-row',
'items-baseline',
'z-10',
'text-sm',
@@ -126,7 +123,7 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
>
{section.tag ? (
<span
className={`openapi-method openapi-method-${section.tag.toLowerCase()}`}
className={`-mt-0.5 openapi-method openapi-method-${section.tag.toLowerCase()}`}
>
{section.tag}
</span>
@@ -35,25 +35,19 @@ export function PageBody(props: {
return (
<>
<main
className={
tcls(
'flex-1',
'relative',
'py-8',
'lg:px-12',
// Allow words to break if they are too long.
'break-anywhere',
// When in api page mode without the aside, we align with the border of the main content
'page-api-block:xl:max-2xl:pr-0',
// Max size to ensure one column in api is aligned with rest of content (2 x 3xl) + (gap-3 + 2) * px-12
'page-api-block:max-w-[1654px]',
'page-api-block:mx-auto'
// page.layout.tableOfContents ? null : 'xl:ml-56'
) +
(asFullWidth ? ' page-full-width' : ' page-default-width') +
(!page.layout.tableOfContents ? ' page-no-toc' : ' page-has-toc')
}
className={tcls(
'relative min-w-0 flex-1',
'py-8 lg:px-12',
// Allow words to break if they are too long.
'break-anywhere',
// When in api page mode without the aside, we align with the border of the main content
'page-api-block:xl:max-2xl:pr-0',
// Max size to ensure one column in api is aligned with rest of content (2 x 3xl) + (gap-3 + 2) * px-12
'page-api-block:mx-auto page-api-block:max-w-screen-2xl',
// page.layout.tableOfContents ? null : 'xl:ml-56',
asFullWidth ? 'page-full-width' : 'page-default-width',
page.layout.tableOfContents ? 'page-has-toc' : 'page-no-toc'
)}
>
<PreservePageLayout asFullWidth={asFullWidth} />
{page.cover && page.layout.cover && page.layout.coverSize === 'hero' ? (
@@ -66,14 +60,14 @@ export function PageBody(props: {
fallback={
<DocumentViewSkeleton
document={document}
blockStyle={['page-api-block:ml-0']}
blockStyle="page-api-block:ml-0"
/>
}
>
<DocumentView
document={document}
style={['[&>*+*]:mt-5', 'grid']}
blockStyle={['page-api-block:ml-0']}
style="grid [&>*+*]:mt-5"
blockStyle="page-api-block:ml-0"
context={{
mode: 'default',
contentContext: context,
@@ -88,23 +82,9 @@ export function PageBody(props: {
<PageFooterNavigation context={context} page={page} />
) : null}
<div
className={tcls(
'flex',
'flex-row',
'flex-wrap',
'gap-4',
'items-center',
'mt-6',
'max-w-3xl',
'mx-auto',
'page-api-block:ml-0',
'text-tint',
'contrast-more:text-tint-strong'
)}
>
<div className="mx-auto mt-6 page-api-block:ml-0 flex max-w-3xl flex-row flex-wrap items-center gap-4 text-tint contrast-more:text-tint-strong">
{updatedAt ? (
<p className={tcls('mr-auto text-sm')}>
<p className="mr-auto text-sm">
{t(language, 'page_last_modified', <DateRelative value={updatedAt} />)}
</p>
) : null}
@@ -21,8 +21,10 @@ import {
hexToRgb,
} from '@gitbook/colors';
import { IconStyle, IconsProvider } from '@gitbook/icons';
import * as ReactDOM from 'react-dom';
import { fontNotoColorEmoji, fonts, ibmPlexMono } from '@/fonts';
import { getFontData } from '@/fonts';
import { fontNotoColorEmoji, ibmPlexMono } from '@/fonts/default';
import { getSpaceLanguage } from '@/intl/server';
import { getAssetURL } from '@/lib/assets';
import { tcls } from '@/lib/tailwind';
@@ -31,7 +33,7 @@ import { ClientContexts } from './ClientContexts';
import '@gitbook/icons/style.css';
import './globals.css';
import { GITBOOK_ICONS_TOKEN, GITBOOK_ICONS_URL } from '@v2/lib/env';
import { GITBOOK_FONTS_URL, GITBOOK_ICONS_TOKEN, GITBOOK_ICONS_URL } from '@v2/lib/env';
/**
* Layout shared between the content and the PDF renderer.
@@ -48,6 +50,22 @@ export async function CustomizationRootLayout(props: {
const mixColor = getTintMixColor(customization.styling.primaryColor, tintColor);
const sidebarStyles = getSidebarStyles(customization);
const { infoColor, successColor, warningColor, dangerColor } = getSemanticColors(customization);
const fontData = getFontData(customization.styling.font);
// Preconnect and preload custom fonts if needed
if (fontData.type === 'custom') {
ReactDOM.preconnect(GITBOOK_FONTS_URL);
fontData.preloadSources
.flatMap((face) => face.sources)
.forEach(({ url, format }) => {
ReactDOM.preload(url, {
as: 'font',
crossOrigin: 'anonymous',
fetchPriority: 'high',
type: format ? `font/${format}` : undefined,
});
});
}
return (
<html
@@ -66,14 +84,18 @@ export async function CustomizationRootLayout(props: {
sidebarStyles.list && `sidebar-list-${sidebarStyles.list}`,
'links' in customization.styling && `links-${customization.styling.links}`,
fontNotoColorEmoji.variable,
fonts[customization.styling.font].variable,
ibmPlexMono.variable
ibmPlexMono.variable,
fontData.type === 'default' ? fontData.variable : 'font-custom'
)}
>
<head>
{customization.privacyPolicy.url ? (
<link rel="privacy-policy" href={customization.privacyPolicy.url} />
) : null}
{/* Inject custom font @font-face rules */}
{fontData.type === 'custom' ? <style>{fontData.fontFaceRules}</style> : null}
<style
nonce={
//Since I can't get the nonce to work for inline styles, we need to allow unsafe-inline
@@ -143,6 +143,10 @@
margin-right: 0;
width: calc(100% - var(--scrollbar-width));
}
body:has(.page-full-width) .scroll-nojump {
margin-left: 0;
width: 100%;
}
}
}
@@ -236,7 +236,6 @@ function AnswerSources(props: {
'focus-within:text-primary'
)}
href={source.href}
prefetch={false}
>
<Icon
icon="arrow-up-right"
@@ -99,7 +99,7 @@ export function SearchButton(props: { children?: React.ReactNode; style?: ClassV
);
}
const Shortcut = () => {
function Shortcut() {
const [operatingSystem, setOperatingSystem] = useState<string | null>(null);
useEffect(() => {
@@ -115,21 +115,14 @@ const Shortcut = () => {
setOperatingSystem(getOperatingSystem());
}, []);
return operatingSystem ? (
return (
<div
aria-busy={operatingSystem === null ? 'true' : undefined}
className={tcls(
'shortcut',
'hidden',
'md:flex',
'gap-0.5',
'-mr-1',
'justify-end',
'text-xs',
'text-tint',
'contrast-more:text-tint-strong',
'whitespace-nowrap',
`[font-feature-settings:"calt",_"case"]`,
'animate-fadeIn'
`shortcut -mr-1 hidden justify-end gap-0.5 whitespace-nowrap text-tint text-xs [font-feature-settings:"calt",_"case"] contrast-more:text-tint-strong md:flex`,
operatingSystem
? 'motion-safe:animate-fadeIn motion-reduce:opacity-100'
: 'opacity-0'
)}
>
<kbd
@@ -141,5 +134,5 @@ const Shortcut = () => {
K
</kbd>
</div>
) : null;
};
);
}
@@ -12,6 +12,7 @@ import { SpaceLayout } from '@/components/SpaceLayout';
import { buildVersion } from '@/lib/build';
import { isSiteIndexable } from '@/lib/seo';
import type { VisitorAuthClaims } from '@/lib/adaptive';
import { GITBOOK_API_PUBLIC_URL, GITBOOK_ASSETS_URL, GITBOOK_ICONS_URL } from '@v2/lib/env';
import { getResizedImageURL } from '@v2/lib/images';
import { ClientContexts } from './ClientContexts';
@@ -25,10 +26,10 @@ export async function SiteLayout(props: {
context: GitBookSiteContext;
forcedTheme?: CustomizationThemeMode | null;
withTracking: boolean;
visitorAuthToken: string | null;
visitorAuthClaims: VisitorAuthClaims;
children: React.ReactNode;
}) {
const { context, nonce, forcedTheme, withTracking, visitorAuthToken, children } = props;
const { context, nonce, forcedTheme, withTracking, visitorAuthClaims, children } = props;
const { scripts, customization } = context;
@@ -57,7 +58,7 @@ export async function SiteLayout(props: {
<SpaceLayout
context={context}
withTracking={withTracking}
visitorAuthToken={visitorAuthToken}
visitorAuthClaims={visitorAuthClaims}
>
{children}
</SpaceLayout>
@@ -109,7 +110,7 @@ export async function generateSiteLayoutMetadata(context: GitBookSiteContext): P
height: faviconSize,
})
: linker.toAbsoluteURL(
linker.toPathInContent('~gitbook/icon?size=small&theme=light')
linker.toPathInSpace('~gitbook/icon?size=small&theme=light')
),
type: 'image/png',
media: '(prefers-color-scheme: light)',
@@ -120,9 +121,7 @@ export async function generateSiteLayoutMetadata(context: GitBookSiteContext): P
width: faviconSize,
height: faviconSize,
})
: linker.toAbsoluteURL(
linker.toPathInContent('~gitbook/icon?size=small&theme=dark')
),
: linker.toAbsoluteURL(linker.toPathInSpace('~gitbook/icon?size=small&theme=dark')),
type: 'image/png',
media: '(prefers-color-scheme: dark)',
},
@@ -12,7 +12,7 @@ import { isPageIndexable, isSiteIndexable } from '@/lib/seo';
import { getResizedImageURL } from '@v2/lib/images';
import { PageClientLayout } from './PageClientLayout';
import { type PagePathParams, fetchPageData, getPathnameParam, normalizePathname } from './fetch';
import { type PagePathParams, fetchPageData, getPathnameParam } from './fetch';
export const runtime = 'edge';
export const dynamic = 'force-dynamic';
@@ -33,11 +33,11 @@ export async function SitePage(props: SitePageProps) {
const rawPathname = getPathnameParam(props.pageParams);
if (!pageTarget) {
const pathname = normalizePathname(rawPathname);
const pathname = rawPathname.toLowerCase();
if (pathname !== rawPathname) {
// If the pathname was not normalized, redirect to the normalized version
// before trying to resolve the page again
redirect(context.linker.toPathInContent(pathname));
redirect(context.linker.toPathInSpace(pathname));
} else {
notFound();
}
@@ -137,7 +137,7 @@ export async function generateSitePageMetadata(props: SitePageProps): Promise<Me
width: 1200,
height: 630,
})
: linker.toAbsoluteURL(linker.toPathInContent(`~gitbook/ogimage/${page.id}`)),
: linker.toAbsoluteURL(linker.toPathInSpace(`~gitbook/ogimage/${page.id}`)),
],
},
robots:
@@ -2,6 +2,7 @@ import type { GitBookSiteContext } from '@v2/lib/context';
import { redirect } from 'next/navigation';
import { resolvePageId, resolvePagePath } from '@/lib/pages';
import { withLeadingSlash } from '@/lib/paths';
import { getDataOrNull } from '@v2/lib/data';
export interface PagePathParams {
@@ -35,14 +36,14 @@ export async function fetchPageData(context: GitBookSiteContext, params: PagePar
* If the path can't be found, we try to resolve it from the API to handle redirects.
*/
async function resolvePage(context: GitBookSiteContext, params: PagePathParams | PageIdParams) {
const { organizationId, site, space, revisionId, pages, shareKey } = context;
const { organizationId, site, space, revisionId, pages, shareKey, linker } = context;
if ('pageId' in params) {
return resolvePageId(pages, params.pageId);
}
const rawPathname = getPathnameParam(params);
const pathname = normalizePathname(rawPathname);
const pathname = rawPathname.toLowerCase();
// When resolving a page, we use the lowercased pathname
const page = resolvePagePath(pages, pathname);
@@ -67,16 +68,31 @@ async function resolvePage(context: GitBookSiteContext, params: PagePathParams |
}
// If a page still can't be found, we try with the API, in case we have a redirect at site level.
const resolvedSiteRedirect = await getDataOrNull(
context.dataFetcher.getSiteRedirectBySource({
organizationId,
siteId: site.id,
source: rawPathname.startsWith('/') ? rawPathname : `/${rawPathname}`,
siteShareKey: shareKey,
})
);
if (resolvedSiteRedirect) {
return redirect(resolvedSiteRedirect.target);
const redirectPathname = withLeadingSlash(rawPathname);
if (/^\/[a-zA-Z0-9-_.\/]+[a-zA-Z0-9-_.]$/.test(redirectPathname)) {
const redirectSources = new Set<string>([
// Test the pathname relative to the root
// For example hello/world -> section/variant/hello/world
withLeadingSlash(
linker.toRelativePathInSite(linker.toPathInSpace(redirectPathname))
),
// Test the pathname relative to the content/space
// For example hello/world -> /hello/world
redirectPathname,
]);
for (const source of redirectSources) {
const resolvedSiteRedirect = await getDataOrNull(
context.dataFetcher.getSiteRedirectBySource({
organizationId,
siteId: site.id,
source,
siteShareKey: shareKey,
})
);
if (resolvedSiteRedirect) {
return redirect(linker.toLinkForContent(resolvedSiteRedirect.target));
}
}
}
}
@@ -99,10 +115,3 @@ export function getPathnameParam(params: PagePathParams): string {
return pathname.map((part) => decodeURIComponent(part)).join('/');
}
/**
* Normalize the URL pathname into the format used in the revision page path.
*/
export function normalizePathname(pathname: string) {
return pathname.toLowerCase();
}
@@ -1,19 +1,20 @@
import { Icon, type IconName } from '@gitbook/icons';
import { tcls } from '@/lib/tailwind';
import { type ClassValue, tcls } from '@/lib/tailwind';
/**
* Icon shown beside a section in the site section tabs.
*/
export function SectionIcon(props: { icon: IconName; isActive: boolean }) {
const { icon, isActive } = props;
export function SectionIcon(props: { icon: IconName; isActive: boolean; className?: ClassValue }) {
const { icon, isActive, className } = props;
return (
<Icon
icon={icon}
className={tcls(
'size-[1em] shrink-0 text-inherit opacity-8',
isActive && 'text-inherit opacity-10'
isActive && 'text-inherit opacity-10',
className
)}
/>
);
@@ -112,10 +112,10 @@ export function SiteSectionTabs(props: { sections: ClientSiteSections }) {
);
})}
<NavigationMenu.Indicator
className="top-full z-0 flex h-3 items-end justify-center duration-150 motion-safe:transition-[width,_transform] data-[state=hidden]:motion-safe:animate-fadeOut data-[state=visible]:motion-safe:animate-fadeIn"
className="fixed top-full z-50 flex h-3 items-end justify-center duration-150 motion-safe:transition-[width,_transform] data-[state=hidden]:motion-safe:animate-fadeOut data-[state=visible]:motion-safe:animate-fadeIn"
aria-hidden
>
<div className="relative top-[70%] size-3 rotate-[225deg] rounded-tl-sm bg-tint shadow-1xs shadow-dark/1 dark:shadow-dark/4" />
<div className="relative top-1/2 size-3 rotate-45 rounded-tl-sm border-tint-subtle border-t border-l bg-tint-base" />
</NavigationMenu.Indicator>
</NavigationMenu.List>
</div>
@@ -127,7 +127,7 @@ export function SiteSectionTabs(props: { sections: ClientSiteSections }) {
}}
>
<NavigationMenu.Viewport
className="relative mt-3 h-[var(--radix-navigation-menu-viewport-height)] w-[calc(100vw_-_2rem)] origin-[top_center] overflow-hidden rounded straight-corners:rounded-none bg-tint shadow-1xs shadow-dark/1 duration-250 data-[state=closed]:duration-150 motion-safe:transition-[width,_height,_transform] data-[state=closed]:motion-safe:animate-scaleOut data-[state=open]:motion-safe:animate-scaleIn md:mx-0 md:w-[var(--radix-navigation-menu-viewport-width)] dark:shadow-dark/4"
className="relative mt-3 h-[var(--radix-navigation-menu-viewport-height)] w-[calc(100vw_-_2rem)] origin-[top_center] overflow-hidden rounded-lg straight-corners:rounded-sm bg-tint-base shadow-lg shadow-tint-10/6 ring-1 ring-tint-subtle duration-250 data-[state=closed]:duration-150 motion-safe:transition-[width,_height,_transform] data-[state=closed]:motion-safe:animate-scaleOut data-[state=open]:motion-safe:animate-scaleIn md:mx-0 md:w-[var(--radix-navigation-menu-viewport-width)] dark:shadow-tint-1/6"
style={{
translate:
undefined /* don't move this to a Tailwind class as Radix renders viewport incorrectly for a few frames */,
@@ -220,7 +220,7 @@ function SectionGroupTileList(props: {
return (
<ul
className={tcls(
'grid w-full p-2 sm:grid-cols-1 md:w-max',
'grid w-full gap-1 p-2 sm:grid-cols-1 md:w-max',
sections.length < MIN_ITEMS_FOR_COLS ? 'md:grid-cols-1' : 'md:grid-cols-2'
)}
>
@@ -246,17 +246,23 @@ function SectionGroupTile(props: { section: ClientSiteSection; isActive: boolean
<Link
href={url}
className={tcls(
'flex min-h-12 w-full select-none flex-col gap-2 rounded p-3 transition-colors hover:bg-tint-hover',
isActive
? 'text-primary hover:text-primary-strong focus:text-primary-strong'
: 'text-tint hover:text-tint-strong focus:text-tint-strong'
'flex w-full select-none flex-col gap-1 rounded straight-corners:rounded-none px-3 py-2 transition-colors hover:bg-tint-hover',
isActive ? 'text-primary' : 'text-tint-strong'
)}
>
<div className="flex w-full items-center gap-2 font-medium light:text-dark dark:text-light">
{icon ? <SectionIcon isActive={false} icon={icon as IconName} /> : null}
<span className="min-w-0 truncate">{title}</span>
<div className="flex w-full gap-2">
{icon ? (
<SectionIcon
className="mt-[3px]"
isActive={false}
icon={icon as IconName}
/>
) : null}
{title}
</div>
<p className="text-tint-subtle">{section.description}</p>
{section.description ? (
<p className="text-tint-subtle">{section.description}</p>
) : null}
</Link>
</li>
);
@@ -11,6 +11,7 @@ import { getSpaceLanguage } from '@/intl/server';
import { t } from '@/intl/translate';
import { tcls } from '@/lib/tailwind';
import type { VisitorAuthClaims } from '@/lib/adaptive';
import { GITBOOK_API_PUBLIC_URL, GITBOOK_APP_URL } from '@v2/lib/env';
import { SpacesDropdown } from '../Header/SpacesDropdown';
import { InsightsProvider } from '../Insights';
@@ -26,13 +27,13 @@ export function SpaceLayout(props: {
/** Whether to enable tracking of events into site insights. */
withTracking: boolean;
/** The visitor token used to access this content */
visitorAuthToken: string | null;
/** The visitor auth claims. */
visitorAuthClaims: VisitorAuthClaims;
/** The children of the layout. */
children: React.ReactNode;
}) {
const { context, withTracking, visitorAuthToken, children } = props;
const { context, withTracking, visitorAuthClaims, children } = props;
const { siteSpace, customization, sections, siteSpaces } = context;
const withTopHeader = customization.header.preset !== CustomizationHeaderPreset.None;
@@ -47,12 +48,11 @@ export function SpaceLayout(props: {
customization.footer.groups?.length;
return (
<SpaceLayoutContextProvider basePath={context.linker.toPathInContent('')}>
<SpaceLayoutContextProvider basePath={context.linker.toPathInSpace('')}>
<InsightsProvider
enabled={withTracking}
appURL={GITBOOK_APP_URL}
apiHost={GITBOOK_API_PUBLIC_URL}
visitorAuthToken={visitorAuthToken}
organizationId={context.organizationId}
siteId={context.site.id}
siteSectionId={context.sections?.current?.id ?? null}
@@ -60,81 +60,84 @@ export function SpaceLayout(props: {
siteShareKey={context.shareKey ?? null}
revisionId={context.revisionId}
spaceId={context.space.id}
visitorAuthClaims={visitorAuthClaims}
>
<Header withTopHeader={withTopHeader} context={context} />
<div
className={tcls(
'flex',
'flex-col',
'lg:flex-row',
CONTAINER_STYLE,
<div className="scroll-nojump">
<div
className={tcls(
'flex',
'flex-col',
'lg:flex-row',
CONTAINER_STYLE,
// Ensure the footer is display below the viewport even if the content is not enough
withFooter && 'min-h-[calc(100vh-64px)]',
withTopHeader ? null : 'lg:min-h-screen'
)}
>
<TableOfContents
context={context}
header={
withTopHeader ? null : (
<div
className={tcls(
'hidden',
'pr-4',
'lg:flex',
'grow-0',
'flex-wrap',
'dark:shadow-light/1',
'text-base/tight'
)}
>
<HeaderLogo context={context} />
</div>
)
}
innerHeader={
// displays the search button and/or the space dropdown in the ToC according to the header/variant settings. E.g if there is no header, the search button will be displayed in the ToC.
<>
{!withTopHeader && (
<div className={tcls('hidden', 'lg:block')}>
<React.Suspense fallback={null}>
<SearchButton>
<span className={tcls('flex-1')}>
{t(
getSpaceLanguage(customization),
customization.aiSearch.enabled
? 'search_or_ask'
: 'search'
)}
...
</span>
</SearchButton>
</React.Suspense>
</div>
)}
{!withTopHeader && withSections && sections && (
<SiteSectionList
className={tcls('hidden', 'lg:block')}
sections={encodeClientSiteSections(context, sections)}
/>
)}
{isMultiVariants && (
<SpacesDropdown
context={context}
siteSpace={siteSpace}
siteSpaces={siteSpaces}
// Ensure the footer is display below the viewport even if the content is not enough
withFooter && 'min-h-[calc(100vh-64px)]',
withTopHeader ? null : 'lg:min-h-screen'
)}
>
<TableOfContents
context={context}
header={
withTopHeader ? null : (
<div
className={tcls(
'w-full',
'page-no-toc:hidden',
'site-header-none:page-no-toc:flex'
'hidden',
'pr-4',
'lg:flex',
'grow-0',
'flex-wrap',
'dark:shadow-light/1',
'text-base/tight'
)}
/>
)}
</>
}
/>
<div className={tcls('flex-1', 'flex', 'flex-col')}>{children}</div>
>
<HeaderLogo context={context} />
</div>
)
}
innerHeader={
// displays the search button and/or the space dropdown in the ToC according to the header/variant settings. E.g if there is no header, the search button will be displayed in the ToC.
<>
{!withTopHeader && (
<div className={tcls('hidden', 'lg:block')}>
<React.Suspense fallback={null}>
<SearchButton>
<span className={tcls('flex-1')}>
{t(
getSpaceLanguage(customization),
customization.aiSearch.enabled
? 'search_or_ask'
: 'search'
)}
...
</span>
</SearchButton>
</React.Suspense>
</div>
)}
{!withTopHeader && withSections && sections && (
<SiteSectionList
className={tcls('hidden', 'lg:block')}
sections={encodeClientSiteSections(context, sections)}
/>
)}
{isMultiVariants && (
<SpacesDropdown
context={context}
siteSpace={siteSpace}
siteSpaces={siteSpaces}
className={tcls(
'w-full',
'page-no-toc:hidden',
'site-header-none:page-no-toc:flex'
)}
/>
)}
</>
}
/>
<div className="flex min-w-0 flex-1 flex-col">{children}</div>
</div>
</div>
{withFooter ? <Footer context={context} /> : null}
+280
View File
@@ -0,0 +1,280 @@
import { describe, expect, test } from 'bun:test';
import type { CustomizationFontDefinition } from '@gitbook/api';
import stylelint from 'stylelint';
import { generateFontFacesCSS, getFontSourcesToPreload } from './custom';
const TEST_FONTS: { [key in string]: CustomizationFontDefinition } = {
basic: {
id: 'open-sans',
fontFamily: 'Open Sans',
fontFaces: [
{
weight: 400,
sources: [
{
url: 'https://example.com/fonts/opensans-regular.woff2',
format: 'woff2',
},
],
},
{
weight: 700,
sources: [
{
url: 'https://example.com/fonts/opensans-bold.woff2',
format: 'woff2',
},
],
},
],
},
multiWeight: {
id: 'roboto',
fontFamily: 'Roboto',
fontFaces: [
{
weight: 300,
sources: [
{
url: 'https://example.com/fonts/roboto-light.woff2',
format: 'woff2',
},
],
},
{
weight: 400,
sources: [
{
url: 'https://example.com/fonts/roboto-regular.woff2',
format: 'woff2',
},
],
},
{
weight: 500,
sources: [
{
url: 'https://example.com/fonts/roboto-medium.woff2',
format: 'woff2',
},
],
},
{
weight: 700,
sources: [
{
url: 'https://example.com/fonts/roboto-bold.woff2',
format: 'woff2',
},
],
},
{
weight: 900,
sources: [
{
url: 'https://example.com/fonts/roboto-black.woff2',
format: 'woff2',
},
],
},
],
},
multiSource: {
id: 'lato',
fontFamily: 'Lato',
fontFaces: [
{
weight: 400,
sources: [
{
url: 'https://example.com/fonts/lato-regular.woff2',
format: 'woff2',
},
{ url: 'https://example.com/fonts/lato-regular.woff', format: 'woff' },
],
},
],
},
missingFormat: {
id: 'source-sans',
fontFamily: 'Source Sans Pro',
fontFaces: [
{
weight: 400,
sources: [
{ url: 'https://example.com/fonts/sourcesans-regular.woff2' },
{
url: 'https://example.com/fonts/sourcesans-regular.woff',
format: 'woff',
},
],
},
],
},
empty: {
id: 'empty-font',
fontFamily: 'Empty Font',
fontFaces: [],
},
specialChars: {
id: 'special-font',
fontFamily: 'Special Font & Co.',
fontFaces: [
{
weight: 400,
sources: [{ url: 'https://example.com/fonts/special.woff2', format: 'woff2' }],
},
],
},
complex: {
id: 'complex-font',
fontFamily: 'Complex Font',
fontFaces: [
{
weight: 400,
sources: [
{ url: 'https://example.com/fonts/regular.woff2' },
{ url: 'https://example.com/fonts/regular.woff' },
],
},
{
weight: 700,
sources: [
{ url: 'https://example.com/fonts/bold.woff2' },
{ url: 'https://example.com/fonts/bold.woff' },
],
},
],
},
variousURLs: {
id: 'various-urls',
fontFamily: 'Various URLs Font',
fontFaces: [
{
weight: 400,
sources: [
{ url: 'https://example.com/fonts.woff2' },
{ url: 'https://example.com/fonts.woff' },
{ url: 'https://example.com/fonts.woff2' },
],
},
],
},
};
// Helper function to validate CSS with stylelint
async function isCSSValid(css: string): Promise<boolean> {
try {
const stylelintResult = await stylelint.lint({
code: css,
config: {
rules: {
'at-rule-no-unknown': true,
'declaration-block-no-duplicate-properties': true,
'property-no-unknown': true,
'selector-pseudo-class-no-unknown': true,
'selector-pseudo-element-no-unknown': true,
'no-duplicate-selectors': true,
'no-empty-source': true,
'no-invalid-double-slash-comments': true,
},
},
});
return !stylelintResult.errored;
} catch (error) {
console.error('Error while linting CSS:', error);
return false;
}
}
describe('generateFontFacesCSS', () => {
test('basic case with regular and bold weights', async () => {
const css = generateFontFacesCSS(TEST_FONTS.basic);
const isValid = await isCSSValid(css);
expect(isValid).toBe(true);
expect(css).toContain('font-weight: 400');
expect(css).toContain('font-weight: 700');
expect(css).toContain(
"url(https://example.com/fonts/opensans-regular.woff2) format('woff2')"
);
expect(css).toContain("url(https://example.com/fonts/opensans-bold.woff2) format('woff2')");
expect(css).toContain('--font-custom: CustomFont');
});
test('multiple font weights', async () => {
const css = generateFontFacesCSS(TEST_FONTS.multiWeight);
const isValid = await isCSSValid(css);
expect(isValid).toBe(true);
[300, 400, 500, 700, 900].forEach((weight) => {
expect(css).toContain(`font-weight: ${weight}`);
});
});
test('multiple sources for a single weight', async () => {
const css = generateFontFacesCSS(TEST_FONTS.multiSource);
const isValid = await isCSSValid(css);
expect(isValid).toBe(true);
expect(css).toContain(
"url(https://example.com/fonts/lato-regular.woff2) format('woff2'), url(https://example.com/fonts/lato-regular.woff) format('woff')"
);
});
test('missing format property', async () => {
const css = generateFontFacesCSS(TEST_FONTS.missingFormat);
const isValid = await isCSSValid(css);
expect(isValid).toBe(true);
expect(css).toContain(
"url(https://example.com/fonts/sourcesans-regular.woff2), url(https://example.com/fonts/sourcesans-regular.woff) format('woff')"
);
});
test('empty font faces array', async () => {
const css = generateFontFacesCSS(TEST_FONTS.empty);
expect(css).toBe('');
});
test('font with special characters in name', async () => {
const css = generateFontFacesCSS(TEST_FONTS.specialChars);
// Validate CSS syntax
const isValid = await isCSSValid(css);
expect(isValid).toBe(true);
});
});
describe('getFontSourcesToPreload', () => {
const preloadTestCases = [
{ name: 'basic case', font: TEST_FONTS.basic, expectedCount: 2 },
{ name: 'multiple weights', font: TEST_FONTS.multiWeight, expectedCount: 2 },
{ name: 'multiple sources', font: TEST_FONTS.multiSource, expectedCount: 2 },
{ name: 'missing format', font: TEST_FONTS.missingFormat, expectedCount: 2 },
{ name: 'empty font faces', font: TEST_FONTS.empty, expectedCount: 0 },
{ name: 'special characters', font: TEST_FONTS.specialChars, expectedCount: 1 },
{ name: 'complex case', font: TEST_FONTS.complex, expectedCount: 4 },
];
preloadTestCases.forEach(({ name, font, expectedCount }) => {
test(`extracts ${expectedCount} URLs from ${name}`, () => {
const result = getFontSourcesToPreload(font).flatMap((face) => face.sources);
expect(result).toBeArray();
expect(result.length).toBe(expectedCount);
});
});
});
+53
View File
@@ -0,0 +1,53 @@
import type { CustomizationFontDefinition } from '@gitbook/api';
/**
* Define the custom font faces and set the --font-custom to the custom font name
*/
export function generateFontFacesCSS(customFont: CustomizationFontDefinition): string {
const { fontFaces } = customFont;
// Generate font face declarations for all weights
const fontFaceDeclarations = fontFaces
.map((face) => {
const srcAttr = face.sources
.map((source) => {
let srcDefinition = `url(${source.url})`;
if (source.format) {
srcDefinition += ` format('${source.format}')`;
}
return srcDefinition;
})
.join(', ');
// We could use the customFont.fontFamily name here, but to avoid extra normalization we're using 'CustomFont'
return `
@font-face {
font-family: CustomFont;
font-style: normal;
font-weight: ${face.weight};
font-display: swap;
src: ${srcAttr};
}
`;
})
.join('\n');
return fontFaceDeclarations
? `${fontFaceDeclarations}
:root {
--font-custom: CustomFont;
}`
: '';
}
/**
* Get a list of font sources to preload (only 400 and 700 weights)
*/
export function getFontSourcesToPreload(customFont: CustomizationFontDefinition) {
return customFont.fontFaces.filter(
(face): face is typeof face & { weight: 400 | 700 } =>
face.weight === 400 || face.weight === 700
);
}
+207
View File
@@ -0,0 +1,207 @@
import { CustomizationDefaultFont } from '@gitbook/api';
import {
Fira_Sans_Extra_Condensed,
IBM_Plex_Mono,
IBM_Plex_Serif,
Inter,
Lato,
Merriweather,
Noto_Color_Emoji,
Noto_Sans,
Open_Sans,
Overpass,
Poppins,
Raleway,
Roboto,
Roboto_Slab,
Source_Sans_3,
Ubuntu,
} from 'next/font/google';
import localFont from 'next/font/local';
export const fontNotoColorEmoji = Noto_Color_Emoji({
variable: '--font-noto-color-emoji',
weight: ['400'],
preload: false,
display: 'swap',
});
/*
Fonts are downloaded and loaded by next/font.
We can't use "preload: true" as otherwise Next will preload all the fonts on the page
while spaces only use one font at a time.
*/
const inter = Inter({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
export const ibmPlexMono = IBM_Plex_Mono({
weight: ['400', '500', '600', '700'],
variable: '--font-mono',
style: 'normal',
display: 'swap',
preload: false,
fallback: ['monospace'],
adjustFontFallback: false,
});
const firaSans = Fira_Sans_Extra_Condensed({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const ibmPlexSerif = IBM_Plex_Serif({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['serif'],
});
const lato = Lato({
weight: ['400', '700', '900'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const merriweather = Merriweather({
weight: ['400', '700', '900'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['serif'],
});
const notoSans = Noto_Sans({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const openSans = Open_Sans({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const overpass = Overpass({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const poppins = Poppins({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const raleway = Raleway({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const roboto = Roboto({
weight: ['400', '500', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const robotoSlab = Roboto_Slab({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const sourceSansPro = Source_Sans_3({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const ubuntu = Ubuntu({
weight: ['400', '500', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const abcFavorit = localFont({
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
src: [
{
path: './ABCFavorit/ABCFavorit-Variable.woff2',
weight: '400 700',
style: 'normal',
},
{
path: './ABCFavorit/ABCFavorit-BoldItalic.woff2',
weight: '700',
style: 'italic',
},
{
path: './ABCFavorit/ABCFavorit-MediumItalic.woff2',
weight: '500',
style: 'italic',
},
{
path: './ABCFavorit/ABCFavorit-RegularItalic.woff2',
weight: '400',
style: 'italic',
},
],
declarations: [{ prop: 'ascent-override', value: '100%' }],
});
/**
* Font definitions.
*/
export const fonts: { [fontName in CustomizationDefaultFont]: { variable: string } } = {
[CustomizationDefaultFont.Inter]: inter,
[CustomizationDefaultFont.FiraSans]: firaSans,
[CustomizationDefaultFont.IBMPlexSerif]: ibmPlexSerif,
[CustomizationDefaultFont.Lato]: lato,
[CustomizationDefaultFont.Merriweather]: merriweather,
[CustomizationDefaultFont.NotoSans]: notoSans,
[CustomizationDefaultFont.OpenSans]: openSans,
[CustomizationDefaultFont.Overpass]: overpass,
[CustomizationDefaultFont.Poppins]: poppins,
[CustomizationDefaultFont.Raleway]: raleway,
[CustomizationDefaultFont.Roboto]: roboto,
[CustomizationDefaultFont.RobotoSlab]: robotoSlab,
[CustomizationDefaultFont.SourceSansPro]: sourceSansPro,
[CustomizationDefaultFont.Ubuntu]: ubuntu,
[CustomizationDefaultFont.ABCFavorit]: abcFavorit,
};
+40 -204
View File
@@ -1,207 +1,43 @@
import { CustomizationDefaultFont } from '@gitbook/api';
import {
Fira_Sans_Extra_Condensed,
IBM_Plex_Mono,
IBM_Plex_Serif,
Inter,
Lato,
Merriweather,
Noto_Color_Emoji,
Noto_Sans,
Open_Sans,
Overpass,
Poppins,
Raleway,
Roboto,
Roboto_Slab,
Source_Sans_3,
Ubuntu,
} from 'next/font/google';
import localFont from 'next/font/local';
export const fontNotoColorEmoji = Noto_Color_Emoji({
variable: '--font-noto-color-emoji',
weight: ['400'],
preload: false,
display: 'swap',
});
/*
Fonts are downloaded and loaded by next/font.
We can't use "preload: true" as otherwise Next will preload all the fonts on the page
while spaces only use one font at a time.
*/
const inter = Inter({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
export const ibmPlexMono = IBM_Plex_Mono({
weight: ['400', '500', '600', '700'],
variable: '--font-mono',
style: 'normal',
display: 'swap',
preload: false,
fallback: ['monospace'],
adjustFontFallback: false,
});
const firaSans = Fira_Sans_Extra_Condensed({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const ibmPlexSerif = IBM_Plex_Serif({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['serif'],
});
const lato = Lato({
weight: ['400', '700', '900'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const merriweather = Merriweather({
weight: ['400', '700', '900'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['serif'],
});
const notoSans = Noto_Sans({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const openSans = Open_Sans({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const overpass = Overpass({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const poppins = Poppins({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const raleway = Raleway({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const roboto = Roboto({
weight: ['400', '500', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const robotoSlab = Roboto_Slab({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const sourceSansPro = Source_Sans_3({
weight: ['400', '500', '600', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const ubuntu = Ubuntu({
weight: ['400', '500', '700'],
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
});
const abcFavorit = localFont({
variable: '--font-content',
preload: false,
display: 'swap',
fallback: ['system-ui', 'arial'],
src: [
{
path: './ABCFavorit/ABCFavorit-Variable.woff2',
weight: '400 700',
style: 'normal',
},
{
path: './ABCFavorit/ABCFavorit-BoldItalic.woff2',
weight: '700',
style: 'italic',
},
{
path: './ABCFavorit/ABCFavorit-MediumItalic.woff2',
weight: '500',
style: 'italic',
},
{
path: './ABCFavorit/ABCFavorit-RegularItalic.woff2',
weight: '400',
style: 'italic',
},
],
declarations: [{ prop: 'ascent-override', value: '100%' }],
});
import type { CustomizationFont, CustomizationFontDefinition } from '@gitbook/api';
import { generateFontFacesCSS, getFontSourcesToPreload } from './custom';
import { fonts } from './default';
/**
* Font definitions.
* Represents font data for either a default font or a custom font
*/
export const fonts: { [fontName in CustomizationDefaultFont]: { variable: string } } = {
[CustomizationDefaultFont.Inter]: inter,
[CustomizationDefaultFont.FiraSans]: firaSans,
[CustomizationDefaultFont.IBMPlexSerif]: ibmPlexSerif,
[CustomizationDefaultFont.Lato]: lato,
[CustomizationDefaultFont.Merriweather]: merriweather,
[CustomizationDefaultFont.NotoSans]: notoSans,
[CustomizationDefaultFont.OpenSans]: openSans,
[CustomizationDefaultFont.Overpass]: overpass,
[CustomizationDefaultFont.Poppins]: poppins,
[CustomizationDefaultFont.Raleway]: raleway,
[CustomizationDefaultFont.Roboto]: roboto,
[CustomizationDefaultFont.RobotoSlab]: robotoSlab,
[CustomizationDefaultFont.SourceSansPro]: sourceSansPro,
[CustomizationDefaultFont.Ubuntu]: ubuntu,
[CustomizationDefaultFont.ABCFavorit]: abcFavorit,
};
type FontData = DefaultFontData | CustomFontData;
/**
* Font data for a default font, currently handle with next/font
*/
interface DefaultFontData {
type: 'default';
variable: string;
}
/**
* Font data for a custom font with @font-face rules
*/
interface CustomFontData {
type: 'custom';
fontFaceRules: string;
preloadSources: CustomizationFontDefinition['fontFaces'];
}
/**
* Get the appropriate font data for a given font configuration
*/
export function getFontData(font: CustomizationFont): FontData {
if (typeof font === 'string') {
return {
type: 'default',
variable: fonts[font].variable,
};
}
return {
type: 'custom',
fontFaceRules: generateFontFacesCSS(font),
preloadSources: getFontSourcesToPreload(font),
};
}
@@ -89,14 +89,14 @@ A serialnumbers value governs its order within the given token.id</p>
<td>lt(e)</td>
<td>Y</td>
<td>Single occurrence only. Requires the presence of an <strong>lte</strong> or <strong>eq</strong> <strong>token.id</strong> query</td>
<td>?token.id=lte:X&amp;serialnumber=lt:Y</td>
<td>?token.id=lte:X&#x26;serialnumber=lt:Y</td>
</tr>
<tr>
<td></td>
<td>gt(e)</td>
<td>Y</td>
<td>Single occurrence only. Requires the presence of an <strong>gte</strong> or <strong>eq</strong> <strong>token.id</strong> query</td>
<td>?token.id=gte:X&amp;serialnumber=gt:Y</td>
<td>?token.id=gte:X&#x26;serialnumber=gt:Y</td>
</tr>
<tr>
<td>spender.id</td>
@@ -131,3 +131,16 @@ A serialnumbers value governs its order within the given token.id</p>
<p>Note: When searching across a range for individual NFTs a <strong>serialnumber</strong> with an additional <strong>token.id</strong> query filter must be provided.
Both filters must be a single occurrence of <strong>gt(e)</strong> or <strong>lt(e)</strong> which provide a lower and or upper boundary for search.</p>"
`;
exports[`parseMarkdown should parse html 1`] = `
"<p>Returns trending events for the given time frame.</p>
<p>The events are sorted by <code>trending score</code>.</p>
<p>To calculate trending score for an event, each bet placed on this event
contributes a value determined by how recently the bet was placed.</p>
<p>Recent bets add more value than older ones.</p>
<p>The normalized value of its score is also assigned to each event as its confidence.</p>
<p>In this example we get all trending events where <code>league</code> is <code>UEFA Champions League</code>.</p>
<pre><code class="language-bash">$ curl --request GET \\
--url 'https://api.vaix.ai/api/sports/events/trending?filters=league%3Aeq%3AUEFA%20Champions%20League'
</code></pre>"
`;
+23
View File
@@ -0,0 +1,23 @@
import type { PublishedSiteContent, SiteAPIToken } from '@gitbook/api';
import { jwtDecode } from 'jwt-decode';
/**
* Claims about the visitor, stored in the VA and auth token.
*/
export type VisitorAuthClaims = Record<string, any>;
/**
* Get the visitor auth claims from the API response obtained from `getPublishedContentByUrl`.
*/
export function getVisitorAuthClaims(siteData: PublishedSiteContent): VisitorAuthClaims {
const { apiToken } = siteData;
return getVisitorAuthClaimsFromToken(jwtDecode<SiteAPIToken>(apiToken));
}
/**
* Get the visitor auth claims from a decoded API token.
*/
export function getVisitorAuthClaimsFromToken(token: SiteAPIToken): VisitorAuthClaims {
return token.claims ?? {};
}
+6 -2
View File
@@ -153,7 +153,7 @@ export function withAPI<T>(client: GitBookAPIContext, fn: () => Promise<T>): Pro
type SpaceContentLookup = Pick<
PublishedSiteContent,
'space' | 'changeRequest' | 'revision' | 'pathname' | 'basePath' | 'apiToken'
'space' | 'changeRequest' | 'revision' | 'pathname' | 'basePath' | 'siteBasePath' | 'apiToken'
> & { kind: 'space' };
export type PublishedContentWithCache =
@@ -732,12 +732,16 @@ export const getComputedDocument = cache({
_organizationId: string,
spaceId: string,
source: ComputedContentSource,
seed: string,
options: CacheFunctionOptions
) => {
const apiCtx = await api();
const response = await apiCtx.client.spaces.getComputedDocument(
spaceId,
{ source },
{
source,
seed,
},
{},
{
signal: options.signal,
@@ -0,0 +1,56 @@
'use client';
import cookies from 'js-cookie';
import { checkIsSecurityError } from './security-error';
/**
* Get all cookies.
* @returns A map of cookie names to their values.
*/
export function getAllBrowserCookies(): Array<{
name: string;
value: string;
}> {
try {
const all = cookies.get();
return Object.entries(all).map(([name, value]) => ({ name, value }));
} catch (error) {
if (checkIsSecurityError(error)) {
return [];
}
throw error;
}
}
/**
* Get a map of cookie names to their values.
* @returns A map of cookie names to their values.
*/
export function getAllBrowserCookiesMap(): Record<string, string> {
return Object.fromEntries(getAllBrowserCookies().map(({ name, value }) => [name, value]));
}
/**
* Get a cookie by name.
* @param name - The name of the cookie to get.
* @returns The value of the cookie or undefined if the cookie does not exist.
*/
export function getBrowserCookie(name: string): string | undefined {
try {
return cookies.get(name);
} catch (error) {
if (checkIsSecurityError(error)) {
return undefined;
}
throw error;
}
}
/**
* Set a cookie.
* @param name - The name of the cookie to set.
* @param value - The value of the cookie to set.
* @param options - The options for the cookie to set.
*/
export const setBrowserCookie = cookies.set;
-29
View File
@@ -1,29 +0,0 @@
import cookies from 'js-cookie';
import { checkIsSecurityError } from './security-error';
export function getAll(): {
[key: string]: string;
} {
try {
return cookies.get();
} catch (error) {
if (checkIsSecurityError(error)) {
return {};
}
throw error;
}
}
export function get(name: string): string | undefined {
try {
return cookies.get(name);
} catch (error) {
if (checkIsSecurityError(error)) {
return undefined;
}
throw error;
}
}
export const set = cookies.set;
+13 -8
View File
@@ -10,6 +10,7 @@ import { headers } from 'next/headers';
import { GITBOOK_APP_URL } from '@v2/lib/env';
import { getPagePath } from './pages';
import { withLeadingSlash, withTrailingSlash } from './paths';
import { assertIsNotV2 } from './v2';
export interface PageHrefContext {
@@ -27,17 +28,21 @@ export interface PageHrefContext {
export async function getBasePath(): Promise<string> {
assertIsNotV2();
const headersList = await headers();
let path = headersList.get('x-gitbook-basepath') ?? '/';
const path = headersList.get('x-gitbook-basepath') ?? '/';
if (!path.startsWith('/')) {
path = `/${path}`;
}
return withTrailingSlash(withLeadingSlash(path));
}
if (!path.endsWith('/')) {
path = `${path}/`;
}
/**
* Return the site base path for the current request.
* The value will start and finish with /
*/
export async function getSiteBasePath(): Promise<string> {
assertIsNotV2();
const headersList = await headers();
const path = headersList.get('x-gitbook-site-basepath') ?? '/';
return path;
return withTrailingSlash(withLeadingSlash(path));
}
/**
File diff suppressed because one or more lines are too long
+17 -7
View File
@@ -1,12 +1,22 @@
import { micromark } from 'micromark';
import { gfm, gfmHtml } from 'micromark-extension-gfm';
import rehypeSanitize from 'rehype-sanitize';
import rehypeStringify from 'rehype-stringify';
import remarkGfm from 'remark-gfm';
import remarkParse from 'remark-parse';
import remarkRehype from 'remark-rehype';
import { unified } from 'unified';
/**
* Parse markdown and output HTML.
*/
export function parseMarkdown(input: string): string {
return micromark(input, {
extensions: [gfm()],
htmlExtensions: [gfmHtml()],
});
export async function parseMarkdown(markdown: string): Promise<string> {
const promise = unified()
.use(remarkParse)
.use(remarkGfm)
.use(remarkRehype)
.use(rehypeSanitize)
.use(rehypeStringify)
.process(markdown)
.then((file) => file.toString());
return promise;
}
+2 -2
View File
@@ -19,7 +19,7 @@ export async function enrichFilesystem(filesystem: Filesystem) {
if (key !== 'example') {
const description = node.description.trim();
if (description) {
node['x-gitbook-description-html'] = parseMarkdownWithCache(description);
node['x-gitbook-description-html'] = await parseMarkdownWithCache(description);
}
}
}
@@ -31,7 +31,7 @@ export async function enrichFilesystem(filesystem: Filesystem) {
* Create a markdown parser that caches the results of parsing.
*/
const createMarkdownParser = () => (input: string) => {
const cache: Record<string, string> = {};
const cache: Record<string, Promise<string>> = {};
const existing = cache[input];
if (existing) {
return existing;
+1 -1
View File
@@ -56,7 +56,7 @@ const fetchFilesystemV1 = cache({
get: async (url: string, options: CacheFunctionOptions) => {
const richFilesystem = await fetchFilesystemUncached(url, options);
return {
// Cache for 4 hours
// Cache for 24 hours
ttl: 24 * 60 * 60,
// Revalidate every 2 hours
revalidateBefore: 22 * 60 * 60,
+12 -1
View File
@@ -22,10 +22,21 @@ export function removeLeadingSlash(path: string): string {
/**
* Normalize a pathname to make it start with a slash
*/
export function normalizePathname(pathname: string): string {
export function withLeadingSlash(pathname: string): string {
if (!pathname.startsWith('/')) {
pathname = `/${pathname}`;
}
return pathname;
}
/**
* Normalize a pathname to make it end with a slash
*/
export function withTrailingSlash(pathname: string): string {
if (!pathname.endsWith('/')) {
pathname = `${pathname}/`;
}
return pathname;
}
+2 -2
View File
@@ -1,5 +1,5 @@
import type { PublishedSiteContent } from '@gitbook/api';
import { joinPath, normalizePathname, removeTrailingSlash } from './paths';
import { joinPath, removeTrailingSlash, withLeadingSlash } from './paths';
/**
* Compute the final base path for a site served in proxy mode.
@@ -16,6 +16,6 @@ export function getProxyModeBasePath(
.replace(removeTrailingSlash(resolved.pathname), '')
.replace(removeTrailingSlash(resolved.basePath), '');
const result = joinPath(normalizePathname(proxySitePath), resolved.basePath);
const result = joinPath(withLeadingSlash(proxySitePath), resolved.basePath);
return result.endsWith('/') ? result : `${result}/`;
}
+2 -1
View File
@@ -323,7 +323,8 @@ async function resolveContentRefInSpace(
);
const linker = createLinker({
host: baseURL.host,
pathname: baseURL.pathname,
spaceBasePath: baseURL.pathname,
siteBasePath: baseURL.pathname,
});
const resolved = await resolveContentRef(
+26 -21
View File
@@ -18,7 +18,6 @@ import {
getDocument,
getEmbedByUrlInSpace,
getLatestOpenAPISpecVersionContent,
getPublishedContentByUrl,
getPublishedContentSite,
getReusableContent,
getRevision,
@@ -28,10 +27,11 @@ import {
getSiteRedirectBySource,
getSpace,
getUserById,
renderIntegrationUi,
searchSiteContent,
} from './api';
import { getDynamicCustomizationSettings } from './customization';
import { getBasePath, getHost } from './links';
import { getBasePath, getHost, getSiteBasePath } from './links';
/*
* Code that will be used until the migration to v2 is complete.
@@ -43,12 +43,19 @@ import { getBasePath, getHost } from './links';
export async function getV1BaseContext(): Promise<GitBookBaseContext> {
const host = await getHost();
const basePath = await getBasePath();
const siteBasePath = await getSiteBasePath();
const linker = createLinker({
host,
pathname: basePath,
spaceBasePath: basePath,
siteBasePath: siteBasePath,
});
// On V1, we use hard-navigation between different spaces because of layout issues
linker.toLinkForContent = (url) => {
return url;
};
const dataFetcher = await getDataFetcherV1();
const imageResizer = createImageResizer({
@@ -56,7 +63,8 @@ export async function getV1BaseContext(): Promise<GitBookBaseContext> {
// In V1, we always resize at the top level of the hostname, not relative to the content.
linker: createLinker({
host,
pathname: '/',
spaceBasePath: '/',
siteBasePath: '/',
}),
});
@@ -73,11 +81,7 @@ export async function getV1BaseContext(): Promise<GitBookBaseContext> {
* This data fetcher should only be used at the top of the tree.
*/
async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
const apiClient = await api();
const dataFetcher: GitBookDataFetcher = {
apiEndpoint: apiClient.client.endpoint,
async api() {
const result = await api();
return result.client;
@@ -100,17 +104,6 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
});
},
// @ts-ignore - types are compatible enough, and this will not be called in v1 this way
getPublishedContentByUrl(params) {
return wrapDataFetcherError(async () => {
return getPublishedContentByUrl(
params.url,
params.visitorAuthToken ?? undefined,
params.redirectOnError ? true : undefined
);
});
},
getPublishedContentSite(params) {
return wrapDataFetcherError(async () => {
return getPublishedContentSite(params);
@@ -173,7 +166,12 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
getComputedDocument(params) {
return wrapDataFetcherError(() => {
return getComputedDocument(params.organizationId, params.spaceId, params.source);
return getComputedDocument(
params.organizationId,
params.spaceId,
params.source,
params.seed
);
});
},
@@ -248,7 +246,7 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
});
},
async searchSiteContent(params) {
searchSiteContent(params) {
return wrapDataFetcherError(async () => {
const { organizationId, siteId, query, cacheBust, scope } = params;
const result = await searchSiteContent(
@@ -261,6 +259,13 @@ async function getDataFetcherV1(): Promise<GitBookDataFetcher> {
return result.items;
});
},
renderIntegrationUi(params) {
return wrapDataFetcherError(async () => {
const result = await renderIntegrationUi(params.integrationName, params.request);
return result;
});
},
};
return dataFetcher;
+57 -41
View File
@@ -1,5 +1,4 @@
import { describe, expect, it } from 'bun:test';
import type { NextRequest } from 'next/server';
import type { JwtPayload } from 'jwt-decode';
import {
@@ -11,66 +10,94 @@ import {
describe('getVisitorAuthToken', () => {
it('should return the token from the query parameters', () => {
const request = nextRequest('https://example.com?jwt_token=123');
expect(getVisitorToken(request, request.nextUrl)).toEqual({ source: 'url', token: '123' });
expect(
getVisitorToken({ cookies: [], url: new URL('https://example.com?jwt_token=123') })
).toEqual({ source: 'url', token: '123' });
});
it('should return the token from the cookie root basepath', () => {
const request = nextRequest('https://example.com', {
[getVisitorAuthCookieName('/')]: { value: getVisitorAuthCookieValue('/', '123') },
const visitorAuth = getVisitorToken({
cookies: [
{
name: getVisitorAuthCookieName('/'),
value: getVisitorAuthCookieValue('/', '123'),
},
],
url: new URL('https://example.com'),
});
const visitorAuth = getVisitorToken(request, request.nextUrl);
assertVisitorAuthCookieValue(visitorAuth);
expect(visitorAuth.token).toEqual('123');
});
it('should return the token from the cookie root basepath for a sub-path', () => {
const request = nextRequest('https://example.com/hello/world', {
[getVisitorAuthCookieName('/')]: { value: getVisitorAuthCookieValue('/', '123') },
const visitorAuth = getVisitorToken({
cookies: [
{
name: getVisitorAuthCookieName('/'),
value: getVisitorAuthCookieValue('/', '123'),
},
],
url: new URL('https://example.com/hello/world'),
});
const visitorAuth = getVisitorToken(request, request.nextUrl);
assertVisitorAuthCookieValue(visitorAuth);
expect(visitorAuth.token).toEqual('123');
});
it('should return the closest token from the path', () => {
const request = nextRequest('https://example.com/hello/world', {
[getVisitorAuthCookieName('/')]: { value: getVisitorAuthCookieValue('/', 'no') },
[getVisitorAuthCookieName('/hello/')]: {
value: getVisitorAuthCookieValue('/hello/', '123'),
},
const visitorAuth = getVisitorToken({
cookies: [
{
name: getVisitorAuthCookieName('/'),
value: getVisitorAuthCookieValue('/', 'no'),
},
{
name: getVisitorAuthCookieName('/hello/'),
value: getVisitorAuthCookieValue('/hello/', '123'),
},
],
url: new URL('https://example.com/hello/world'),
});
const visitorAuth = getVisitorToken(request, request.nextUrl);
assertVisitorAuthCookieValue(visitorAuth);
expect(visitorAuth.token).toEqual('123');
});
it('should return the token from the cookie in a collection type url', () => {
const request = nextRequest('https://example.com/hello/v/space1/cool', {
[getVisitorAuthCookieName('/hello/v/space1/')]: {
value: getVisitorAuthCookieValue('/hello/v/space1/', '123'),
},
const visitorAuth = getVisitorToken({
cookies: [
{
name: getVisitorAuthCookieName('/hello/v/space1/'),
value: getVisitorAuthCookieValue('/hello/v/space1/', '123'),
},
],
url: new URL('https://example.com/hello/v/space1/cool'),
});
const visitorAuth = getVisitorToken(request, request.nextUrl);
assertVisitorAuthCookieValue(visitorAuth);
expect(visitorAuth.token).toEqual('123');
});
it('should return undefined if no cookie and no query param', () => {
const request = nextRequest('https://example.com');
expect(getVisitorToken(request, request.nextUrl)).toBeUndefined();
expect(
getVisitorToken({
cookies: [],
url: new URL('https://example.com'),
})
).toBeUndefined();
});
// For backwards compatibility
it('should return the token from the cookie of a /v/ path when the url does not have /v/', () => {
const request = nextRequest('https://example.com/hello/space1/cool', {
[getVisitorAuthCookieName('/')]: { value: getVisitorAuthCookieValue('/', 'no') },
[getVisitorAuthCookieName('/hello/v/space1/')]: {
value: getVisitorAuthCookieValue('/hello/v/space1/', 'gotcha'),
},
const visitorAuth = getVisitorToken({
cookies: [
{
name: getVisitorAuthCookieName('/'),
value: getVisitorAuthCookieValue('/', 'no'),
},
{
name: getVisitorAuthCookieName('/hello/v/space1/'),
value: getVisitorAuthCookieValue('/hello/v/space1/', 'gotcha'),
},
],
url: new URL('https://example.com/hello/space1/cool'),
});
const visitorAuth = getVisitorToken(request, request.nextUrl);
assertVisitorAuthCookieValue(visitorAuth);
expect(visitorAuth.token).toEqual('gotcha');
});
@@ -131,14 +158,3 @@ function assertVisitorAuthCookieValue(
throw new Error('Expected a VisitorAuthCookieValue');
}
function nextRequest(url: string, cookies: Record<string, { value: string }> = {}) {
const nextUrl = new URL(url);
// @ts-ignore
return {
url: nextUrl.toString(),
nextUrl,
headers: new Headers(),
cookies: Object.entries(cookies),
} as NextRequest;
}
+63 -54
View File
@@ -5,7 +5,11 @@ import hash from 'object-hash';
const VISITOR_AUTH_PARAM = 'jwt_token';
export const VISITOR_TOKEN_COOKIE = 'gitbook-visitor-token';
/**
* Typing for a cookie, matching the internal type of Next.js.
*/
export type ResponseCookie = {
name: string;
value: string;
options?: Partial<{
httpOnly: boolean;
@@ -15,7 +19,8 @@ export type ResponseCookie = {
}>;
};
export type ResponseCookies = Record<string, ResponseCookie>;
export type ResponseCookies = ResponseCookie[];
export type RequestCookies = ResponseCookies;
/**
* The contents of the visitor authentication cookie.
@@ -52,10 +57,13 @@ export type VisitorTokenLookup =
* Get the visitor token for the request. This token can either be in the
* query parameters or stored as a cookie.
*/
export function getVisitorToken(
request: NextRequest,
url: URL | NextRequest['nextUrl']
): VisitorTokenLookup {
export function getVisitorToken({
cookies,
url,
}: {
cookies: RequestCookies;
url: URL | NextRequest['nextUrl'];
}): VisitorTokenLookup {
const fromUrl = url.searchParams.get(VISITOR_AUTH_PARAM);
// Allow the empty string to come through
@@ -63,27 +71,26 @@ export function getVisitorToken(
return { source: 'url', token: fromUrl };
}
const visitorAuthToken = getVisitorAuthTokenFromCookies(request, url);
const visitorAuthToken = getVisitorAuthTokenFromCookies(cookies, url);
if (visitorAuthToken) {
return { source: 'visitor-auth-cookie', ...visitorAuthToken };
}
const visitorCustomToken = getVisitorCustomTokenFromCookies(request);
const visitorCustomToken = getVisitorCustomTokenFromCookies(cookies);
if (visitorCustomToken) {
return { source: 'gitbook-visitor-cookie', token: visitorCustomToken };
}
}
/**
* Return the lookup result for content served with visitor auth. It basically disables caching
* and sets a cookie with the visitor auth token.
* Return the lookup result for content served with visitor auth.
*/
export function getResponseCookiesForVisitorAuth(
basePath: string,
visitorTokenLookup: VisitorTokenLookup
): ResponseCookies {
if (!visitorTokenLookup) {
return {};
return [];
}
let decoded: JwtPayload;
@@ -91,32 +98,35 @@ export function getResponseCookiesForVisitorAuth(
decoded = jwtDecode(visitorTokenLookup.token);
} catch (error) {
console.error('Error decoding visitor token', error);
return {};
return [];
}
return {
/**
* If the visitor token has been retrieve from the URL, or if its a VA cookie and the basePath is the same, set it
* as a cookie on the response.
*
* Note that we do not re-store the gitbook-visitor-cookie in another cookie, to maintain a single source of truth.
*/
...(visitorTokenLookup?.source === 'url' ||
/**
* If the visitor token has been retrieve from the URL, or if its a VA cookie and the basePath is the same, set it
* as a cookie on the response.
*
* Note that we do not re-store the gitbook-visitor-cookie in another cookie, to maintain a single source of truth.
*/
if (
visitorTokenLookup?.source === 'url' ||
(visitorTokenLookup?.source === 'visitor-auth-cookie' &&
visitorTokenLookup.basePath === basePath)
? {
[getVisitorAuthCookieName(basePath)]: {
value: getVisitorAuthCookieValue(basePath, visitorTokenLookup.token),
options: {
httpOnly: true,
sameSite: process.env.NODE_ENV === 'production' ? 'none' : undefined,
secure: process.env.NODE_ENV === 'production',
maxAge: getVisitorAuthCookieMaxAge(decoded),
},
},
}
: {}),
};
) {
return [
{
name: getVisitorAuthCookieName(basePath),
value: getVisitorAuthCookieValue(basePath, visitorTokenLookup.token),
options: {
httpOnly: true,
sameSite: process.env.NODE_ENV === 'production' ? 'none' : undefined,
secure: process.env.NODE_ENV === 'production',
maxAge: getVisitorAuthCookieMaxAge(decoded),
},
},
];
}
return [];
}
/**
@@ -141,9 +151,13 @@ export function getVisitorAuthCookieValue(basePath: string, token: string): stri
* Normalize the URL by removing the visitor authentication token from the query parameters (if present).
*/
export function normalizeVisitorAuthURL(url: URL): URL {
const withoutVAParam = new URL(url);
withoutVAParam.searchParams.delete(VISITOR_AUTH_PARAM);
return withoutVAParam;
if (url.searchParams.has(VISITOR_AUTH_PARAM)) {
const withoutVAParam = new URL(url);
withoutVAParam.searchParams.delete(VISITOR_AUTH_PARAM);
return withoutVAParam;
}
return url;
}
/**
@@ -180,7 +194,7 @@ function getUrlBasePathCombinations(url: URL | NextRequest['nextUrl']): string[]
* best possible match for the current URL.
*/
function getVisitorAuthTokenFromCookies(
request: NextRequest,
cookies: RequestCookies,
url: URL | NextRequest['nextUrl']
): VisitorAuthCookieValue | undefined {
const urlBasePaths = getUrlBasePathCombinations(url);
@@ -188,7 +202,7 @@ function getVisitorAuthTokenFromCookies(
// for the content could be hosted on a base path like `/foo/v/bar` or `/foo` or just `/`
// We keep trying to find with each of these base paths until we find a token.
for (const basePath of urlBasePaths) {
const found = findVisitorAuthCookieForBasePath(request, basePath);
const found = findVisitorAuthCookieForBasePath(cookies, basePath);
if (found) {
return found;
}
@@ -205,32 +219,27 @@ function getVisitorAuthTokenFromCookies(
*
* The cookie should contain as value a JWT encoded token that contains the claims of the visitor.
*/
function getVisitorCustomTokenFromCookies(request: NextRequest): string | undefined {
const visitorCustomCookie = Array.from(request.cookies).find(
([, cookie]) => cookie.name === VISITOR_TOKEN_COOKIE
);
return visitorCustomCookie ? visitorCustomCookie[1].value : undefined;
function getVisitorCustomTokenFromCookies(cookies: RequestCookies): string | undefined {
const visitorCustomCookie = cookies.find((cookie) => cookie.name === VISITOR_TOKEN_COOKIE);
return visitorCustomCookie ? visitorCustomCookie.value : undefined;
}
/**
* Loop through all cookies and find the visitor authentication token for a given base path.
*/
function findVisitorAuthCookieForBasePath(
request: NextRequest,
cookies: RequestCookies,
basePath: string
): VisitorAuthCookieValue | undefined {
return Array.from(request.cookies).reduce<VisitorAuthCookieValue | undefined>(
(acc, [name, cookie]) => {
if (name === getVisitorAuthCookieName(basePath)) {
const value = JSON.parse(cookie.value) as VisitorAuthCookieValue;
if (value.basePath === basePath) {
acc = value;
}
return cookies.reduce<VisitorAuthCookieValue | undefined>((acc, cookie) => {
if (cookie.name === getVisitorAuthCookieName(basePath)) {
const value = JSON.parse(cookie.value) as VisitorAuthCookieValue;
if (value.basePath === basePath) {
acc = value;
}
return acc;
},
undefined
);
}
return acc;
}, undefined);
}
/**

Some files were not shown because too many files have changed in this diff Show More