Compare commits

..

21 Commits

Author SHA1 Message Date
Zeno Kapitein d980d7a9ea Changeset 2025-04-15 16:55:17 +02:00
Zeno Kapitein eab3b7aea4 Format 2025-04-15 16:54:59 +02:00
Zeno Kapitein 3af177be19 Fix dropdown menu not staying fixed 2025-04-15 16:52:59 +02:00
Samy Pessé e4c7307c99 Fix CORS error when loading fonts in 2c (#3163) 2025-04-14 17:05:31 -07:00
Samy Pessé f9c942c8ff Fix CORS error when loading icons in 2c (#3162) 2025-04-14 16:52:39 -07:00
Greg Bergé eeb977fc03 Fix Python JSON code example (#3159) 2025-04-14 19:50:40 +00:00
Greg Bergé 4b8a62122a Show sections tabs only if there is at least two sections (#3158) 2025-04-14 14:09:34 +02:00
Addison e44e947fc1 Update cookie banner to use American English (#3154) 2025-04-14 18:43:01 +09:00
Nolann B. 7588cfe1c5 Improve OpenAPIResponses examples and schemas (#3123) 2025-04-14 11:16:35 +02:00
Greg Bergé a9a643f222 Improve icon stabilization (#3147) 2025-04-13 05:41:47 +00:00
Samy Pessé 77d6cb75be Improve error message when image resizing failed (#3148) 2025-04-12 17:44:23 -07:00
Samy Pessé 1942d221df Bump the cache of getPublishedContentByUrl in v1 (#3146) 2025-04-12 07:18:26 -07:00
Greg Bergé 041601ed65 Upgrade Argos (#3145) 2025-04-12 15:48:51 +02:00
Steven H 5567281a85 Bump getPublishedContentByUrl cache. (#3144) 2025-04-11 17:13:01 +01:00
Samy Pessé 4c3753046b Cloudflare: fix image resizing (#3143) 2025-04-11 16:48:43 +02:00
Samy Pessé 89f8e16184 Cloudflare: resize images in v2 using direct fetch (#3141) 2025-04-11 15:18:44 +02:00
Samy Pessé a3e1125ced Hotfix for v1 crashes (#3139) 2025-04-10 22:00:41 +02:00
Samy Pessé 4dae8acfed Cloudflare: fix image resizing (#3138) 2025-04-10 19:30:15 +02:00
Samy Pessé bc081c4344 Fix cache revalidation for computed revisions (#3134) 2025-04-10 16:36:02 +02:00
Samy Pessé e4275b235d Version Packages (#3137)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 13:31:27 +02:00
Samy Pessé 77397cac82 Fix version of @gitbook/api referenced in cache-tags (#3136) 2025-04-10 13:30:28 +02:00
75 changed files with 2588 additions and 1440 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Show sections tabs only if there is at least two sections
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/react-openapi": patch
---
Fix Python code example for JSON payload
+6
View File
@@ -0,0 +1,6 @@
---
'@gitbook/react-openapi': patch
'gitbook': patch
---
Improve OpenAPIResponses examples and schemas
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix dropdown menu not staying fixed
@@ -55,6 +55,7 @@ runs:
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
- name: Build worker - name: Build worker
@@ -69,7 +70,7 @@ runs:
apiToken: ${{ inputs.apiToken }} apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }} accountId: ${{ inputs.accountId }}
workingDirectory: ./ workingDirectory: ./
wranglerVersion: '3.112.0' wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }} environment: ${{ inputs.environment }}
command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/wrangler.jsonc command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/wrangler.jsonc
- name: Outputs - name: Outputs
@@ -54,6 +54,7 @@ runs:
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
- name: Build Project Artifacts - name: Build Project Artifacts
+2 -2
View File
@@ -200,7 +200,7 @@ jobs:
name: Visual Testing v2 (Cloudflare) name: Visual Testing v2 (Cloudflare)
needs: deploy-v2-cloudflare needs: deploy-v2-cloudflare
timeout-minutes: 10 timeout-minutes: 10
if: startsWith(github.ref_name, 'cloudflare/') if: startsWith(github.head_ref || github.ref_name, 'cloudflare/') || github.ref == 'refs/heads/main'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -263,7 +263,7 @@ jobs:
name: Visual Testing Customers v2 (Cloudflare) name: Visual Testing Customers v2 (Cloudflare)
needs: deploy-v2-cloudflare needs: deploy-v2-cloudflare
timeout-minutes: 8 timeout-minutes: 8
if: startsWith(github.ref_name, 'cloudflare/') if: startsWith(github.head_ref || github.ref_name, 'cloudflare/') || github.ref == 'refs/heads/main'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
+3 -3
View File
@@ -59,15 +59,15 @@ bun install
4. Start your local development server. 4. Start your local development server.
``` ```
bun dev bun dev:v2
``` ```
5. Open a published GitBook space in your web browser, prefixing it with `http://localhost:3000/`. 5. Open a published GitBook space in your web browser, prefixing it with `http://localhost:3000/`.
examples: examples:
- http://localhost:3000/docs.gitbook.com - http://localhost:3000/url/docs.gitbook.com
- http://localhost:3000/open-source.gitbook.io/midjourney - http://localhost:3000/url/open-source.gitbook.io/midjourney
Any published GitBook site can be accessed through your local development instance, and any updates you make to the codebase will be reflected in your browser. Any published GitBook site can be accessed through your local development instance, and any updates you make to the codebase will be reflected in your browser.
+155 -72
View File
@@ -19,14 +19,14 @@
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.5.3", "typescript": "^5.5.3",
"wrangler": "^3.112.0", "wrangler": "^4.10.0",
}, },
}, },
"packages/cache-tags": { "packages/cache-tags": {
"name": "@gitbook/cache-tags", "name": "@gitbook/cache-tags",
"version": "0.2.0", "version": "0.3.1",
"dependencies": { "dependencies": {
"@gitbook/api": "*", "@gitbook/api": "^0.109.0",
"assert-never": "^1.2.1", "assert-never": "^1.2.1",
}, },
"devDependencies": { "devDependencies": {
@@ -35,7 +35,7 @@
}, },
"packages/colors": { "packages/colors": {
"name": "@gitbook/colors", "name": "@gitbook/colors",
"version": "0.3.1", "version": "0.3.2",
"devDependencies": { "devDependencies": {
"typescript": "^5.5.3", "typescript": "^5.5.3",
}, },
@@ -49,7 +49,7 @@
}, },
"packages/gitbook": { "packages/gitbook": {
"name": "gitbook", "name": "gitbook",
"version": "0.9.2", "version": "0.10.1",
"dependencies": { "dependencies": {
"@gitbook/api": "*", "@gitbook/api": "*",
"@gitbook/cache-do": "workspace:*", "@gitbook/cache-do": "workspace:*",
@@ -110,7 +110,7 @@
"zustand": "^5.0.3", "zustand": "^5.0.3",
}, },
"devDependencies": { "devDependencies": {
"@argos-ci/playwright": "^4.3.0", "@argos-ci/playwright": "^5.0.3",
"@cloudflare/next-on-pages": "1.13.7", "@cloudflare/next-on-pages": "1.13.7",
"@cloudflare/workers-types": "^4.20241230.0", "@cloudflare/workers-types": "^4.20241230.0",
"@playwright/test": "^1.51.1", "@playwright/test": "^1.51.1",
@@ -140,11 +140,12 @@
}, },
"packages/gitbook-v2": { "packages/gitbook-v2": {
"name": "gitbook-v2", "name": "gitbook-v2",
"version": "0.2.3", "version": "0.2.5",
"dependencies": { "dependencies": {
"@gitbook/api": "*", "@gitbook/api": "*",
"@gitbook/cache-tags": "workspace:*", "@gitbook/cache-tags": "workspace:*",
"@sindresorhus/fnv1a": "^3.1.0", "@sindresorhus/fnv1a": "^3.1.0",
"assert-never": "^1.2.1",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"next": "canary", "next": "canary",
"react": "^19.0.0", "react": "^19.0.0",
@@ -154,7 +155,7 @@
"warn-once": "^0.1.1", "warn-once": "^0.1.1",
}, },
"devDependencies": { "devDependencies": {
"@opennextjs/cloudflare": "https://pkg.pr.new/opennextjs/opennextjs-cloudflare/@opennextjs/cloudflare@7b3b305", "@opennextjs/cloudflare": "^1.0.0-beta.3",
"@types/rison": "^0.0.9", "@types/rison": "^0.0.9",
"gitbook": "*", "gitbook": "*",
"postcss": "^8", "postcss": "^8",
@@ -183,7 +184,7 @@
}, },
"packages/openapi-parser": { "packages/openapi-parser": {
"name": "@gitbook/openapi-parser", "name": "@gitbook/openapi-parser",
"version": "2.1.2", "version": "2.1.3",
"dependencies": { "dependencies": {
"@scalar/openapi-parser": "^0.10.10", "@scalar/openapi-parser": "^0.10.10",
"@scalar/openapi-types": "^0.1.9", "@scalar/openapi-types": "^0.1.9",
@@ -230,7 +231,7 @@
}, },
"packages/react-openapi": { "packages/react-openapi": {
"name": "@gitbook/react-openapi", "name": "@gitbook/react-openapi",
"version": "1.1.9", "version": "1.1.10",
"dependencies": { "dependencies": {
"@gitbook/openapi-parser": "workspace:*", "@gitbook/openapi-parser": "workspace:*",
"@scalar/api-client-react": "^1.2.5", "@scalar/api-client-react": "^1.2.5",
@@ -273,15 +274,15 @@
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@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/api-client": ["@argos-ci/api-client@0.8.1", "", { "dependencies": { "debug": "^4.4.0", "openapi-fetch": "0.13.5" } }, "sha512-3IHv7ANSPNO6OwWgwULlHbP9/tFV9kQDu6+nL9jysfPkGj0GgtrOsyBb+iU931c7wSMo1OD+XNujCnRzDD968w=="],
"@argos-ci/browser": ["@argos-ci/browser@3.2.0", "", {}, "sha512-mHLUamfywbdzrM6SRV4KB4kEQs2lrFxC1yS+QWB/9lME7FKzr7MJW/Q++iWcQli8IbmtLsZ+PqP2eqLaDiRsUw=="], "@argos-ci/browser": ["@argos-ci/browser@4.1.1", "", {}, "sha512-UyKdnprGftUjWQkb0jqJ0zGHJmcWBzdko8zRy4y+4efukVX4jjC/Px2HvWW8aqwjoR4aplouMZuzhmOkq2SCsA=="],
"@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/core": ["@argos-ci/core@3.1.1", "", { "dependencies": { "@argos-ci/api-client": "0.8.1", "@argos-ci/util": "2.3.1", "axios": "^1.8.4", "convict": "^6.2.4", "debug": "^4.4.0", "fast-glob": "^3.3.3", "sharp": "^0.33.5", "tmp": "^0.2.3" } }, "sha512-7iE3o1XGxlfHC5AF05pzT0OxuO387sryrZt3gKGj/e+6R20DXz7J49yI8++nQ2cuT+wLhcJp8+X0ox+SGMYHmw=="],
"@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/playwright": ["@argos-ci/playwright@5.0.3", "", { "dependencies": { "@argos-ci/browser": "4.1.1", "@argos-ci/core": "3.1.1", "@argos-ci/util": "2.3.1", "chalk": "^5.4.1", "debug": "^4.4.0" } }, "sha512-sqoARsgnDRrwKm1x10L3Z8+OQukk0F9OksVj9v9rvbzNI2WVCw5zbCUMY0qD4Q3Ba7vMFbl1ELUODRc2mfCbNw=="],
"@argos-ci/util": ["@argos-ci/util@2.3.0", "", {}, "sha512-tkxnCpaj7yN9nCFzo9MX0FJ5YjUepEOGYfdvF8COQqp+EdY1qubOPpc4Z0l1B60BlC8YtjQv/oRxHSh1XzxWFg=="], "@argos-ci/util": ["@argos-ci/util@2.3.1", "", {}, "sha512-kE61HU2480fbAnimmA4x9HK45ZJvkoqLdW5GxT5uvwhkclQykVd2S6WfGFUr3JokTXfZ5LZEEfoWgtGA316KSQ=="],
"@ast-grep/napi": ["@ast-grep/napi@0.35.0", "", { "optionalDependencies": { "@ast-grep/napi-darwin-arm64": "0.35.0", "@ast-grep/napi-darwin-x64": "0.35.0", "@ast-grep/napi-linux-arm64-gnu": "0.35.0", "@ast-grep/napi-linux-arm64-musl": "0.35.0", "@ast-grep/napi-linux-x64-gnu": "0.35.0", "@ast-grep/napi-linux-x64-musl": "0.35.0", "@ast-grep/napi-win32-arm64-msvc": "0.35.0", "@ast-grep/napi-win32-ia32-msvc": "0.35.0", "@ast-grep/napi-win32-x64-msvc": "0.35.0" } }, "sha512-3ucaaSxV6fxXoqHrE/rxAvP1THnDdY5jNzGlnvx+JvnY9C/dSRKc0jlRMRz59N3El572+/yNRUUpAV1T9aBJug=="], "@ast-grep/napi": ["@ast-grep/napi@0.35.0", "", { "optionalDependencies": { "@ast-grep/napi-darwin-arm64": "0.35.0", "@ast-grep/napi-darwin-x64": "0.35.0", "@ast-grep/napi-linux-arm64-gnu": "0.35.0", "@ast-grep/napi-linux-arm64-musl": "0.35.0", "@ast-grep/napi-linux-x64-gnu": "0.35.0", "@ast-grep/napi-linux-x64-musl": "0.35.0", "@ast-grep/napi-win32-arm64-msvc": "0.35.0", "@ast-grep/napi-win32-ia32-msvc": "0.35.0", "@ast-grep/napi-win32-x64-msvc": "0.35.0" } }, "sha512-3ucaaSxV6fxXoqHrE/rxAvP1THnDdY5jNzGlnvx+JvnY9C/dSRKc0jlRMRz59N3El572+/yNRUUpAV1T9aBJug=="],
@@ -469,19 +470,21 @@
"@changesets/write": ["@changesets/write@0.3.2", "", { "dependencies": { "@changesets/types": "^6.0.0", "fs-extra": "^7.0.1", "human-id": "^1.0.2", "prettier": "^2.7.1" } }, "sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw=="], "@changesets/write": ["@changesets/write@0.3.2", "", { "dependencies": { "@changesets/types": "^6.0.0", "fs-extra": "^7.0.1", "human-id": "^1.0.2", "prettier": "^2.7.1" } }, "sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw=="],
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="], "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="],
"@cloudflare/next-on-pages": ["@cloudflare/next-on-pages@1.13.7", "", { "dependencies": { "acorn": "^8.8.0", "ast-types": "^0.14.2", "chalk": "^5.2.0", "chokidar": "^3.5.3", "commander": "^11.1.0", "cookie": "^0.5.0", "esbuild": "^0.15.3", "js-yaml": "^4.1.0", "miniflare": "^3.20231218.1", "package-manager-manager": "^0.2.0", "pcre-to-regexp": "^1.1.0", "semver": "^7.5.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20240208.0", "vercel": ">=30.0.0", "wrangler": "^3.28.2" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "next-on-pages": "bin/index.js" } }, "sha512-TSMVy+1fmxzeyykOC9guMEj7G2FgENw1T8V1sIFnah6piaJNBmybdyikPdQSdikP5w6v9eQhBt/TrXDPMDw0dw=="], "@cloudflare/next-on-pages": ["@cloudflare/next-on-pages@1.13.7", "", { "dependencies": { "acorn": "^8.8.0", "ast-types": "^0.14.2", "chalk": "^5.2.0", "chokidar": "^3.5.3", "commander": "^11.1.0", "cookie": "^0.5.0", "esbuild": "^0.15.3", "js-yaml": "^4.1.0", "miniflare": "^3.20231218.1", "package-manager-manager": "^0.2.0", "pcre-to-regexp": "^1.1.0", "semver": "^7.5.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20240208.0", "vercel": ">=30.0.0", "wrangler": "^3.28.2" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "next-on-pages": "bin/index.js" } }, "sha512-TSMVy+1fmxzeyykOC9guMEj7G2FgENw1T8V1sIFnah6piaJNBmybdyikPdQSdikP5w6v9eQhBt/TrXDPMDw0dw=="],
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250214.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cDvvedWDc5zrgDnuXe2qYcz/TwBvzmweO55C7XpPuAWJ9Oqxv81PkdekYxD8mH989aQ/GI5YD0Fe6fDYlM+T3Q=="], "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.15", "workerd": "^1.20250320.0" }, "optionalPeers": ["workerd"] }, "sha512-Xq57Qd+ADpt6hibcVBO0uLG9zzRgyRhfCUgBT9s+g3+3Ivg5zDyVgLFy40ES1VdNcu8rPNSivm9A+kGP5IVaPg=="],
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250214.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-NytCvRveVzu0mRKo+tvZo3d/gCUway3B2ZVqSi/TS6NXDGBYIJo7g6s3BnTLS74kgyzeDOjhu9j/RBJBS809qw=="], "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250409.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-smA9yq77xsdQ1NMLhFz3JZxMHGd01lg0bE+X3dTFmIUs+hHskJ+HJ/IkMFInkCCeEFlUkoL4yO7ilaU/fin/xA=="],
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250214.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pQ7+aHNHj8SiYEs4d/6cNoimE5xGeCMfgU1yfDFtA9YGN9Aj2BITZgOWPec+HW7ZkOy9oWlNrO6EvVjGgB4tbQ=="], "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250409.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oLVcf+Y5Qun8JHcy1VcR/YnbA5U2ne0czh3XNhDqdHZFK8+vKeC7MnVPX+kEqQA3+uLcMM1/FsIDU1U4Na0h1g=="],
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250214.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Vhlfah6Yd9ny1npNQjNgElLIjR6OFdEbuR3LCfbLDCwzWEBFhIf7yC+Tpp/a0Hq7kLz3sLdktaP7xl3PJhyOjA=="], "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250409.0", "", { "os": "linux", "cpu": "x64" }, "sha512-D31B4kdC3a0RD5yfpdIa89//kGHbYsYihZmejm1k4S4NHOho3MUDHAEh4aHtafQNXbZdydGHlSyiVYjTdQ9ILQ=="],
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250214.0", "", { "os": "win32", "cpu": "x64" }, "sha512-GMwMyFbkjBKjYJoKDhGX8nuL4Gqe3IbVnVWf2Q6086CValyIknupk5J6uQWGw2EBU3RGO3x4trDXT5WphQJZDQ=="], "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250409.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Sr59P0TREayil5OQ7kcbjuIn6L6OTSRLI91LKu0D8vi1hss2q9FUwBcwxg0+Yd/x+ty/x7IISiAK5QBkAMeITQ=="],
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250409.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dK9I8zBX5rR7MtaaP2AhICQTEw3PVzHcsltN8o46w7JsbYlMvFOj27FfYH5dhs3IahgmIfw2e572QXW2o/dbpg=="],
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20241230.0", "", {}, "sha512-dtLD4jY35Lb750cCVyO1i/eIfdZJg2Z0i+B1RYX6BVeRPlgaHx/H18ImKAkYmy0g09Ow8R2jZy3hIxMgXun0WQ=="], "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20241230.0", "", {}, "sha512-dtLD4jY35Lb750cCVyO1i/eIfdZJg2Z0i+B1RYX6BVeRPlgaHx/H18ImKAkYmy0g09Ow8R2jZy3hIxMgXun0WQ=="],
@@ -547,49 +550,55 @@
"@esbuild-plugins/node-modules-polyfill": ["@esbuild-plugins/node-modules-polyfill@0.2.2", "", { "dependencies": { "escape-string-regexp": "^4.0.0", "rollup-plugin-node-polyfills": "^0.2.1" }, "peerDependencies": { "esbuild": "*" } }, "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA=="], "@esbuild-plugins/node-modules-polyfill": ["@esbuild-plugins/node-modules-polyfill@0.2.2", "", { "dependencies": { "escape-string-regexp": "^4.0.0", "rollup-plugin-node-polyfills": "^0.2.1" }, "peerDependencies": { "esbuild": "*" } }, "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.17.19", "", { "os": "android", "cpu": "arm64" }, "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.17.19", "", { "os": "android", "cpu": "x64" }, "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww=="], "@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg=="], "@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw=="], "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.17.19", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ=="], "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.17.19", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ=="], "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.17.19", "", { "os": "linux", "cpu": "arm" }, "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA=="], "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg=="], "@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.17.19", "", { "os": "linux", "cpu": "ia32" }, "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ=="], "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ=="], "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A=="], "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.17.19", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg=="], "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA=="], "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.17.19", "", { "os": "linux", "cpu": "s390x" }, "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q=="], "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw=="], "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.17.19", "", { "os": "none", "cpu": "x64" }, "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q=="], "@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.17.19", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g=="], "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.17.19", "", { "os": "sunos", "cpu": "x64" }, "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg=="], "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag=="], "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.17.19", "", { "os": "win32", "cpu": "ia32" }, "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw=="], "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA=="], "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
"@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="],
@@ -785,9 +794,9 @@
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@opennextjs/aws": ["@opennextjs/aws@3.5.5", "", { "dependencies": { "@ast-grep/napi": "^0.35.0", "@aws-sdk/client-cloudfront": "3.398.0", "@aws-sdk/client-dynamodb": "^3.398.0", "@aws-sdk/client-lambda": "^3.398.0", "@aws-sdk/client-s3": "^3.398.0", "@aws-sdk/client-sqs": "^3.398.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.1", "aws4fetch": "^1.0.18", "chalk": "^5.3.0", "esbuild": "0.19.2", "express": "5.0.1", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.0.0", "yaml": "^2.7.0" }, "bin": { "open-next": "dist/index.js" } }, "sha512-vIqCcNxy7vPdC5HQQkCN+AXhxdNvagZ8EYw5XvuYIAP2T1Ci9ai/8m6UXtjgd3L9+aBA8pbWfr2Wr/BNLX5m2Q=="], "@opennextjs/aws": ["@opennextjs/aws@3.5.7", "", { "dependencies": { "@ast-grep/napi": "^0.35.0", "@aws-sdk/client-cloudfront": "3.398.0", "@aws-sdk/client-dynamodb": "^3.398.0", "@aws-sdk/client-lambda": "^3.398.0", "@aws-sdk/client-s3": "^3.398.0", "@aws-sdk/client-sqs": "^3.398.0", "@node-minify/core": "^8.0.6", "@node-minify/terser": "^8.0.6", "@tsconfig/node18": "^1.0.1", "aws4fetch": "^1.0.18", "chalk": "^5.3.0", "esbuild": "0.19.2", "express": "5.0.1", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.0.0", "yaml": "^2.7.0" }, "bin": { "open-next": "dist/index.js" } }, "sha512-YjyHJrkIHI7YwQRCp8GjDOudu86oOc1RiwxvBBpPHrplsS18H4ZmkzGggAKhK6B4myGsJQ/q9kNP2TraoZiNzg=="],
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@https://pkg.pr.new/opennextjs/opennextjs-cloudflare/@opennextjs/cloudflare@7b3b305", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.5.5", "enquirer": "^2.4.1", "glob": "^11.0.0", "ts-tqdm": "^0.8.6" }, "peerDependencies": { "wrangler": "^3.114.3 || ^4.7.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }], "@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.0.0-beta.3", "", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.5.7", "enquirer": "^2.4.1", "glob": "^11.0.0", "ts-tqdm": "^0.8.6" }, "peerDependencies": { "wrangler": "^3.114.3 || ^4.7.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-qKBXQZhUeQ+iGvfJeF7PO30g59LHnPOlRVZd77zxwn6Uc9C+c0LSwo8N28XRIWyQPkY007rKk9pSIxOrP4MHtQ=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
@@ -1441,7 +1450,7 @@
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
"axios": ["axios@1.7.9", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw=="], "axios": ["axios@1.8.4", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw=="],
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
@@ -1699,7 +1708,7 @@
"es6-weak-map": ["es6-weak-map@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.46", "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.1" } }, "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA=="], "es6-weak-map": ["es6-weak-map@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.46", "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.1" } }, "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA=="],
"esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="], "esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
"esbuild-android-64": ["esbuild-android-64@0.15.18", "", { "os": "android", "cpu": "x64" }, "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA=="], "esbuild-android-64": ["esbuild-android-64@0.15.18", "", { "os": "android", "cpu": "x64" }, "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA=="],
@@ -1773,6 +1782,8 @@
"express": ["express@5.0.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.0.1", "content-disposition": "^1.0.0", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "^1.2.1", "debug": "4.3.6", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "^2.0.0", "fresh": "2.0.0", "http-errors": "2.0.0", "merge-descriptors": "^2.0.0", "methods": "~1.1.2", "mime-types": "^3.0.0", "on-finished": "2.4.1", "once": "1.4.0", "parseurl": "~1.3.3", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "router": "^2.0.0", "safe-buffer": "5.2.1", "send": "^1.1.0", "serve-static": "^2.1.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "^2.0.0", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ=="], "express": ["express@5.0.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.0.1", "content-disposition": "^1.0.0", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "^1.2.1", "debug": "4.3.6", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "^2.0.0", "fresh": "2.0.0", "http-errors": "2.0.0", "merge-descriptors": "^2.0.0", "methods": "~1.1.2", "mime-types": "^3.0.0", "on-finished": "2.4.1", "once": "1.4.0", "parseurl": "~1.3.3", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "router": "^2.0.0", "safe-buffer": "5.2.1", "send": "^1.1.0", "serve-static": "^2.1.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "^2.0.0", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ=="],
"exsolve": ["exsolve@1.0.4", "", {}, "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw=="],
"ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="], "ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="],
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
@@ -2281,7 +2292,7 @@
"min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
"miniflare": ["miniflare@3.20250214.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250214.0", "ws": "8.18.0", "youch": "3.2.3", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-t+lT4p2lbOcKv4PS3sx1F/wcDAlbEYZCO2VooLp4H7JErWWYIi9yjD3UillC3CGOpiBahVg5nrPCoFltZf6UlA=="], "miniflare": ["miniflare@4.20250409.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250409.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-Hu02dYZvFR+MyrI57O6rSrOUTofcO9EIvcodgq2SAHzAeWSJw2E0oq9lylOrcckFwPMcwxUAb/cQN1LIoCyySw=="],
"minimatch": ["minimatch@10.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ=="], "minimatch": ["minimatch@10.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ=="],
@@ -2351,7 +2362,7 @@
"obliterator": ["obliterator@1.6.1", "", {}, "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig=="], "obliterator": ["obliterator@1.6.1", "", {}, "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig=="],
"ohash": ["ohash@1.1.4", "", {}, "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g=="], "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
@@ -2363,7 +2374,7 @@
"oniguruma-to-es": ["oniguruma-to-es@4.1.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "oniguruma-parser": "^0.5.4", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-SNwG909cSLo4vPyyPbU/VJkEc9WOXqu2ycBlfd1UCXLqk1IijcQktSBb2yRQ2UFPsDhpkaf+C1dtT3PkLK/yWA=="], "oniguruma-to-es": ["oniguruma-to-es@4.1.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "oniguruma-parser": "^0.5.4", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-SNwG909cSLo4vPyyPbU/VJkEc9WOXqu2ycBlfd1UCXLqk1IijcQktSBb2yRQ2UFPsDhpkaf+C1dtT3PkLK/yWA=="],
"openapi-fetch": ["openapi-fetch@0.13.4", "", { "dependencies": { "openapi-typescript-helpers": "^0.0.15" } }, "sha512-JHX7UYjLEiHuQGCPxa3CCCIqe/nc4bTIF9c4UYVC8BegAbWoS3g4gJxKX5XcG7UtYQs2060kY6DH64KkvNZahg=="], "openapi-fetch": ["openapi-fetch@0.13.5", "", { "dependencies": { "openapi-typescript-helpers": "^0.0.15" } }, "sha512-AQK8T9GSKFREFlN1DBXTYsLjs7YV2tZcJ7zUWxbjMoQmj8dDSFRrzhLCbHPZWA1TMV3vACqfCxLEZcwf2wxV6Q=="],
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
@@ -2429,7 +2440,7 @@
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
"pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"pcre-to-regexp": ["pcre-to-regexp@1.1.0", "", {}, "sha512-KF9XxmUQJ2DIlMj3TqNqY1AWvyvTuIuq11CuuekxyaYMiFuMKGgQrePYMX5bXKLhLG3sDI4CsGAYHPaT7VV7+g=="], "pcre-to-regexp": ["pcre-to-regexp@1.1.0", "", {}, "sha512-KF9XxmUQJ2DIlMj3TqNqY1AWvyvTuIuq11CuuekxyaYMiFuMKGgQrePYMX5bXKLhLG3sDI4CsGAYHPaT7VV7+g=="],
@@ -2817,7 +2828,7 @@
"undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], "undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
"unenv": ["unenv@2.0.0-rc.1", "", { "dependencies": { "defu": "^6.1.4", "mlly": "^1.7.4", "ohash": "^1.1.4", "pathe": "^1.1.2", "ufo": "^1.5.4" } }, "sha512-PU5fb40H8X149s117aB4ytbORcCvlASdtF97tfls4BPIyj4PeVxvpSuy1jAptqYHqB0vb2w2sHvzM0XWcp2OKg=="], "unenv": ["unenv@2.0.0-rc.15", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.5.4" } }, "sha512-J/rEIZU8w6FOfLNz/hNKsnY+fFHWnu9MH4yRbSZF3xbbGHovcetXPs7sD+9p8L6CeNC//I9bhRYAOsBt2u7/OA=="],
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
@@ -2907,9 +2918,9 @@
"widest-line": ["widest-line@3.1.0", "", { "dependencies": { "string-width": "^4.0.0" } }, "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg=="], "widest-line": ["widest-line@3.1.0", "", { "dependencies": { "string-width": "^4.0.0" } }, "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg=="],
"workerd": ["workerd@1.20250214.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250214.0", "@cloudflare/workerd-darwin-arm64": "1.20250214.0", "@cloudflare/workerd-linux-64": "1.20250214.0", "@cloudflare/workerd-linux-arm64": "1.20250214.0", "@cloudflare/workerd-windows-64": "1.20250214.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-QWcqXZLiMpV12wiaVnb3nLmfs/g4ZsFQq2mX85z546r3AX4CTIkXl0VP50W3CwqLADej3PGYiRDOTelDOwVG1g=="], "workerd": ["workerd@1.20250409.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250409.0", "@cloudflare/workerd-darwin-arm64": "1.20250409.0", "@cloudflare/workerd-linux-64": "1.20250409.0", "@cloudflare/workerd-linux-arm64": "1.20250409.0", "@cloudflare/workerd-windows-64": "1.20250409.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-hqjX9swiHvrkOI3jlH9lrZsZRRv9lddUwcMe8Ua76jnyQz+brybWznNjHu8U5oswwcrFwvky1A4CcLjcLY31gQ=="],
"wrangler": ["wrangler@3.112.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20250214.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.1", "workerd": "1.20250214.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250214.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-PNQWGze3ODlWwG33LPr8kNhbht3eB3L9fogv+fapk2fjaqj0kNweRapkwmvtz46ojcqWzsxmTe4nOC0hIVUfPA=="], "wrangler": ["wrangler@4.10.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.1", "blake3-wasm": "2.1.5", "esbuild": "0.24.2", "miniflare": "4.20250409.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.15", "workerd": "1.20250409.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250409.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-fTE4hZ79msEUt8+HEjl/8Q72haCyzPLu4PgrU3L81ysmjrMEdiYfUPqnvCkBUVtJvrDNdctTEimkufT1Y0ipNg=="],
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
@@ -2941,7 +2952,7 @@
"yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="], "yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="],
"youch": ["youch@3.2.3", "", { "dependencies": { "cookie": "^0.5.0", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw=="], "youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="],
"zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="], "zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="],
@@ -3555,6 +3566,8 @@
"@cloudflare/next-on-pages/miniflare": ["miniflare@3.20241018.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "^8.8.0", "acorn-walk": "^8.2.0", "capnp-ts": "^0.7.0", "exit-hook": "^2.2.1", "glob-to-regexp": "^0.4.1", "stoppable": "^1.1.0", "undici": "^5.28.4", "workerd": "1.20241018.1", "ws": "^8.17.1", "youch": "^3.2.2", "zod": "^3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-g7i5oGAoJOk8+hJp77A5/wAdu7PEvi5hQc+0wzwzjhUNM2I5DHd2Cc29ACPhAe1kIXvCCVkxs3+REF52qnX0aw=="], "@cloudflare/next-on-pages/miniflare": ["miniflare@3.20241018.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "^8.8.0", "acorn-walk": "^8.2.0", "capnp-ts": "^0.7.0", "exit-hook": "^2.2.1", "glob-to-regexp": "^0.4.1", "stoppable": "^1.1.0", "undici": "^5.28.4", "workerd": "1.20241018.1", "ws": "^8.17.1", "youch": "^3.2.2", "zod": "^3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-g7i5oGAoJOk8+hJp77A5/wAdu7PEvi5hQc+0wzwzjhUNM2I5DHd2Cc29ACPhAe1kIXvCCVkxs3+REF52qnX0aw=="],
"@cloudflare/next-on-pages/wrangler": ["wrangler@3.112.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20250214.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.1", "workerd": "1.20250214.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250214.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-PNQWGze3ODlWwG33LPr8kNhbht3eB3L9fogv+fapk2fjaqj0kNweRapkwmvtz46ojcqWzsxmTe4nOC0hIVUfPA=="],
"@codemirror/lang-html/@codemirror/autocomplete": ["@codemirror/autocomplete@6.18.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA=="], "@codemirror/lang-html/@codemirror/autocomplete": ["@codemirror/autocomplete@6.18.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA=="],
"@codemirror/lang-html/@codemirror/lang-css": ["@codemirror/lang-css@6.3.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/css": "^1.1.7" } }, "sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA=="], "@codemirror/lang-html/@codemirror/lang-css": ["@codemirror/lang-css@6.3.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/css": "^1.1.7" } }, "sha512-CyR4rUNG9OYcXDZwMPvJdtb6PHbBDKUc/6Na2BIwZ6dKab1JQqKa4di+RNRY9Myn7JB81vayKwJeQ7jEdmNVDA=="],
@@ -4017,7 +4030,7 @@
"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=="], "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.3.0-canary.43", "", { "dependencies": { "@next/env": "15.3.0-canary.43", "@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.3.0-canary.43", "@next/swc-darwin-x64": "15.3.0-canary.43", "@next/swc-linux-arm64-gnu": "15.3.0-canary.43", "@next/swc-linux-arm64-musl": "15.3.0-canary.43", "@next/swc-linux-x64-gnu": "15.3.0-canary.43", "@next/swc-linux-x64-musl": "15.3.0-canary.43", "@next/swc-win32-arm64-msvc": "15.3.0-canary.43", "@next/swc-win32-x64-msvc": "15.3.0-canary.43", "sharp": "^0.34.1" }, "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-am6xpZIx2P0VJ26N7K2CImmznYUP65XS0e0nkYtypWf/RiMsScwmCqrA4qrEK9u/tiPlA+583IcQPos9yKLg1Q=="], "gitbook-v2/next": ["next@15.3.1-canary.7", "", { "dependencies": { "@next/env": "15.3.1-canary.7", "@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.3.1-canary.7", "@next/swc-darwin-x64": "15.3.1-canary.7", "@next/swc-linux-arm64-gnu": "15.3.1-canary.7", "@next/swc-linux-arm64-musl": "15.3.1-canary.7", "@next/swc-linux-x64-gnu": "15.3.1-canary.7", "@next/swc-linux-x64-musl": "15.3.1-canary.7", "@next/swc-win32-arm64-msvc": "15.3.1-canary.7", "@next/swc-win32-x64-msvc": "15.3.1-canary.7", "sharp": "^0.34.1" }, "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-RblvC7A7k8cKpSa6JSCbaatb6JAncv2niUSH7TqXt6xHUoItnxa3Fu7xkducFJQdcW2Hv2h0+NBu6kaKf0S4zw=="],
"global-dirs/ini": ["ini@1.3.7", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="], "global-dirs/ini": ["ini@1.3.7", "", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="],
@@ -4087,8 +4100,6 @@
"mlly/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], "mlly/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"mlly/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"next/@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="], "next/@swc/helpers": ["@swc/helpers@0.5.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A=="],
"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=="], "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=="],
@@ -4103,8 +4114,6 @@
"path-scurry/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="], "path-scurry/lru-cache": ["lru-cache@11.0.2", "", {}, "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA=="],
"pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
"postcss/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="], "postcss/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
@@ -4205,6 +4214,8 @@
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], "wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"youch/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
"@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw=="], "@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=="], "@aws-crypto/crc32c/@aws-sdk/types/@smithy/types": ["@smithy/types@4.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw=="],
@@ -4639,10 +4650,18 @@
"@cloudflare/next-on-pages/miniflare/workerd": ["workerd@1.20241018.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20241018.1", "@cloudflare/workerd-darwin-arm64": "1.20241018.1", "@cloudflare/workerd-linux-64": "1.20241018.1", "@cloudflare/workerd-linux-arm64": "1.20241018.1", "@cloudflare/workerd-windows-64": "1.20241018.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-JPW2oAbYOnJj1c5boyDOdjl/Yvur45jhVE8lf+I9oxR6myyAvuH2tdXO62kye68jRluJOMUeyssLes+JRwLmaA=="], "@cloudflare/next-on-pages/miniflare/workerd": ["workerd@1.20241018.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20241018.1", "@cloudflare/workerd-darwin-arm64": "1.20241018.1", "@cloudflare/workerd-linux-64": "1.20241018.1", "@cloudflare/workerd-linux-arm64": "1.20241018.1", "@cloudflare/workerd-windows-64": "1.20241018.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-JPW2oAbYOnJj1c5boyDOdjl/Yvur45jhVE8lf+I9oxR6myyAvuH2tdXO62kye68jRluJOMUeyssLes+JRwLmaA=="],
"@cloudflare/next-on-pages/miniflare/youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="],
"@cloudflare/next-on-pages/miniflare/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="], "@cloudflare/next-on-pages/miniflare/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
"@cloudflare/next-on-pages/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="],
"@cloudflare/next-on-pages/wrangler/esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="],
"@cloudflare/next-on-pages/wrangler/miniflare": ["miniflare@3.20250214.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250214.0", "ws": "8.18.0", "youch": "3.2.3", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-t+lT4p2lbOcKv4PS3sx1F/wcDAlbEYZCO2VooLp4H7JErWWYIi9yjD3UillC3CGOpiBahVg5nrPCoFltZf6UlA=="],
"@cloudflare/next-on-pages/wrangler/unenv": ["unenv@2.0.0-rc.1", "", { "dependencies": { "defu": "^6.1.4", "mlly": "^1.7.4", "ohash": "^1.1.4", "pathe": "^1.1.2", "ufo": "^1.5.4" } }, "sha512-PU5fb40H8X149s117aB4ytbORcCvlASdtF97tfls4BPIyj4PeVxvpSuy1jAptqYHqB0vb2w2sHvzM0XWcp2OKg=="],
"@cloudflare/next-on-pages/wrangler/workerd": ["workerd@1.20250214.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250214.0", "@cloudflare/workerd-darwin-arm64": "1.20250214.0", "@cloudflare/workerd-linux-64": "1.20250214.0", "@cloudflare/workerd-linux-arm64": "1.20250214.0", "@cloudflare/workerd-windows-64": "1.20250214.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-QWcqXZLiMpV12wiaVnb3nLmfs/g4ZsFQq2mX85z546r3AX4CTIkXl0VP50W3CwqLADej3PGYiRDOTelDOwVG1g=="],
"@codemirror/lang-json/@codemirror/language/@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=="], "@codemirror/lang-json/@codemirror/language/@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=="],
"@codemirror/lang-json/@codemirror/language/@lezer/common": ["@lezer/common@1.2.2", "", {}, "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw=="], "@codemirror/lang-json/@codemirror/language/@lezer/common": ["@lezer/common@1.2.2", "", {}, "sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw=="],
@@ -4885,23 +4904,23 @@
"gaxios/https-proxy-agent/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], "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.3.0-canary.43", "", {}, "sha512-ogryUGufxhtidxr/42NY6+ADlvxt9Hq6Q/DOMS/20vGuePZ70wO6Z5m1RP3Q397RGgpe1gSWyYdlg+Mt7H4KeQ=="], "gitbook-v2/next/@next/env": ["@next/env@15.3.1-canary.7", "", {}, "sha512-QSzAlzxaGrQYj3Nzgxd35WM6pmxmNk9lOCn0nsNLPidvc3H6wj/fV3Pl8/tVCYQP61luGuA0Ib0VwZc8ja119w=="],
"gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.0-canary.43", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eXrs8CyIBgdnMTNjku+h/xm61gmRCcKAT+tM2CjpEXbEqXBux5hRIakOk5kJJDu2fA2P3pzQGt5PRD1hg4srXA=="], "gitbook-v2/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.3.1-canary.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fs7JYU0MKtpgsDBIvbT/wn1MwKP8C+bqcb6IjWnDtVcciFKzYDs/tPCBW6CtYg1wpHqm9horZDA1ytb/sAghxA=="],
"gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.0-canary.43", "", { "os": "darwin", "cpu": "x64" }, "sha512-iRGvblEh/b2grxkkp9pT+yea9EzGNM4tLyUZoCzkejkU2jMLsn2DH6h3bQwCfEYZL3YFGsYmVISrVCOVi8LeMw=="], "gitbook-v2/next/@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.3.1-canary.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-Efl6es/IEXJf5groF78fc1PrTEwK9bOyi6fRjp1bsa/AWOHp8rTc3l0T6EeY5T0SWeYilisZZa9rax7nTgw4bw=="],
"gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.0-canary.43", "", { "os": "linux", "cpu": "arm64" }, "sha512-IqknHGNxpL03uIutIuv7FPjGHuD/AnJVC5exi5g+C7P3f6JVvOjFLS264eqi91tVCXhN2LpcKNGwTlK81bJVVg=="], "gitbook-v2/next/@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.3.1-canary.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-IS71I8z2wlecRZRHXqWTtxVozZt1h9IH/2MX1P/WkCjQH98acFmCkw0jo/y70mrZ4lw6tRGTNkSBMun+5P5P+w=="],
"gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.0-canary.43", "", { "os": "linux", "cpu": "arm64" }, "sha512-FbO0dnFsNe3f1LWTn4vyXTWTSrZdwNRnURYExSQ+0AINHphNfwKQNrPqVLrapQ9CAOCOz8R5p9Kf1++IsH3JJQ=="], "gitbook-v2/next/@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.3.1-canary.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-uJ4env9fZxIFM+aRBCjNeG91HHZ2cRXiF25xJqGmCC1Y5jT5oYAB/tIBCFRSj6NRzdt4pZAamCMRS41WzzVVUQ=="],
"gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.0-canary.43", "", { "os": "linux", "cpu": "x64" }, "sha512-MAaLEm8eO6Xir3YC3xLYDHDjLGogAAGRrxhuflvaqAtGQZ6NIMG4YjvAyramYTq/SwrUIDobggKxdQLtu8/pPQ=="], "gitbook-v2/next/@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.3.1-canary.7", "", { "os": "linux", "cpu": "x64" }, "sha512-/+efTL2TxoSAYCxdf+g6WvDKIJDoaETvF3I+lxpixhctfrIwpr2qnG0e6u2M55k1iYiiyFIOA63fpIsGnIMJZA=="],
"gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.0-canary.43", "", { "os": "linux", "cpu": "x64" }, "sha512-gdwF79/EQjY3zgcolO0jlDe0yfII9tXyXQeqL+uvzA8gZT5FpH0KkwSWzxj8EUswWzZcprbDa87sq8H0Eo+whw=="], "gitbook-v2/next/@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.3.1-canary.7", "", { "os": "linux", "cpu": "x64" }, "sha512-Hb+agfULOjEhJAmq/r9OTVAo8KxFlnuCT7BEUbSuVP/Gxn883al6juSG7ZZfk7P4a+ZU6iVYyHKIFAKCarZmUA=="],
"gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.0-canary.43", "", { "os": "win32", "cpu": "arm64" }, "sha512-5WYne3jvo1478kUfe901wFxvPMdC8tRKundKIgU5Upe1HafMMS7ymm1hQ7CUpp3/1vY/R1TV1oKHHJfqDubiNg=="], "gitbook-v2/next/@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.3.1-canary.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-gNd5Vq7k9xIEQj85JoavHGRsyvu1zKOjVoPUYtsZRxXw6kcBg84x2g8NeT/3SmwH1PiFU/sjQXtRzr2Kuc2j3A=="],
"gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.0-canary.43", "", { "os": "win32", "cpu": "x64" }, "sha512-xE3WZhwjb91eezecVsmXn/OtdISfMsIfS3t0ZXsS/+bMvO/LZLdcVBtl0Zy5yR+XJyKfXXmwpdYbL6WH4dGuQg=="], "gitbook-v2/next/@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.3.1-canary.7", "", { "os": "win32", "cpu": "x64" }, "sha512-D5i+KinvNNmspXfgIvEZ1RFOKVhOgdtoSC7/xEUm55hEkcWFVHOtVVPvgV7WUeo1elNxbgxytzmyKbLaifP03g=="],
"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/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=="],
@@ -5101,7 +5120,71 @@
"@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20241018.1", "", { "os": "win32", "cpu": "x64" }, "sha512-qwDVh/KrwEPY82h6tZ1O4BXBAKeGy30BeTr9wvTUVeY9eX/KT73GuEG+ttwiashRfqjOa0Gcqjsfpd913ITFyg=="], "@cloudflare/next-on-pages/miniflare/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20241018.1", "", { "os": "win32", "cpu": "x64" }, "sha512-qwDVh/KrwEPY82h6tZ1O4BXBAKeGy30BeTr9wvTUVeY9eX/KT73GuEG+ttwiashRfqjOa0Gcqjsfpd913ITFyg=="],
"@cloudflare/next-on-pages/miniflare/youch/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], "@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.17.19", "", { "os": "android", "cpu": "arm64" }, "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.17.19", "", { "os": "android", "cpu": "x64" }, "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.17.19", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.17.19", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.17.19", "", { "os": "linux", "cpu": "arm" }, "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.17.19", "", { "os": "linux", "cpu": "ia32" }, "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.17.19", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.17.19", "", { "os": "linux", "cpu": "s390x" }, "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.17.19", "", { "os": "none", "cpu": "x64" }, "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.17.19", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.17.19", "", { "os": "sunos", "cpu": "x64" }, "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.17.19", "", { "os": "win32", "cpu": "ia32" }, "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw=="],
"@cloudflare/next-on-pages/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA=="],
"@cloudflare/next-on-pages/wrangler/miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
"@cloudflare/next-on-pages/wrangler/miniflare/undici": ["undici@5.28.5", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA=="],
"@cloudflare/next-on-pages/wrangler/miniflare/youch": ["youch@3.2.3", "", { "dependencies": { "cookie": "^0.5.0", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-ZBcWz/uzZaQVdCvfV4uk616Bbpf2ee+F/AvuKDR5EwX/Y4v06xWdtMluqTD7+KlZdM93lLm9gMZYo0sKBS0pgw=="],
"@cloudflare/next-on-pages/wrangler/miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
"@cloudflare/next-on-pages/wrangler/unenv/ohash": ["ohash@1.1.4", "", {}, "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g=="],
"@cloudflare/next-on-pages/wrangler/unenv/pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
"@cloudflare/next-on-pages/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250214.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-cDvvedWDc5zrgDnuXe2qYcz/TwBvzmweO55C7XpPuAWJ9Oqxv81PkdekYxD8mH989aQ/GI5YD0Fe6fDYlM+T3Q=="],
"@cloudflare/next-on-pages/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250214.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-NytCvRveVzu0mRKo+tvZo3d/gCUway3B2ZVqSi/TS6NXDGBYIJo7g6s3BnTLS74kgyzeDOjhu9j/RBJBS809qw=="],
"@cloudflare/next-on-pages/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250214.0", "", { "os": "linux", "cpu": "x64" }, "sha512-pQ7+aHNHj8SiYEs4d/6cNoimE5xGeCMfgU1yfDFtA9YGN9Aj2BITZgOWPec+HW7ZkOy9oWlNrO6EvVjGgB4tbQ=="],
"@cloudflare/next-on-pages/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250214.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Vhlfah6Yd9ny1npNQjNgElLIjR6OFdEbuR3LCfbLDCwzWEBFhIf7yC+Tpp/a0Hq7kLz3sLdktaP7xl3PJhyOjA=="],
"@cloudflare/next-on-pages/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250214.0", "", { "os": "win32", "cpu": "x64" }, "sha512-GMwMyFbkjBKjYJoKDhGX8nuL4Gqe3IbVnVWf2Q6086CValyIknupk5J6uQWGw2EBU3RGO3x4trDXT5WphQJZDQ=="],
"@node-minify/core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "@node-minify/core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+2 -2
View File
@@ -21,10 +21,10 @@
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.5.3", "typescript": "^5.5.3",
"wrangler": "^3.112.0" "wrangler": "^4.10.0"
}, },
"scripts": { "scripts": {
"generate": "wrangler types --experimental-include-runtime", "generate": "wrangler types",
"build": "tsc", "build": "tsc",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"dev": "tsc -w", "dev": "tsc -w",
+1 -1
View File
@@ -14,7 +14,7 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"incremental": true, "incremental": true,
"types": ["./.wrangler/types/runtime.d.ts"] "types": ["./worker-configuration.d.ts"]
}, },
"include": ["src/**/*.ts"], "include": ["src/**/*.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
+6
View File
@@ -1,5 +1,11 @@
# @gitbook/cache-tags # @gitbook/cache-tags
## 0.3.1
### Patch Changes
- 77397ca: Fix version of @gitbook/api referenced in package.json
## 0.3.0 ## 0.3.0
### Minor Changes ### Minor Changes
+2 -2
View File
@@ -8,9 +8,9 @@
"default": "./dist/index.js" "default": "./dist/index.js"
} }
}, },
"version": "0.3.0", "version": "0.3.1",
"dependencies": { "dependencies": {
"@gitbook/api": "*", "@gitbook/api": "^0.109.0",
"assert-never": "^1.2.1" "assert-never": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
+7
View File
@@ -1,5 +1,12 @@
# gitbook-v2 # gitbook-v2
## 0.2.5
### Patch Changes
- Updated dependencies [77397ca]
- @gitbook/cache-tags@0.3.1
## 0.2.4 ## 0.2.4
### Patch Changes ### Patch Changes
+20 -4
View File
@@ -7,10 +7,7 @@ const nextConfig = {
experimental: { experimental: {
// This is needed to throw "forbidden" when the api token expired during revalidation // This is needed to throw "forbidden" when the api token expired during revalidation
authInterrupts: true, authInterrupts: true,
useCache: true,
// We don't use 'use cache' as it's not supported in OpenNext yet
// and instead we scope `unstable_cache` to the parameters of the function
useCache: false,
// Content is fully static, we can cache it in the session memory cache for a long time // Content is fully static, we can cache it in the session memory cache for a long time
staleTimes: { staleTimes: {
@@ -34,6 +31,7 @@ const nextConfig = {
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX, GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
GITBOOK_SECRET: process.env.GITBOOK_SECRET, GITBOOK_SECRET: process.env.GITBOOK_SECRET,
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY, GITBOOK_IMAGE_RESIZE_SIGNING_KEY: process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY,
GITBOOK_IMAGE_RESIZE_MODE: process.env.GITBOOK_IMAGE_RESIZE_MODE,
GITBOOK_FONTS_URL: process.env.GITBOOK_FONTS_URL, GITBOOK_FONTS_URL: process.env.GITBOOK_FONTS_URL,
GITBOOK_RUNTIME: process.env.GITBOOK_RUNTIME, GITBOOK_RUNTIME: process.env.GITBOOK_RUNTIME,
@@ -55,6 +53,24 @@ const nextConfig = {
}, },
], ],
}, },
async headers() {
return [
{
source: '/~gitbook/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
{
key: 'Access-Control-Allow-Origin',
value: '*',
},
],
},
];
},
}; };
export default nextConfig; export default nextConfig;
+4 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "gitbook-v2", "name": "gitbook-v2",
"version": "0.2.4", "version": "0.2.5",
"private": true, "private": true,
"dependencies": { "dependencies": {
"next": "canary", "next": "canary",
@@ -12,11 +12,12 @@
"server-only": "^0.0.1", "server-only": "^0.0.1",
"warn-once": "^0.1.1", "warn-once": "^0.1.1",
"rison": "^0.1.1", "rison": "^0.1.1",
"jwt-decode": "^4.0.0" "jwt-decode": "^4.0.0",
"assert-never": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"gitbook": "*", "gitbook": "*",
"@opennextjs/cloudflare": "https://pkg.pr.new/opennextjs/opennextjs-cloudflare/@opennextjs/cloudflare@7b3b305", "@opennextjs/cloudflare": "^1.0.0-beta.3",
"@types/rison": "^0.0.9", "@types/rison": "^0.0.9",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
"postcss": "^8" "postcss": "^8"
File diff suppressed because it is too large Load Diff
@@ -98,6 +98,10 @@ export async function wrapDataFetcherError<T>(
*/ */
export function getExposableError(error: Error): DataFetcherErrorData { export function getExposableError(error: Error): DataFetcherErrorData {
if (error instanceof GitBookAPIError) { if (error instanceof GitBookAPIError) {
if (error.code >= 500) {
throw error;
}
return { return {
code: error.code, code: error.code,
message: error.errorMessage, message: error.errorMessage,
@@ -105,6 +109,10 @@ export function getExposableError(error: Error): DataFetcherErrorData {
} }
if (error instanceof DataFetcherError) { if (error instanceof DataFetcherError) {
if (error.code >= 500) {
throw error;
}
return { return {
code: error.code, code: error.code,
message: error.message, message: error.message,
@@ -4,7 +4,7 @@ import { withCacheKey, withoutConcurrentExecution } from './memoize';
describe('withoutConcurrentExecution', () => { describe('withoutConcurrentExecution', () => {
it('should memoize the function based on the cache key', async () => { it('should memoize the function based on the cache key', async () => {
const fn = mock(async (a: number, b: number) => a + b); const fn = mock(async (_cacheKey: string, a: number, b: number) => a + b);
const memoized = withoutConcurrentExecution(() => null, fn); const memoized = withoutConcurrentExecution(() => null, fn);
const p1 = memoized('c1', 1, 2); const p1 = memoized('c1', 1, 2);
+2 -2
View File
@@ -4,7 +4,7 @@
*/ */
export function withoutConcurrentExecution<ArgsType extends any[], ReturnType>( export function withoutConcurrentExecution<ArgsType extends any[], ReturnType>(
getGlobalContext: () => object | null | undefined, getGlobalContext: () => object | null | undefined,
wrapped: (...args: ArgsType) => Promise<ReturnType> wrapped: (key: string, ...args: ArgsType) => Promise<ReturnType>
): (cacheKey: string, ...args: ArgsType) => Promise<ReturnType> { ): (cacheKey: string, ...args: ArgsType) => Promise<ReturnType> {
const globalPromiseCache = new WeakMap<object, Map<string, Promise<ReturnType>>>(); const globalPromiseCache = new WeakMap<object, Map<string, Promise<ReturnType>>>();
@@ -26,7 +26,7 @@ export function withoutConcurrentExecution<ArgsType extends any[], ReturnType>(
const promise = (async () => { const promise = (async () => {
try { try {
const result = await wrapped(...args); const result = await wrapped(key, ...args);
return result; return result;
} finally { } finally {
promiseCache.delete(key); promiseCache.delete(key);
+18
View File
@@ -85,6 +85,15 @@ export const GITBOOK_IMAGE_RESIZE_URL = process.env.GITBOOK_IMAGE_RESIZE_URL ??
export const GITBOOK_IMAGE_RESIZE_SIGNING_KEY = export const GITBOOK_IMAGE_RESIZE_SIGNING_KEY =
process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY ?? null; process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY ?? null;
/**
* Mode used for resizing images.
*/
export const GITBOOK_IMAGE_RESIZE_MODE = enforceEnum(
'GITBOOK_IMAGE_RESIZE_MODE',
process.env.GITBOOK_IMAGE_RESIZE_MODE || 'cdn-cgi',
['cdn-cgi', 'cf-fetch']
);
/** /**
* Endpoint where icons are served. * Endpoint where icons are served.
*/ */
@@ -100,3 +109,12 @@ export const GITBOOK_ICONS_TOKEN = process.env.GITBOOK_ICONS_TOKEN;
* Secret used to validate requests from the GitBook app. * Secret used to validate requests from the GitBook app.
*/ */
export const GITBOOK_SECRET = process.env.GITBOOK_SECRET ?? null; export const GITBOOK_SECRET = process.env.GITBOOK_SECRET ?? null;
function enforceEnum<T extends string>(key: string, value: string, enumValues: T[]): T {
if (!enumValues.includes(value as T)) {
throw new Error(
`Invalid value for ${key}: "${value}", expected one of: ${enumValues.join(', ')}`
);
}
return value as T;
}
@@ -0,0 +1,27 @@
import { checkIsHttpURL } from '@/lib/urls';
/**
* Check if an image URL is resizable.
* Skip it for non-http(s) URLs (data, etc).
* Skip it for SVGs.
* Skip it for GitBook images (to avoid recursion).
*/
export function checkIsSizableImageURL(input: string): boolean {
if (!URL.canParse(input)) {
return false;
}
if (input.includes('/~gitbook/image')) {
return false;
}
const parsed = new URL(input);
if (parsed.pathname.endsWith('.svg') || parsed.pathname.endsWith('.avif')) {
return false;
}
if (!checkIsHttpURL(parsed)) {
return false;
}
return true;
}
@@ -1,34 +1,11 @@
import 'server-only'; import 'server-only';
import { GITBOOK_IMAGE_RESIZE_SIGNING_KEY, GITBOOK_IMAGE_RESIZE_URL } from '../env'; import { GITBOOK_IMAGE_RESIZE_SIGNING_KEY, GITBOOK_IMAGE_RESIZE_URL } from '../env';
import type { GitBookLinker } from '../links'; import type { GitBookLinker } from '../links';
import { checkIsSizableImageURL } from './checkIsSizableImageURL';
import { getImageSize } from './resizer';
import { type SignatureVersion, generateImageSignature } from './signatures'; import { type SignatureVersion, generateImageSignature } from './signatures';
import type { ImageResizer } from './types'; import type { ImageResizer } from './types';
interface CloudflareImageJsonFormat {
width: number;
height: number;
original: {
file_size: number;
width: number;
height: number;
format: string;
};
}
/**
* https://developers.cloudflare.com/images/image-resizing/resize-with-workers/
*/
export interface CloudflareImageOptions {
format?: 'webp' | 'avif' | 'json' | 'jpeg';
fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
width?: number;
height?: number;
dpr?: number;
anim?: boolean;
quality?: number;
}
/** /**
* Create an image resizer for a rendering context. * Create an image resizer for a rendering context.
*/ */
@@ -106,124 +83,6 @@ export function createNoopImageResizer(): ImageResizer {
}; };
} }
/**
* Check if a URL is an HTTP URL.
*/
export function checkIsHttpURL(input: string | URL): boolean {
if (!URL.canParse(input)) {
return false;
}
const parsed = new URL(input);
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
}
/**
* Check if an image URL is resizable.
* Skip it for non-http(s) URLs (data, etc).
* Skip it for SVGs.
* Skip it for GitBook images (to avoid recursion).
*/
export function checkIsSizableImageURL(input: string): boolean {
if (!URL.canParse(input)) {
return false;
}
if (input.includes('/~gitbook/image')) {
return false;
}
const parsed = new URL(input);
if (parsed.pathname.endsWith('.svg') || parsed.pathname.endsWith('.avif')) {
return false;
}
if (!checkIsHttpURL(parsed)) {
return false;
}
return true;
}
/**
* Get the size of an image.
*/
export async function getImageSize(
input: string,
defaultSize: Partial<CloudflareImageOptions> = {}
): Promise<{ width: number; height: number } | null> {
if (!checkIsSizableImageURL(input)) {
return null;
}
try {
const response = await resizeImage(input, {
// Abort the request after 2 seconds to avoid blocking rendering for too long
signal: AbortSignal.timeout(2000),
// Measure size and resize it to the most common size
// to optimize caching
...defaultSize,
format: 'json',
anim: false,
});
const json = (await response.json()) as CloudflareImageJsonFormat;
return {
width: json.original.width,
height: json.original.height,
};
} catch (_error) {
return null;
}
}
/**
* Execute a Cloudflare Image Resize operation on an image.
*/
export async function resizeImage(
input: string,
options: CloudflareImageOptions & {
signal?: AbortSignal;
}
): Promise<Response> {
const { signal, ...resizeOptions } = options;
const parsed = new URL(input);
if (parsed.protocol === 'data:') {
throw new Error('Cannot resize data: URLs');
}
if (parsed.hostname === 'localhost') {
throw new Error('Cannot resize localhost URLs');
}
// Since Cloudflare Images options on fetch are not supported on Cloudflare Pages,
// we need to use the Cloudflare Image Resize API directly.
if (!GITBOOK_IMAGE_RESIZE_URL) {
throw new Error('GITBOOK_IMAGE_RESIZE_URL is not set');
}
return await fetch(
`${GITBOOK_IMAGE_RESIZE_URL}${stringifyOptions(
resizeOptions
)}/${encodeURIComponent(input)}`,
{
headers: {
// Pass the `Accept` header, as Cloudflare uses this to validate the format.
Accept:
resizeOptions.format === 'json'
? 'application/json'
: `image/${resizeOptions.format || 'jpeg'}`,
},
signal,
}
);
}
function stringifyOptions(options: CloudflareImageOptions): string {
return Object.entries({ ...options }).reduce((rest, [key, value]) => {
return `${rest}${rest ? ',' : ''}${key}=${value}`;
}, '');
}
/** /**
* Because of a bug in Cloudflare, 127.0.0.1 is replaced by localhost. * Because of a bug in Cloudflare, 127.0.0.1 is replaced by localhost.
* We protect against it by converting to a special token, and then parsing * We protect against it by converting to a special token, and then parsing
@@ -3,3 +3,5 @@ export * from './createImageResizer';
export * from './signatures'; export * from './signatures';
export * from './utils'; export * from './utils';
export * from './getImageResizingContextId'; export * from './getImageResizingContextId';
export * from './resizer';
export * from './checkIsSizableImageURL';
@@ -0,0 +1,48 @@
import { GITBOOK_IMAGE_RESIZE_URL } from '@v2/lib/env';
import type { CloudflareImageOptions } from './types';
import { copyImageResponse } from './utils';
/**
* Resize an image by doing a request to a /cdn/cgi/ endpoint.
* https://developers.cloudflare.com/images/transform-images/transform-via-url/
*/
export async function resizeImageWithCDNCgi(
input: string,
options: CloudflareImageOptions & {
signal?: AbortSignal;
}
): Promise<Response> {
const { signal, ...resizeOptions } = options;
// Since Cloudflare Images options on fetch are not supported on Cloudflare Pages,
// we need to use the Cloudflare Image Resize API directly.
if (!GITBOOK_IMAGE_RESIZE_URL) {
throw new Error('GITBOOK_IMAGE_RESIZE_URL is not set for cdn-cgi image resize mode');
}
const resizeURL = `${GITBOOK_IMAGE_RESIZE_URL}${stringifyOptions(
resizeOptions
)}/${encodeURIComponent(input)}`;
// biome-ignore lint/suspicious/noConsole: this log is useful for debugging
console.log(`resize image using cdn-cgi: ${resizeURL}`);
return copyImageResponse(
await fetch(resizeURL, {
headers: {
// Pass the `Accept` header, as Cloudflare uses this to validate the format.
Accept:
resizeOptions.format === 'json'
? 'application/json'
: `image/${resizeOptions.format || 'jpeg'}`,
},
signal,
})
);
}
function stringifyOptions(options: CloudflareImageOptions): string {
return Object.entries({ ...options }).reduce((rest, [key, value]) => {
return `${rest}${rest ? ',' : ''}${key}=${value}`;
}, '');
}
@@ -0,0 +1,34 @@
import type { CloudflareImageOptions } from './types';
import { copyImageResponse } from './utils';
/**
* Resize an image by doing a request to the image itself using the Cloudflare fetch.
* https://developers.cloudflare.com/images/transform-images/transform-via-workers/
*
* This method doesn't work in Cloudflare Pages and is only supported in workers.
*/
export async function resizeImageWithCFFetch(
input: string,
options: CloudflareImageOptions & {
signal?: AbortSignal;
}
): Promise<Response> {
const { signal, ...resizeOptions } = options;
// biome-ignore lint/suspicious/noConsole: this log is useful for debugging
console.log(`resize image using cf-fetch: ${input}`);
return copyImageResponse(
await fetch(input, {
headers: {
// Pass the `Accept` header, as Cloudflare uses this to validate the format.
Accept:
resizeOptions.format === 'json'
? 'application/json'
: `image/${resizeOptions.format || 'jpeg'}`,
},
signal,
cf: { image: resizeOptions },
})
);
}
@@ -0,0 +1,2 @@
export * from './types';
export * from './resizeImage';
@@ -0,0 +1,68 @@
import 'server-only';
import assertNever from 'assert-never';
import { GITBOOK_IMAGE_RESIZE_MODE } from '../../env';
import { checkIsSizableImageURL } from '../checkIsSizableImageURL';
import { resizeImageWithCDNCgi } from './cdn-cgi';
import { resizeImageWithCFFetch } from './cf-fetch';
import type { CloudflareImageJsonFormat, CloudflareImageOptions } from './types';
/**
* Get the size of an image.
*/
export async function getImageSize(
input: string,
defaultSize: Partial<CloudflareImageOptions> = {}
): Promise<{ width: number; height: number } | null> {
if (!checkIsSizableImageURL(input)) {
return null;
}
try {
const response = await resizeImage(input, {
// Abort the request after 2 seconds to avoid blocking rendering for too long
signal: AbortSignal.timeout(2000),
// Measure size and resize it to the most common size
// to optimize caching
...defaultSize,
format: 'json',
anim: false,
});
const json = (await response.json()) as CloudflareImageJsonFormat;
return {
width: json.original.width,
height: json.original.height,
};
} catch (error) {
console.warn(`Error getting image size for ${input}:`, error);
return null;
}
}
/**
* Execute a Cloudflare Image Resize operation on an image.
*/
export async function resizeImage(
input: string,
options: CloudflareImageOptions & {
signal?: AbortSignal;
}
): Promise<Response> {
const parsed = new URL(input);
if (parsed.protocol === 'data:') {
throw new Error('Cannot resize data: URLs');
}
if (parsed.hostname === 'localhost') {
throw new Error('Cannot resize localhost URLs');
}
switch (GITBOOK_IMAGE_RESIZE_MODE) {
case 'cdn-cgi':
return resizeImageWithCDNCgi(input, options);
case 'cf-fetch':
return resizeImageWithCFFetch(input, options);
default:
assertNever(GITBOOK_IMAGE_RESIZE_MODE);
}
}
@@ -0,0 +1,23 @@
export interface CloudflareImageJsonFormat {
width: number;
height: number;
original: {
file_size: number;
width: number;
height: number;
format: string;
};
}
/**
* https://developers.cloudflare.com/images/image-resizing/resize-with-workers/
*/
export interface CloudflareImageOptions {
format?: 'webp' | 'avif' | 'json' | 'jpeg';
fit?: 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
width?: number;
height?: number;
dpr?: number;
anim?: boolean;
quality?: number;
}
@@ -0,0 +1,7 @@
/**
* Copy a response to make sure it can be mutated by the rest of the middleware.
* To avoid errors "Can't modify immutable headers".
*/
export function copyImageResponse(response: Response) {
return new Response(response.body, response);
}
+1 -22
View File
@@ -1,7 +1,7 @@
{ {
"main": ".open-next/worker.js", "main": ".open-next/worker.js",
"name": "gitbook-open-v2", "name": "gitbook-open-v2",
"compatibility_date": "2025-03-11", "compatibility_date": "2025-04-14",
"compatibility_flags": ["nodejs_compat", "allow_importable_env"], "compatibility_flags": ["nodejs_compat", "allow_importable_env"],
"assets": { "assets": {
"directory": ".open-next/assets", "directory": ".open-next/assets",
@@ -12,13 +12,6 @@
}, },
"env": { "env": {
"preview": { "preview": {
"d1_databases": [
{
"binding": "NEXT_CACHE_D1",
"database_id": "f59ddb40-ad72-4312-9395-0ac6a129af8e",
"database_name": "gitbook-open-v2-tags-preview"
}
],
"r2_buckets": [ "r2_buckets": [
{ {
"binding": "NEXT_INC_CACHE_R2_BUCKET", "binding": "NEXT_INC_CACHE_R2_BUCKET",
@@ -55,13 +48,6 @@
"bucket_name": "gitbook-open-v2-cache-staging" "bucket_name": "gitbook-open-v2-cache-staging"
} }
], ],
"d1_databases": [
{
"binding": "NEXT_CACHE_D1",
"database_id": "9df62e39-1f35-4066-83aa-e9b8ed3ac8d5",
"database_name": "gitbook-open-v2-tags-staging"
}
],
"services": [ "services": [
{ {
"binding": "WORKER_SELF_REFERENCE", "binding": "WORKER_SELF_REFERENCE",
@@ -113,13 +99,6 @@
"bucket_name": "gitbook-open-v2-cache-production" "bucket_name": "gitbook-open-v2-cache-production"
} }
], ],
"d1_databases": [
{
"binding": "NEXT_CACHE_D1",
"database_id": "a6f16fce-5f45-43a9-89a4-7b83ddf25b77",
"database_name": "gitbook-open-v2-tags-production"
}
],
"services": [ "services": [
{ {
"binding": "WORKER_SELF_REFERENCE", "binding": "WORKER_SELF_REFERENCE",
+7
View File
@@ -1,5 +1,12 @@
# gitbook # gitbook
## 0.10.1
### Patch Changes
- Updated dependencies [77397ca]
- @gitbook/cache-tags@0.3.1
## 0.10.0 ## 0.10.0
### Minor Changes ### Minor Changes
+47 -34
View File
@@ -219,10 +219,10 @@ export function runTestCases(testCases: TestsCase[]) {
fullPage: testEntry.fullPage ?? false, fullPage: testEntry.fullPage ?? false,
beforeScreenshot: async ({ runStabilization }) => { beforeScreenshot: async ({ runStabilization }) => {
await runStabilization(); await runStabilization();
await waitForIcons(page);
if (screenshotOptions?.waitForTOCScrolling !== false) { if (screenshotOptions?.waitForTOCScrolling !== false) {
await waitForTOCScrolling(page); await waitForTOCScrolling(page);
} }
await waitForIcons(page);
}, },
}); });
} }
@@ -345,61 +345,74 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
*/ */
async function waitForIcons(page: Page) { async function waitForIcons(page: Page) {
await page.waitForFunction(() => { await page.waitForFunction(() => {
const urls = new Set<string>(); const urlStates: Record<string, 'pending' | 'loaded'> =
(window as any).__ICONS_STATES__ || {};
(window as any).__ICONS_STATES__ = urlStates;
const loadUrl = (url: string) => {
// Mark the URL as pending.
urlStates[url] = 'pending';
const img = new Image();
img.onload = () => {
urlStates[url] = 'loaded';
};
img.src = url;
};
const icons = Array.from(document.querySelectorAll('svg.gb-icon')); const icons = Array.from(document.querySelectorAll('svg.gb-icon'));
const results = icons.map((icon) => { const results = icons.map((icon) => {
if (!(icon instanceof SVGElement)) { if (!(icon instanceof SVGElement)) {
throw new Error('Icon is not an 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. // Ignore icons that are not visible.
if (!icon.checkVisibility()) { if (!icon.checkVisibility()) {
return true; return true;
} }
const state = icon.getAttribute('data-argos-state');
if (state === 'pending') {
return false;
}
if (state === 'loaded') {
return true;
}
// url("https://ka-p.fontawesome.com/releases/v6.6.0/svgs/light/moon.svg?v=2&token=a463935e93") // 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 maskImage = window.getComputedStyle(icon).getPropertyValue('mask-image');
const urlMatch = maskImage.match(/url\("([^"]+)"\)/); const urlMatch = maskImage.match(/url\("([^"]+)"\)/);
const url = urlMatch ? urlMatch[1] : null; const url = urlMatch?.[1];
// If URL is invalid we throw an error. // If URL is invalid we throw an error.
if (!url) { if (!url) {
throw new Error('No mask-image'); throw new Error('No mask-image');
} }
// If the URL is already loaded, we just mark it as loaded. // If the URL is already queued for loading, we return the state.
if (urls.has(url)) { if (urlStates[url]) {
icon.dataset.loadingState = 'loaded'; if (urlStates[url] === 'loaded') {
return true; icon.setAttribute('data-argos-state', 'pending');
const bckMaskImage = icon.style.maskImage;
const bckDisplay = icon.style.display;
icon.style.maskImage = '';
icon.style.display = 'none';
requestAnimationFrame(() => {
icon.style.maskImage = bckMaskImage;
icon.style.display = bckDisplay;
requestAnimationFrame(() => {
icon.setAttribute('data-argos-state', 'loaded');
});
});
return false;
}
return false;
} }
// Mark the icon as pending and load the image. loadUrl(url);
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 false;
}); });
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "gitbook", "name": "gitbook",
"version": "0.10.0", "version": "0.10.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "env-cmd --silent -f ../../.env.local next dev", "dev": "env-cmd --silent -f ../../.env.local next dev",
@@ -75,7 +75,7 @@
"zustand": "^5.0.3" "zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@argos-ci/playwright": "^4.3.0", "@argos-ci/playwright": "^5.0.3",
"@cloudflare/next-on-pages": "1.13.7", "@cloudflare/next-on-pages": "1.13.7",
"@cloudflare/workers-types": "^4.20241230.0", "@cloudflare/workers-types": "^4.20241230.0",
"@playwright/test": "^1.51.1", "@playwright/test": "^1.51.1",
+4
View File
@@ -1,3 +1,7 @@
# GitBook immutable static assets # GitBook immutable static assets
# Duplicated from next.config.mjs until OpenNext supports generating static headers
/~gitbook/static/* /~gitbook/static/*
cache-control: public,max-age=31536000,immutable cache-control: public,max-age=31536000,immutable
Access-Control-Allow-Origin: *
/_next/static/*
Access-Control-Allow-Origin: *
@@ -26,6 +26,7 @@ export function ClientCodeBlock(props: ClientBlockProps) {
const [isInViewport, setIsInViewport] = useState(false); const [isInViewport, setIsInViewport] = useState(false);
const plainLines = useMemo(() => plainHighlight(block, []), [block]); const plainLines = useMemo(() => plainHighlight(block, []), [block]);
const [lines, setLines] = useState<null | HighlightLine[]>(null); const [lines, setLines] = useState<null | HighlightLine[]>(null);
const [highlighting, setHighlighting] = useState(false);
// Preload the highlighter when the block is mounted. // Preload the highlighter when the block is mounted.
useEffect(() => { useEffect(() => {
@@ -77,6 +78,7 @@ export function ClientCodeBlock(props: ClientBlockProps) {
let cancelled = false; let cancelled = false;
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
setHighlighting(true);
import('./highlight').then(({ highlight }) => { import('./highlight').then(({ highlight }) => {
highlight(block, inlines).then((lines) => { highlight(block, inlines).then((lines) => {
if (cancelled) { if (cancelled) {
@@ -84,6 +86,7 @@ export function ClientCodeBlock(props: ClientBlockProps) {
} }
setLines(lines); setLines(lines);
setHighlighting(false);
}); });
}); });
} }
@@ -98,6 +101,12 @@ export function ClientCodeBlock(props: ClientBlockProps) {
}, [isInViewport, block, inlines]); }, [isInViewport, block, inlines]);
return ( return (
<CodeBlockRenderer ref={blockRef} block={block} style={style} lines={lines ?? plainLines} /> <CodeBlockRenderer
ref={blockRef}
aria-busy={highlighting}
block={block}
style={style}
lines={lines ?? plainLines}
/>
); );
} }
@@ -14,6 +14,7 @@ import './CodeBlockRenderer.css';
type CodeBlockRendererProps = Pick<BlockProps<DocumentBlockCode>, 'block' | 'style'> & { type CodeBlockRendererProps = Pick<BlockProps<DocumentBlockCode>, 'block' | 'style'> & {
lines: HighlightLine[]; lines: HighlightLine[];
'aria-busy'?: boolean;
}; };
/** /**
@@ -23,7 +24,7 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
props: CodeBlockRendererProps, props: CodeBlockRendererProps,
ref: React.ForwardedRef<HTMLDivElement> ref: React.ForwardedRef<HTMLDivElement>
) { ) {
const { block, style, lines } = props; const { block, style, lines, 'aria-busy': ariaBusy } = props;
const id = useId(); const id = useId();
const withLineNumbers = Boolean(block.data.lineNumbers) && block.nodes.length > 1; const withLineNumbers = Boolean(block.data.lineNumbers) && block.nodes.length > 1;
@@ -31,7 +32,11 @@ export const CodeBlockRenderer = forwardRef(function CodeBlockRenderer(
const title = block.data.title; const title = block.data.title;
return ( return (
<div ref={ref} className={tcls('group/codeblock grid grid-flow-col', style)}> <div
ref={ref}
aria-busy={ariaBusy}
className={tcls('group/codeblock grid grid-flow-col', style)}
>
<div className="flex items-center justify-start gap-2 text-sm [grid-area:1/1]"> <div className="flex items-center justify-start gap-2 text-sm [grid-area:1/1]">
{title ? ( {title ? (
<div className="inline-flex items-center justify-center rounded-t straight-corners:rounded-t-s bg-tint px-3 py-2 text-tint text-xs leading-none tracking-wide"> <div className="inline-flex items-center justify-center rounded-t straight-corners:rounded-t-s bg-tint px-3 py-2 text-tint text-xs leading-none tracking-wide">
@@ -8,6 +8,10 @@
@apply flex flex-col mb-14 flex-1; @apply flex flex-col mb-14 flex-1;
} }
.openapi-schemas-title {
@apply tabular-nums text-[0.813rem] leading-4 font-mono shrink-0 font-medium text-tint-strong;
}
.openapi-columns { .openapi-columns {
@apply grid grid-cols-1 lg:grid-cols-2 gap-6 print-mode:grid-cols-1 justify-stretch; @apply grid grid-cols-1 lg:grid-cols-2 gap-6 print-mode:grid-cols-1 justify-stretch;
} }
@@ -46,10 +50,7 @@
@apply font-semibold font-mono truncate; @apply font-semibold font-mono truncate;
} }
.openapi-description.openapi-markdown { .openapi-description.openapi-markdown,
@apply prose-sm text-[0.938rem];
}
.openapi-description.openapi-markdown code { .openapi-description.openapi-markdown code {
@apply prose-sm; @apply prose-sm;
} }
@@ -89,21 +90,23 @@
} }
/* Method Tags */ /* Method Tags */
.openapi-method { .openapi-method,
@apply rounded uppercase font-mono shrink-0 font-bold text-xs px-1 py-0.5 mr-2 text-tint-12/8 leading-tight align-middle inline-flex ring-1 ring-inset ring-tint-12/1 dark:ring-tint-1/1 whitespace-nowrap; .openapi-statuscode {
@apply rounded uppercase font-mono items-center shrink-0 font-semibold text-[0.813rem] px-1 py-0.5 mr-2 text-tint-12/8 leading-tight align-middle inline-flex ring-1 ring-inset ring-tint-12/1 dark:ring-tint-1/1 whitespace-nowrap;
} }
.openapi-method-get { .openapi-method-get,
/* @apply bg-[hsl(215,54%,86%)] dark:bg-[hsla(215,54%,45%,0.24)] dark:text-[hsl(215,54%,86%)]; */ .openapi-statuscode-success {
@apply bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100; @apply bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100;
} }
.openapi-method-post { .openapi-method-post,
/* @apply bg-[hsl(120,25%,80%)] dark:bg-[hsla(120,54%,32%,0.24)] dark:text-[hsl(120,25%,80%)]; */ .openapi-statuscode-redirection {
@apply bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-100; @apply bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-100;
} }
.openapi-method-put { .openapi-method-put,
.openapi-statuscode-informational {
@apply bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-100; @apply bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-100;
} }
@@ -111,8 +114,9 @@
@apply bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100; @apply bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100;
} }
.openapi-method-delete { .openapi-method-delete,
@apply bg-pomegranate-100 text-pomegranate-800 dark:bg-pomegranate-900 dark:text-pomegranate-100; .openapi-statuscode-error {
@apply bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100;
} }
.openapi-method-head, .openapi-method-head,
@@ -140,7 +144,7 @@
} }
.openapi-column-preview-body { .openapi-column-preview-body {
@apply flex flex-col gap-4 sticky top-4 site-header:top-20 site-header-sections:page-has-toc:top-32 page-api-block:xl:max-2xl:top-32 print-mode:static; @apply flex flex-col gap-4 sticky top-4 site-header:top-20 site-header-sections:top-32 page-api-block:xl:max-2xl:top-32 print-mode:static;
} }
.openapi-column-preview pre { .openapi-column-preview pre {
@@ -157,7 +161,7 @@
} }
.openapi-section-schemas > .openapi-section-body > .openapi-schema-root-description { .openapi-section-schemas > .openapi-section-body > .openapi-schema-root-description {
@apply px-2.5 pt-1 !text-sm; @apply px-2.5 pt-2.5 mt-0 !text-sm;
} }
.openapi-schema-properties { .openapi-schema-properties {
@@ -360,7 +364,7 @@
/* Responses */ /* Responses */
.openapi-responses-header { .openapi-responses-header {
@apply py-2 border-b border-tint-subtle max-w-full flex-1; @apply py-2 max-w-full flex-1;
} }
.openapi-responses-header-content { .openapi-responses-header-content {
@@ -368,7 +372,8 @@
} }
.openapi-response-tab-content { .openapi-response-tab-content {
@apply overflow-hidden max-w-full flex items-baseline gap-2; @apply overflow-hidden max-w-full flex items-baseline;
@apply text-left text-pretty relative leading-[1.125rem] text-tint !font-normal truncate select-text;
} }
.openapi-response-description.openapi-markdown { .openapi-response-description.openapi-markdown {
@@ -383,10 +388,6 @@
@apply truncate max-w-full inline pr-1; @apply truncate max-w-full inline pr-1;
} }
.openapi-response-statuscode {
@apply tabular-nums text-sm font-normal font-mono shrink-0;
}
.openapi-response-content-type { .openapi-response-content-type {
@apply text-xs text-tint-8 ml-auto shrink-0; @apply text-xs text-tint-8 ml-auto shrink-0;
} }
@@ -416,15 +417,23 @@
/* Code Sample */ /* Code Sample */
.openapi-codesample { .openapi-codesample {
@apply border rounded bg-tint border-tint-subtle; @apply border rounded-md straight-corners:rounded-none bg-tint border-tint-subtle;
} }
.openapi-codesample-header { .openapi-codesample-header {
@apply flex flex-row items-center; @apply flex flex-row items-center;
} }
.openapi-codesample-header .openapi-select > button { .openapi-response-media-types-examples-footer-content {
@apply border-none; @apply flex flex-row items-center gap-2.5;
}
.openapi-response-examples-header .openapi-select > button {
@apply max-w-full overflow-hidden shrink pl-0.5 py-0.5;
}
.openapi-response-examples-header .openapi-select > button .openapi-statuscode {
@apply h-full;
} }
.openapi-codesample-header-content { .openapi-codesample-header-content {
@@ -432,7 +441,7 @@
} }
.openapi-codesample-header-content .openapi-path { .openapi-codesample-header-content .openapi-path {
@apply flex items-center font-mono *:text-[0.813rem] gap-2 h-fit *:truncate overflow-x-auto min-w-0 max-w-full font-normal text-tint-strong; @apply flex items-center font-mono text-[0.813rem] gap-1 h-fit *:truncate overflow-x-auto min-w-0 max-w-full font-normal text-tint-strong;
} }
.openapi-codesample-header-content .openapi-path .openapi-path-variable { .openapi-codesample-header-content .openapi-path .openapi-path-variable {
@@ -463,7 +472,7 @@
} }
.openapi-path .openapi-method { .openapi-path .openapi-method {
@apply text-[0.813rem] m-0 mt-0.5 items-center flex px-1; @apply m-0 mt-0.5 items-center flex px-1;
} }
.openapi-path-title { .openapi-path-title {
@@ -494,7 +503,7 @@
/* Panel */ /* Panel */
.openapi-panel { .openapi-panel {
@apply border rounded bg-tint border-tint-subtle; @apply border rounded-md straight-corners:rounded-none bg-tint border-tint-subtle;
} }
.openapi-panel-heading { .openapi-panel-heading {
@@ -516,13 +525,41 @@
} }
/* Example */ /* Example */
.openapi-response-examples {
@apply border rounded-md straight-corners:rounded-none bg-tint border-tint-subtle;
}
.openapi-response-examples-header {
@apply flex flex-row items-center p-2.5;
}
.openapi-response-examples-header-content {
@apply max-w-full overflow-hidden truncate;
}
.openapi-response-examples-statuscode-title {
@apply flex items-center;
}
.openapi-response-examples-header .openapi-select > button,
.openapi-response-examples-header .openapi-markdown,
.openapi-response-examples-statuscode-title {
@apply text-[0.813rem] truncate text-tint font-normal;
}
.openapi-response-examples-panel,
.openapi-codesample-panel {
@apply flex-1 text-sm relative focus-visible:outline-none;
@apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10;
}
.openapi-example-empty { .openapi-example-empty {
@apply relative text-tint bg-tint min-h-20 flex flex-col justify-center items-center; @apply relative text-tint bg-tint min-h-20 flex flex-col justify-center items-center;
} }
/* Common Elements */ /* Common Elements */
.openapi-select { .openapi-select {
/* unstyled */ @apply w-auto max-w-full;
} }
/* Prevent react-aria popover from setting overflow:auto on body */ /* Prevent react-aria popover from setting overflow:auto on body */
@@ -531,24 +568,32 @@ body:has(.openapi-select-popover) {
} }
.openapi-select > button { .openapi-select > button {
@apply flex items-center cursor-pointer gap-1.5 text-tint-strong max-w-60 rounded text-xs leading-6 px-1.5 truncate border border-tint-subtle bg-tint; @apply flex items-center font-normal cursor-pointer *:truncate gap-1.5 text-tint-strong max-w-32 rounded text-xs p-1.5 leading-none border border-tint-subtle bg-tint;
@apply hover:bg-tint-hover transition-all; @apply hover:bg-tint-hover transition-all;
} }
.openapi-select > button[data-focused="true"] {
@apply outline-primary -outline-offset-1 outline outline-1;
}
.openapi-select > button > span.react-aria-SelectValue { .openapi-select > button > span.react-aria-SelectValue {
@apply shrink truncate; @apply shrink truncate flex items-center;
} }
.openapi-select > button > .gb-icon { .openapi-select > button > span.react-aria-SelectValue span:not(.openapi-statuscode) {
@apply shrink-0; @apply truncate;
} }
.openapi-select > button svg { .openapi-select > button .openapi-markdown {
@apply size-2.5; @apply *:leading-none;
}
.openapi-select > button .gb-icon {
@apply size-2.5 shrink-0;
} }
.openapi-select-popover { .openapi-select-popover {
@apply min-w-32 max-w-fit w-auto max-h-52 overflow-y-auto p-1.5 border border-tint-subtle bg-tint-base backdrop-blur-xl rounded-md; @apply min-w-32 z-10 max-w-[max(20rem,var(--trigger-width))] overflow-x-hidden max-h-52 overflow-y-auto p-1.5 border border-tint-subtle bg-tint-base backdrop-blur-xl rounded-md straight-corners:rounded-none;
@apply shadow-md shadow-tint-12/1 dark:shadow-tint-1/1; @apply shadow-md shadow-tint-12/1 dark:shadow-tint-1/1;
} }
@@ -561,11 +606,17 @@ body:has(.openapi-select-popover) {
} }
.openapi-select-item { .openapi-select-item {
@apply text-sm cursor-pointer px-1.5 py-0.5 truncate text-tint ring-0 border-none rounded !outline-none; @apply text-sm flex items-center cursor-pointer px-1.5 overflow-hidden py-1 *:truncate text-tint ring-0 border-none rounded !outline-none;
@apply hover:bg-tint-hover theme-gradient:hover:bg-tint-12/1 hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-inset contrast-more:hover:ring-current; @apply hover:bg-tint-hover theme-gradient:hover:bg-tint-12/1 hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-inset contrast-more:hover:ring-current;
} }
.openapi-select-item-selected { .openapi-select button .openapi-markdown,
.openapi-select-item .openapi-markdown {
@apply text-[0.813rem] *:truncate;
}
.openapi-select-item-selected,
.openapi-select-item-selected .openapi-markdown {
@apply text-primary-subtle hover:text-primary hover:bg-primary-hover; @apply text-primary-subtle hover:text-primary hover:bg-primary-hover;
@apply theme-muted:hover:bg-primary-active theme-gradient:hover:bg-primary-active tint:font-semibold; @apply theme-muted:hover:bg-primary-active theme-gradient:hover:bg-primary-active tint:font-semibold;
@apply contrast-more:text-primary contrast-more:hover:text-primary-strong contrast-more:font-semibold; @apply contrast-more:text-primary contrast-more:hover:text-primary-strong contrast-more:font-semibold;
@@ -600,6 +651,14 @@ body:has(.openapi-select-popover) {
@apply text-tint; @apply text-tint;
} }
.openapi-section-footer {
@apply flex flex-row items-center p-2.5 gap-2.5 text-sm text-tint-strong border-t border-tint-subtle;
}
.openapi-section-footer-content {
@apply text-sm text-tint-strong;
}
.openapi-section-toggle { .openapi-section-toggle {
@apply text-tint-subtle contrast-more:text-tint-strong; @apply text-tint-subtle contrast-more:text-tint-strong;
} }
@@ -621,6 +680,7 @@ body:has(.openapi-select-popover) {
} }
/* Tabs */ /* Tabs */
.openapi-panel-header,
.openapi-tabs-list { .openapi-tabs-list {
@apply flex flex-row gap-1.5 py-1.5 px-2.5 w-full overflow-x-scroll; @apply flex flex-row gap-1.5 py-1.5 px-2.5 w-full overflow-x-scroll;
scrollbar-width: none; scrollbar-width: none;
@@ -632,18 +692,17 @@ body:has(.openapi-select-popover) {
} }
.openapi-tabs-tab[aria-selected="true"] { .openapi-tabs-tab[aria-selected="true"] {
@apply text-primary after:absolute after:-bottom-[calc(0.375rem_+_1px)] after:z-20 after:left-0 after:w-full after:h-px after:bg-primary-solid after:transition-all; @apply !text-primary-subtle after:absolute after:-bottom-[calc(0.375rem_+_1px)] after:z-20 after:left-0 after:w-full after:h-px after:bg-primary-solid after:transition-all;
} }
.openapi-tabs-panel, .openapi-tabs-panel {
.openapi-codesample-panel {
@apply flex-1 text-sm relative focus-visible:outline-none; @apply flex-1 text-sm relative focus-visible:outline-none;
@apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10; @apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10;
} }
/* Disclosure group */ /* Disclosure group */
.openapi-disclosure-group { .openapi-disclosure-group {
@apply border-b border-tint-subtle relative; @apply border-tint-subtle border-b border-x overflow-auto last:rounded-b-md straight-corners:last:rounded-none first:rounded-t-md straight-corners:first:rounded-none first:border-t relative;
} }
.openapi-disclosure-group-header { .openapi-disclosure-group-header {
@@ -651,16 +710,11 @@ body:has(.openapi-select-popover) {
} }
.openapi-disclosure-group-trigger { .openapi-disclosure-group-trigger {
@apply flex items-baseline relative flex-1 gap-2.5 py-2 truncate -outline-offset-1; @apply flex items-baseline transition-all hover:bg-tint-subtle relative flex-1 gap-2.5 p-3 truncate -outline-offset-1;
} }
.openapi-disclosure-group-trigger:disabled { .openapi-disclosure-group-trigger:disabled {
@apply cursor-default; @apply cursor-default hover:bg-inherit;
}
.openapi-disclosure-group:only-child,
.openapi-disclosure-group:last-child {
@apply border-b-0;
} }
.openapi-disclosure-group-trigger:disabled .openapi-disclosure-group-icon { .openapi-disclosure-group-trigger:disabled .openapi-disclosure-group-icon {
@@ -680,28 +734,42 @@ body:has(.openapi-select-popover) {
} }
.openapi-disclosure-group-panel { .openapi-disclosure-group-panel {
@apply pb-2.5; @apply p-3 pt-1 transition-all;
} }
.openapi-disclosure-group-trigger[aria-expanded="true"] > .openapi-disclosure-group-icon > svg { .openapi-disclosure-group-trigger[aria-expanded="true"] > .openapi-disclosure-group-icon > svg {
@apply rotate-90; @apply rotate-90;
} }
.openapi-disclosure-group:hover .openapi-disclosure-group-mediatype { .openapi-disclosure-group:hover .openapi-disclosure-group-mediatype,
@apply opacity-11 flex; .openapi-disclosure-group-mediatype:has(> .openapi-select[data-open="true"]) {
@apply opacity-11 visible flex;
} }
.openapi-disclosure-group-mediatype { .openapi-disclosure-group-mediatype {
@apply opacity-0 hidden text-xs transition-all duration-200 shrink-0 absolute right-0 top-2.5; @apply opacity-0 invisible text-xs transition-opacity duration-300 shrink-0 absolute right-2.5 top-2.5;
} }
.openapi-disclosure-group-mediatype > span { .openapi-disclosure-group-mediatype > span {
@apply px-1 bg-tint-6 text-tint-12 rounded-full; @apply px-1 bg-tint-6 text-tint-12 rounded-full straight-corners:rounded-md;
} }
/* Disclosure */ /* Disclosure */
.openapi-schemas-disclosure > .openapi-disclosure-trigger {
@apply flex items-center font-mono !w-full transition-all text-tint-strong !text-sm hover:bg-tint-subtle relative flex-1 gap-2.5 p-3 truncate -outline-offset-1;
}
.openapi-schemas-disclosure > .openapi-disclosure-trigger,
.openapi-schemas-disclosure .openapi-disclosure-panel {
@apply straight-corners:!rounded-none;
}
.openapi-schemas-disclosure > .openapi-disclosure-trigger[aria-expanded="true"] > svg {
@apply rotate-90;
}
.openapi-disclosure-trigger { .openapi-disclosure-trigger {
@apply transition-all truncate duration-300 max-w-full hover:text-tint-strong rounded-2xl border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1; @apply transition-all truncate duration-300 max-w-full hover:text-tint-strong rounded-2xl straight-corners:rounded border border-tint-subtle px-2.5 py-1 text-[0.813rem] text-tint flex flex-row items-center gap-1.5 -outline-offset-1;
} }
.openapi-disclosure-trigger span { .openapi-disclosure-trigger span {
@@ -717,7 +785,7 @@ body:has(.openapi-select-popover) {
} }
.openapi-disclosure-trigger[aria-expanded="true"] { .openapi-disclosure-trigger[aria-expanded="true"] {
@apply w-full rounded-lg border-b rounded-b-none; @apply w-full rounded-lg border-b rounded-b-none straight-corners:rounded-b-none;
} }
.openapi-disclosure-trigger[aria-expanded="false"] { .openapi-disclosure-trigger[aria-expanded="false"] {
@@ -725,7 +793,7 @@ body:has(.openapi-select-popover) {
} }
.openapi-disclosure-panel[aria-hidden="false"] { .openapi-disclosure-panel[aria-hidden="false"] {
@apply border-b border-x border-tint-subtle rounded-b-lg; @apply border-b border-x border-tint-subtle rounded-b-lg straight-corners:rounded-b;
} }
.openapi-disclosure-panel .openapi-schema { .openapi-disclosure-panel .openapi-schema {
@@ -745,17 +813,13 @@ body:has(.openapi-select-popover) {
@apply space-y-2.5; @apply space-y-2.5;
} }
.openapi-section-schemas {
@apply border border-tint-subtle rounded-lg;
}
.openapi-section-schemas > .openapi-section-body > .openapi-schema-properties > .openapi-schema, .openapi-section-schemas > .openapi-section-body > .openapi-schema-properties > .openapi-schema,
.openapi-section-schemas > .openapi-section-body > .openapi-schema-root { .openapi-section-schemas > .openapi-section-body > .openapi-schema-root {
@apply p-2.5; @apply p-2.5;
} }
.openapi-tooltip { .openapi-tooltip {
@apply flex items-center gap-1 bg-tint-base border border-tint-subtle text-tint-strong rounded-md font-medium px-1.5 py-0.5 shadow-sm text-[13px]; @apply flex items-center gap-1 bg-tint-base border border-tint-subtle text-tint-strong rounded-md straight-corners:rounded-none font-medium px-1.5 py-0.5 shadow-sm text-[13px];
} }
.openapi-tooltip svg { .openapi-tooltip svg {
@@ -10,18 +10,22 @@ export type DropdownButtonProps<E extends HTMLElement = HTMLElement> = Omit<
'ref' 'ref'
>; >;
/** export type DropdownProps<E extends HTMLElement = HTMLElement> = {
* Button with a dropdown.
*/
export function Dropdown<E extends HTMLElement>(props: {
/** Content of the button */ /** Content of the button */
button: (buttonProps: DropdownButtonProps<E>) => React.ReactNode; button: (buttonProps: DropdownButtonProps<E>) => React.ReactNode;
/** Content of the dropdown */ /** Content of the dropdown */
children: React.ReactNode; children: React.ReactNode;
/** Custom styles */ /** Custom styles */
className?: ClassValue; className?: ClassValue;
}) { /** Whether to use fixed positioning (for fixed navbars) */
const { button, children, className } = props; useFixedPosition?: boolean;
};
/**
* Button with a dropdown.
*/
export function Dropdown<E extends HTMLElement = HTMLElement>(props: DropdownProps<E>) {
const { button, children, className, useFixedPosition = false } = props;
const dropdownId = useId(); const dropdownId = useId();
return ( return (
@@ -53,7 +57,7 @@ export function Dropdown<E extends HTMLElement>(props: {
className className
)} )}
> >
<div className="fixed z-50 w-52"> <div className={tcls(useFixedPosition ? 'fixed' : 'absolute', 'z-50 w-52')}>
<div <div
className={tcls( className={tcls(
'mt-2', 'mt-2',
@@ -92,6 +96,7 @@ export function DropdownChevron() {
'ms-1', 'ms-1',
'transition-all', 'transition-all',
'group-hover/dropdown:opacity-11', 'group-hover/dropdown:opacity-11',
'group-hover/dropdown:rotate-180',
'group-focus-within/dropdown:rotate-180' 'group-focus-within/dropdown:rotate-180'
)} )}
/> />
@@ -212,10 +212,11 @@ export function Header(props: { context: GitBookSiteContext; withTopHeader?: boo
siteSpace={siteSpace} siteSpace={siteSpace}
siteSpaces={siteSpaces} siteSpaces={siteSpaces}
className="w-full grow py-1" className="w-full grow py-1"
useFixedPosition={true}
/> />
</div> </div>
)} )}
{sections && ( {sections && sections.list.length > 1 && (
<SiteSectionTabs <SiteSectionTabs
sections={encodeClientSiteSections(context, sections)} sections={encodeClientSiteSections(context, sections)}
/> />
@@ -3,15 +3,17 @@ import type { SiteSpace } from '@gitbook/api';
import { tcls } from '@/lib/tailwind'; import { tcls } from '@/lib/tailwind';
import type { GitBookSiteContext } from '@v2/lib/context'; import type { GitBookSiteContext } from '@v2/lib/context';
import { Dropdown, DropdownChevron, DropdownMenu } from './Dropdown'; import { Dropdown, DropdownChevron, DropdownMenu, type DropdownProps } from './Dropdown';
import { SpacesDropdownMenuItem } from './SpacesDropdownMenuItem'; import { SpacesDropdownMenuItem } from './SpacesDropdownMenuItem';
export function SpacesDropdown(props: { export function SpacesDropdown(
context: GitBookSiteContext; props: {
siteSpace: SiteSpace; context: GitBookSiteContext;
siteSpaces: SiteSpace[]; siteSpace: SiteSpace;
className?: string; siteSpaces: SiteSpace[];
}) { className?: string;
} & Partial<DropdownProps>
) {
const { context, siteSpace, siteSpaces, className } = props; const { context, siteSpace, siteSpaces, className } = props;
const { linker } = context; const { linker } = context;
@@ -67,6 +69,7 @@ export function SpacesDropdown(props: {
<DropdownChevron /> <DropdownChevron />
</div> </div>
)} )}
{...props}
> >
<DropdownMenu> <DropdownMenu>
{siteSpaces.map((otherSiteSpace, index) => ( {siteSpaces.map((otherSiteSpace, index) => (
@@ -123,7 +123,7 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
> >
{section.tag ? ( {section.tag ? (
<span <span
className={`-mt-0.5 openapi-method openapi-method-${section.tag.toLowerCase()}`} className={`-mt-0.5 openapi-method !text-xs openapi-method-${section.tag.toLowerCase()}`}
> >
{section.tag} {section.tag}
</span> </span>
@@ -1,9 +1,10 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import { type ImageResizer, checkIsHttpURL } from '@v2/lib/images'; import type { ImageResizer } from '@v2/lib/images';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { type ClassValue, tcls } from '@/lib/tailwind'; import { type ClassValue, tcls } from '@/lib/tailwind';
import { checkIsHttpURL } from '@/lib/urls';
import { ZoomImage } from './ZoomImage'; import { ZoomImage } from './ZoomImage';
import type { PolymorphicComponentProp } from './types'; import type { PolymorphicComponentProp } from './types';
+1 -1
View File
@@ -36,7 +36,7 @@ export const en = {
table_of_contents_button_label: 'Open table of contents', table_of_contents_button_label: 'Open table of contents',
cookies_title: 'Cookies', cookies_title: 'Cookies',
cookies_prompt: cookies_prompt:
'This site uses cookies to deliver its service and to analyse traffic. By browsing this site, you accept the ${1}.', 'This site uses cookies to deliver its service and to analyze traffic. By browsing this site, you accept the ${1}.',
cookies_prompt_privacy: 'privacy policy', cookies_prompt_privacy: 'privacy policy',
cookies_accept: 'Accept', cookies_accept: 'Accept',
cookies_reject: 'Reject', cookies_reject: 'Reject',
+5 -1
View File
@@ -242,7 +242,7 @@ export const getLatestOpenAPISpecVersionContent = cache({
* Resolve a URL to the content to render. * Resolve a URL to the content to render.
*/ */
export const getPublishedContentByUrl = cache({ export const getPublishedContentByUrl = cache({
name: 'api.getPublishedContentByUrl.v4', name: 'api.getPublishedContentByUrl.v7',
tag: (url) => getCacheTagForURL(url), tag: (url) => getCacheTagForURL(url),
get: async ( get: async (
url: string, url: string,
@@ -267,6 +267,10 @@ export const getPublishedContentByUrl = cache({
const parsed = parseCacheResponse(response); const parsed = parseCacheResponse(response);
console.log(
`Parsed ttl: ${parsed.ttl} at ${Date.now()}, for ${'apiToken' in response.data ? response.data.apiToken : '<no-token>'}`
);
const data: PublishedContentWithCache = { const data: PublishedContentWithCache = {
...response.data, ...response.data,
cacheMaxAge: parsed.ttl, cacheMaxAge: parsed.ttl,
+5 -1
View File
@@ -1,8 +1,12 @@
import { GITBOOK_ASSETS_URL, GITBOOK_URL } from '@v2/lib/env'; import { GITBOOK_ASSETS_URL, GITBOOK_URL } from '@v2/lib/env';
import { joinPath, joinPathWithBaseURL } from './paths';
/** /**
* Create a public URL for an asset. * Create a public URL for an asset.
*/ */
export function getAssetURL(path: string): string { export function getAssetURL(path: string): string {
return `${GITBOOK_ASSETS_URL || GITBOOK_URL}/~gitbook/static/${path}`; return joinPathWithBaseURL(
GITBOOK_ASSETS_URL || GITBOOK_URL,
joinPath('~gitbook/static', path)
);
} }
+17 -15
View File
@@ -9,6 +9,7 @@ import type {
} from '@/lib/openapi/types'; } from '@/lib/openapi/types';
import { getCloudflareRequestGlobal } from '@v2/lib/data/cloudflare'; import { getCloudflareRequestGlobal } from '@v2/lib/data/cloudflare';
import { withCacheKey, withoutConcurrentExecution } from '@v2/lib/data/memoize'; import { withCacheKey, withoutConcurrentExecution } from '@v2/lib/data/memoize';
import { GITBOOK_RUNTIME } from '@v2/lib/env';
import { assert } from 'ts-essentials'; import { assert } from 'ts-essentials';
import { resolveContentRef } from '../references'; import { resolveContentRef } from '../references';
import { isV2 } from '../v2'; import { isV2 } from '../v2';
@@ -68,26 +69,27 @@ const fetchFilesystemV1 = cache({
}, },
}); });
const fetchFilesystemV2 = withCacheKey(async (cacheKey, url: string) => { const fetchFilesystemV2 = withCacheKey(
const uncached = unstable_cache( withoutConcurrentExecution(getCloudflareRequestGlobal, async (cacheKey, url: string) => {
async () => fetchFilesystemV2Uncached(cacheKey, url), if (GITBOOK_RUNTIME !== 'cloudflare') {
[cacheKey], return fetchFilesystemUseCache(url);
{
revalidate: 60 * 60 * 24,
} }
);
const response = await uncached(); // FIXME: OpenNext doesn't support 'use cache' yet
return response; const uncached = unstable_cache(async () => fetchFilesystemUncached(url), [cacheKey], {
}); revalidate: 60 * 60 * 24,
const fetchFilesystemV2Uncached = withoutConcurrentExecution( });
getCloudflareRequestGlobal,
async function fetchFilesystemV2(url: string) { const response = await uncached();
const response = await fetchFilesystemUncached(url);
return response; return response;
} })
); );
const fetchFilesystemUseCache = async (url: string) => {
'use cache';
return fetchFilesystemUncached(url);
};
async function fetchFilesystemUncached( async function fetchFilesystemUncached(
url: string, url: string,
options?: { options?: {
+10
View File
@@ -0,0 +1,10 @@
/**
* Check if a URL is an HTTP URL.
*/
export function checkIsHttpURL(input: string | URL): boolean {
if (!URL.canParse(input)) {
return false;
}
const parsed = new URL(input);
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
}
+3 -2
View File
@@ -107,12 +107,13 @@ export async function serveResizedImage(
try { try {
const response = await resizeImage(url, options); const response = await resizeImage(url, options);
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to resize image'); throw new Error(`Failed to resize image, received status code ${response.status}`);
} }
return response; return response;
} catch (_error) { } catch (error) {
// Redirect to the original image if resizing fails // Redirect to the original image if resizing fails
console.warn('Error while resizing image, redirecting to original', error);
return NextResponse.redirect(url, 302); return NextResponse.redirect(url, 302);
} }
} }
+5 -1
View File
@@ -54,6 +54,9 @@ async function main() {
writeDataFile('styles-map', JSON.stringify(onlyStyles, null, 2)), writeDataFile('styles-map', JSON.stringify(onlyStyles, null, 2)),
writeDataFile('icons', JSON.stringify(result, null, 2)), writeDataFile('icons', JSON.stringify(result, null, 2)),
]); ]);
// biome-ignore lint/suspicious/noConsole: We want the CLI to log
console.log(`Generated ${result.length} icons`);
} }
async function writeDataFile(name, content) { async function writeDataFile(name, content) {
@@ -74,6 +77,7 @@ async function writeDataFile(name, content) {
]); ]);
} }
main().catch((_error) => { main().catch((error) => {
console.error(`Error generating icons list: ${error}`);
process.exit(1); process.exit(1);
}); });
+5 -3
View File
@@ -20,8 +20,6 @@ async function main() {
// Create the output folder if it doesn't exist // Create the output folder if it doesn't exist
await fs.mkdir(outputFolder, { recursive: true }); await fs.mkdir(outputFolder, { recursive: true });
const _printOutputFolder = path.relative(process.cwd(), outputFolder);
// Copy the assets from // Copy the assets from
// source/sprites to outputFolder/sprites // source/sprites to outputFolder/sprites
// source/svgs to outputFolder/svgs // source/svgs to outputFolder/svgs
@@ -45,8 +43,12 @@ async function main() {
} }
}), }),
]); ]);
// biome-ignore lint/suspicious/noConsole: We want the CLI to log
console.log(`Copied ${stylesToCopy.length} styles to ${outputFolder}`);
} }
main().catch((_error) => { main().catch((error) => {
console.error(`Error copying icons: ${error}`);
process.exit(1); process.exit(1);
}); });
+1
View File
@@ -2,6 +2,7 @@
"extends": ["//"], "extends": ["//"],
"tasks": { "tasks": {
"generate": { "generate": {
"inputs": ["bin/**/*", "package.json"],
"outputs": ["src/data/*.json", "dist/data/*.json"] "outputs": ["src/data/*.json", "dist/data/*.json"]
} }
} }
@@ -1,10 +1,10 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useRef, useState } from 'react'; import { useRef } from 'react';
import { mergeProps, useButton, useDisclosure, useFocusRing } from 'react-aria'; import { mergeProps, useButton, useDisclosure, useFocusRing } from 'react-aria';
import { useDisclosureState } from 'react-stately'; import { useDisclosureState } from 'react-stately';
import { OpenAPISelect, OpenAPISelectItem } from './OpenAPISelect'; import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
import { Section, SectionBody, SectionHeader, SectionHeaderContent } from './StaticSection'; import { Section, SectionBody, SectionHeader, SectionHeaderContent } from './StaticSection';
interface InteractiveSectionTab { interface InteractiveSectionTab {
@@ -35,6 +35,10 @@ export function InteractiveSection(props: {
header?: React.ReactNode; header?: React.ReactNode;
/** Children to display within the container */ /** Children to display within the container */
overlay?: React.ReactNode; overlay?: React.ReactNode;
/** State key to use with a store */
stateKey?: string;
/** Icon for the tabs select */
selectIcon?: React.ReactNode;
}) { }) {
const { const {
id, id,
@@ -46,12 +50,9 @@ export function InteractiveSection(props: {
header, header,
overlay, overlay,
toggleIcon = '▶', toggleIcon = '▶',
selectIcon,
stateKey = 'interactive-section',
} = props; } = props;
const [selectedTabKey, setSelectedTab] = useState(defaultTab);
const selectedTab: InteractiveSectionTab | undefined =
tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
const state = useDisclosureState({ const state = useDisclosureState({
defaultExpanded: defaultOpened, defaultExpanded: defaultOpened,
}); });
@@ -60,6 +61,10 @@ export function InteractiveSection(props: {
const { buttonProps: triggerProps, panelProps } = useDisclosure({}, state, panelRef); const { buttonProps: triggerProps, panelProps } = useDisclosure({}, state, panelRef);
const { buttonProps } = useButton(triggerProps, triggerRef); const { buttonProps } = useButton(triggerProps, triggerRef);
const { isFocusVisible, focusProps } = useFocusRing(); const { isFocusVisible, focusProps } = useFocusRing();
const store = useSelectState(stateKey, defaultTab);
const selectedTab: InteractiveSectionTab | undefined =
tabs.find((tab) => tab.key === store.key) ?? tabs[0];
return ( return (
<Section <Section
@@ -108,16 +113,12 @@ export function InteractiveSection(props: {
> >
{tabs.length > 1 ? ( {tabs.length > 1 ? (
<OpenAPISelect <OpenAPISelect
className={clsx( stateKey={stateKey}
'openapi-section-select',
`${className}-tabs-select`
)}
items={tabs} items={tabs}
selectedKey={selectedTab?.key ?? ''} onSelectionChange={() => {
onSelectionChange={(key) => {
setSelectedTab(String(key));
state.expand(); state.expand();
}} }}
icon={selectIcon}
placement="bottom end" placement="bottom end"
> >
{tabs.map((tab) => ( {tabs.map((tab) => (
@@ -206,6 +206,7 @@ function OpenAPICodeSampleFooter(props: {
method={data.method} method={data.method}
path={data.path} path={data.path}
renderers={renderers} renderers={renderers}
selectIcon={context.icons.chevronDown}
/> />
) : ( ) : (
<span /> <span />
@@ -1,70 +1,41 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback } from 'react';
import { useStore } from 'zustand';
import type { MediaTypeRenderer } from './OpenAPICodeSample'; import type { MediaTypeRenderer } from './OpenAPICodeSample';
import { OpenAPISelect, OpenAPISelectItem } from './OpenAPISelect'; import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
import { getOrCreateStoreByKey } from './getOrCreateStoreByKey';
type MediaTypeState = {
mediaType: string;
setMediaType: (mediaType: string) => void;
};
function useMediaTypeState(
data: { method: string; path: string },
defaultKey: string
): MediaTypeState {
const { method, path } = data;
const store = useStore(getOrCreateStoreByKey(`media-type-${method}-${path}`, defaultKey));
if (typeof store.key !== 'string') {
throw new Error('Media type key is not a string');
}
return {
mediaType: store.key,
setMediaType: useCallback((index: string) => store.setKey(index), [store.setKey]),
};
}
function useMediaTypeSampleIndexState(data: { method: string; path: string }, mediaType: string) {
const { method, path } = data;
const store = useStore(
getOrCreateStoreByKey(`media-type-sample-${mediaType}-${method}-${path}`, 0)
);
if (typeof store.key !== 'number') {
throw new Error('Example key is not a number');
}
return {
index: store.key,
setIndex: useCallback((index: number) => store.setKey(index), [store.setKey]),
};
}
export function OpenAPIMediaTypeExamplesSelector(props: { export function OpenAPIMediaTypeExamplesSelector(props: {
method: string; method: string;
path: string; path: string;
renderers: MediaTypeRenderer[]; renderers: MediaTypeRenderer[];
selectIcon?: React.ReactNode;
}) { }) {
const { method, path, renderers } = props; const { method, path, renderers, selectIcon } = props;
if (!renderers[0]) { if (!renderers[0]) {
throw new Error('No renderers provided'); throw new Error('No renderers provided');
} }
const state = useMediaTypeState({ method, path }, renderers[0].mediaType); const stateKey = `media-type-${method}-${path}`;
const selected = renderers.find((r) => r.mediaType === state.mediaType) || renderers[0]; const state = useSelectState(stateKey, renderers[0].mediaType);
const selected = renderers.find((r) => r.mediaType === state.key) || renderers[0];
return ( return (
<div className="openapi-codesample-selectors"> <div className="openapi-codesample-selectors">
<MediaTypeSelector state={state} renderers={renderers} /> <MediaTypeSelector selectIcon={selectIcon} stateKey={stateKey} renderers={renderers} />
<ExamplesSelector method={method} path={path} renderer={selected} /> <ExamplesSelector
selectIcon={selectIcon}
method={method}
path={path}
renderer={selected}
/>
</div> </div>
); );
} }
function MediaTypeSelector(props: { function MediaTypeSelector(props: {
state: MediaTypeState; stateKey: string;
renderers: MediaTypeRenderer[]; renderers: MediaTypeRenderer[];
selectIcon?: React.ReactNode;
}) { }) {
const { renderers, state } = props; const { renderers, stateKey, selectIcon } = props;
if (renderers.length < 2) { if (renderers.length < 2) {
return null; return null;
@@ -78,12 +49,12 @@ function MediaTypeSelector(props: {
return ( return (
<OpenAPISelect <OpenAPISelect
className={clsx('openapi-select')} className={clsx('openapi-select')}
selectedKey={state.mediaType}
items={renderers.map((renderer) => ({ items={renderers.map((renderer) => ({
key: renderer.mediaType, key: renderer.mediaType,
label: renderer.mediaType, label: renderer.mediaType,
}))} }))}
onSelectionChange={(e) => state.setMediaType(String(e))} icon={selectIcon}
stateKey={stateKey}
placement="bottom start" placement="bottom start"
> >
{items.map((item) => ( {items.map((item) => (
@@ -99,9 +70,9 @@ function ExamplesSelector(props: {
method: string; method: string;
path: string; path: string;
renderer: MediaTypeRenderer; renderer: MediaTypeRenderer;
selectIcon?: React.ReactNode;
}) { }) {
const { method, path, renderer } = props; const { method, path, renderer, selectIcon } = props;
const state = useMediaTypeSampleIndexState({ method, path }, renderer.mediaType);
if (renderer.examples.length < 2) { if (renderer.examples.length < 2) {
return null; return null;
} }
@@ -114,8 +85,8 @@ function ExamplesSelector(props: {
return ( return (
<OpenAPISelect <OpenAPISelect
items={items} items={items}
selectedKey={state.index} icon={selectIcon}
onSelectionChange={(e) => state.setIndex(Number(e))} stateKey={`media-type-sample-${renderer.mediaType}-${method}-${path}`}
placement="bottom start" placement="bottom start"
> >
{items.map((item) => ( {items.map((item) => (
@@ -136,9 +107,9 @@ export function OpenAPIMediaTypeExamplesBody(props: {
if (!renderers[0]) { if (!renderers[0]) {
throw new Error('No renderers provided'); throw new Error('No renderers provided');
} }
const mediaTypeState = useMediaTypeState({ method, path }, renderers[0].mediaType);
const selected = const mediaTypeState = useSelectState(`media-type-${method}-${path}`, renderers[0].mediaType);
renderers.find((r) => r.mediaType === mediaTypeState.mediaType) ?? renderers[0]; const selected = renderers.find((r) => r.mediaType === mediaTypeState.key) ?? renderers[0];
if (selected.examples.length === 0) { if (selected.examples.length === 0) {
return selected.element; return selected.element;
} }
@@ -147,10 +118,13 @@ export function OpenAPIMediaTypeExamplesBody(props: {
function ExamplesBody(props: { method: string; path: string; renderer: MediaTypeRenderer }) { function ExamplesBody(props: { method: string; path: string; renderer: MediaTypeRenderer }) {
const { method, path, renderer } = props; const { method, path, renderer } = props;
const exampleState = useMediaTypeSampleIndexState({ method, path }, renderer.mediaType); const exampleState = useSelectState(
const example = renderer.examples[exampleState.index] ?? renderer.examples[0]; `media-type-sample-${renderer.mediaType}-${method}-${path}`,
renderer.mediaType
);
const example = renderer.examples[Number(exampleState.key)] ?? renderer.examples[0];
if (!example) { if (!example) {
throw new Error(`No example found for index ${exampleState.index}`); throw new Error(`No example found for key ${exampleState.key}`);
} }
return example.element; return example.element;
} }
@@ -22,25 +22,21 @@ type CodeSampleItem = OpenAPISelectItem & {
footer?: React.ReactNode; footer?: React.ReactNode;
}; };
export function OpenAPICodeSampleHeader(props: { function OpenAPICodeSampleHeader(props: {
items: CodeSampleItem[]; items: CodeSampleItem[];
data: OpenAPIOperationData; data: OpenAPIOperationData;
selectIcon?: React.ReactNode;
}) { }) {
const { data, items } = props; const { data, items, selectIcon } = props;
const state = useCodeSampleState(items[0]?.key ?? '');
const selected = items.find((item) => item.key === state.key) || items[0];
return ( return (
<> <>
<OpenAPIPath canCopy={false} withServer={false} data={data} /> <OpenAPIPath canCopy={false} withServer={false} data={data} />
{items.length > 1 ? ( {items.length > 1 ? (
<OpenAPISelect <OpenAPISelect
selectedKey={selected?.key} icon={selectIcon}
onSelectionChange={(key) => {
state.setKey(key);
}}
items={items} items={items}
stateKey="codesample"
placement="bottom end" placement="bottom end"
> >
{items.map((item) => ( {items.map((item) => (
@@ -59,8 +55,9 @@ export function OpenAPICodeSampleHeader(props: {
export function OpenAPICodeSampleBody(props: { export function OpenAPICodeSampleBody(props: {
items: CodeSampleItem[]; items: CodeSampleItem[];
data: OpenAPIOperationData; data: OpenAPIOperationData;
selectIcon?: React.ReactNode;
}) { }) {
const { items, data } = props; const { items, data, selectIcon } = props;
if (!items[0]) { if (!items[0]) {
throw new Error('No items provided'); throw new Error('No items provided');
} }
@@ -75,7 +72,7 @@ export function OpenAPICodeSampleBody(props: {
return ( return (
<StaticSection <StaticSection
header={<OpenAPICodeSampleHeader data={data} items={items} />} header={<OpenAPICodeSampleHeader selectIcon={selectIcon} data={data} items={items} />}
className="openapi-codesample" className="openapi-codesample"
> >
<div id={selected.key as string} className="openapi-codesample-panel"> <div id={selected.key as string} className="openapi-codesample-panel">
@@ -1,41 +1,39 @@
'use client'; 'use client';
import clsx from 'clsx';
import type React from 'react';
import { useState } from 'react'; import { useState } from 'react';
import { Button, Disclosure, DisclosurePanel, Heading } from 'react-aria-components'; import { Button, Disclosure, DisclosurePanel } from 'react-aria-components';
import type { OpenAPIClientContext } from './types';
/** /**
* Display an interactive OpenAPI disclosure. * Display an interactive OpenAPI disclosure.
*/ */
export function OpenAPIDisclosure(props: { export function OpenAPIDisclosure(props: {
context: OpenAPIClientContext; icon: React.ReactNode;
children: React.ReactNode; children: React.ReactNode;
label: string; label: string | ((isExpanded: boolean) => string);
className?: string;
}): React.JSX.Element { }): React.JSX.Element {
const { context, children, label } = props; const { icon, children, label, className } = props;
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
return ( return (
<Disclosure <Disclosure
className="openapi-disclosure" className={clsx('openapi-disclosure', className)}
isExpanded={isExpanded} isExpanded={isExpanded}
onExpandedChange={setIsExpanded} onExpandedChange={setIsExpanded}
> >
<Heading> <Button
<Button slot="trigger"
slot="trigger" className="openapi-disclosure-trigger"
className="openapi-disclosure-trigger" style={({ isFocusVisible }) => ({
style={({ isFocusVisible }) => ({ outline: isFocusVisible
outline: isFocusVisible ? '2px solid rgb(var(--primary-color-500) / 0.4)'
? '2px solid rgb(var(--primary-color-500) / 0.4)' : 'none',
: 'none', })}
})} >
> {icon}
{context.icons.plus} <span>{typeof label === 'function' ? label(isExpanded) : label}</span>
<span> </Button>
{isExpanded ? 'Hide' : 'Show'} {label}
</span>
</Button>
</Heading>
<DisclosurePanel className="openapi-disclosure-panel"> <DisclosurePanel className="openapi-disclosure-panel">
{isExpanded ? children : null} {isExpanded ? children : null}
</DisclosurePanel> </DisclosurePanel>
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { createContext, useContext, useRef, useState } from 'react'; import { createContext, useContext, useRef } from 'react';
import { mergeProps, useButton, useDisclosure, useFocusRing, useId } from 'react-aria'; import { mergeProps, useButton, useDisclosure, useFocusRing, useId } from 'react-aria';
import { import {
type DisclosureGroupProps, type DisclosureGroupProps,
@@ -8,18 +8,23 @@ import {
useDisclosureGroupState, useDisclosureGroupState,
useDisclosureState, useDisclosureState,
} from 'react-stately'; } from 'react-stately';
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
interface Props { interface Props {
groups: TDisclosureGroup[]; groups: TDisclosureGroup[];
icon?: React.ReactNode; icon?: React.ReactNode;
/** State key to use with a store */
selectStateKey?: string;
/** Icon to display for the select */
selectIcon?: React.ReactNode;
} }
type TDisclosureGroup = { type TDisclosureGroup = {
id: string; key: string;
label: string | React.ReactNode; label: string | React.ReactNode;
tabs?: { tabs?: {
id: string; key: string;
label?: string | React.ReactNode; label: string | React.ReactNode;
body?: React.ReactNode; body?: React.ReactNode;
}[]; }[];
}; };
@@ -30,24 +35,35 @@ const DisclosureGroupStateContext = createContext<DisclosureGroupState | null>(n
* Display an interactive OpenAPI disclosure group. * Display an interactive OpenAPI disclosure group.
*/ */
export function OpenAPIDisclosureGroup(props: DisclosureGroupProps & Props) { export function OpenAPIDisclosureGroup(props: DisclosureGroupProps & Props) {
const { icon, groups } = props; const { icon, groups, selectStateKey, selectIcon } = props;
const state = useDisclosureGroupState(props); const state = useDisclosureGroupState(props);
return ( return (
<DisclosureGroupStateContext.Provider value={state}> <DisclosureGroupStateContext.Provider value={state}>
{groups.map((group) => ( {groups.map((group) => (
<DisclosureItem icon={icon} key={group.id} group={group} /> <DisclosureItem
selectStateKey={selectStateKey}
selectIcon={selectIcon}
icon={icon}
key={group.key}
group={group}
/>
))} ))}
</DisclosureGroupStateContext.Provider> </DisclosureGroupStateContext.Provider>
); );
} }
function DisclosureItem(props: { group: TDisclosureGroup; icon?: React.ReactNode }) { function DisclosureItem(props: {
const { icon, group } = props; group: TDisclosureGroup;
icon?: React.ReactNode;
selectStateKey?: string;
selectIcon?: React.ReactNode;
}) {
const { icon, group, selectStateKey, selectIcon } = props;
const defaultId = useId(); const defaultId = useId();
const id = group.id || defaultId; const id = group.key || defaultId;
const groupState = useContext(DisclosureGroupStateContext); const groupState = useContext(DisclosureGroupStateContext);
const isExpanded = groupState?.expandedKeys.has(id) || false; const isExpanded = groupState?.expandedKeys.has(id) || false;
const state = useDisclosureState({ const state = useDisclosureState({
@@ -74,9 +90,9 @@ function DisclosureItem(props: { group: TDisclosureGroup; icon?: React.ReactNode
const { buttonProps } = useButton(triggerProps, triggerRef); const { buttonProps } = useButton(triggerProps, triggerRef);
const { isFocusVisible, focusProps } = useFocusRing(); const { isFocusVisible, focusProps } = useFocusRing();
const defaultTab = group.tabs?.[0]?.id || ''; const defaultTab = group.tabs?.[0]?.key || '';
const [selectedTabKey, setSelectedTabKey] = useState(defaultTab); const store = useSelectState(selectStateKey, defaultTab);
const selectedTab = group.tabs?.find((tab) => tab.id === selectedTabKey); const selectedTab = group.tabs?.find((tab) => tab.key === store.key) || group.tabs?.[0];
return ( return (
<div className="openapi-disclosure-group" aria-expanded={state.isExpanded}> <div className="openapi-disclosure-group" aria-expanded={state.isExpanded}>
@@ -104,23 +120,25 @@ function DisclosureItem(props: { group: TDisclosureGroup; icon?: React.ReactNode
{group.label} {group.label}
</button> </button>
{group.tabs ? ( {group.tabs ? (
<div className="openapi-disclosure-group-mediatype"> <div
className="openapi-disclosure-group-mediatype"
onClick={(e) => e.stopPropagation()}
>
{group.tabs?.length > 1 ? ( {group.tabs?.length > 1 ? (
<select <OpenAPISelect
className="openapi-section-select openapi-select openapi-disclosure-group-tabs-select" icon={selectIcon}
onClick={(event) => event.stopPropagation()} stateKey={selectStateKey}
value={selectedTab?.id} onSelectionChange={() => {
onChange={(event) => {
setSelectedTabKey(event.target.value);
state.expand(); state.expand();
}} }}
items={group.tabs}
> >
{group.tabs.map((tab) => ( {group.tabs.map((tab) => (
<option key={tab.id} value={tab.id}> <OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
{tab.label} {tab.label}
</option> </OpenAPISelectItem>
))} ))}
</select> </OpenAPISelect>
) : group.tabs[0]?.label ? ( ) : group.tabs[0]?.label ? (
<span>{group.tabs[0].label}</span> <span>{group.tabs[0].label}</span>
) : null} ) : null}
@@ -1,7 +1,7 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser'; import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { InteractiveSection } from './InteractiveSection'; import { InteractiveSection } from './InteractiveSection';
import { OpenAPIRootSchema } from './OpenAPISchemaServer'; import { OpenAPIRootSchema } from './OpenAPISchemaServer';
import type { OpenAPIClientContext } from './types'; import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
import { checkIsReference } from './utils'; import { checkIsReference } from './utils';
/** /**
@@ -10,8 +10,10 @@ import { checkIsReference } from './utils';
export function OpenAPIRequestBody(props: { export function OpenAPIRequestBody(props: {
requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject; requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject;
context: OpenAPIClientContext; context: OpenAPIClientContext;
data: OpenAPIOperationData;
}) { }) {
const { requestBody, context } = props; const { requestBody, context, data } = props;
const { method, path } = data;
if (checkIsReference(requestBody)) { if (checkIsReference(requestBody)) {
return null; return null;
@@ -21,6 +23,8 @@ export function OpenAPIRequestBody(props: {
<InteractiveSection <InteractiveSection
header="Body" header="Body"
className="openapi-requestbody" className="openapi-requestbody"
stateKey={`media-type-${method}-${path}`}
selectIcon={context.icons.chevronDown}
tabs={Object.entries(requestBody.content ?? {}).map( tabs={Object.entries(requestBody.content ?? {}).map(
([contentType, mediaTypeObject]) => { ([contentType, mediaTypeObject]) => {
return { return {
@@ -27,7 +27,7 @@ export function OpenAPIResponse(props: {
return ( return (
<div className="openapi-response-body"> <div className="openapi-response-body">
{headers.length > 0 ? ( {headers.length > 0 ? (
<OpenAPIDisclosure context={context} label="Headers"> <OpenAPIDisclosure icon={context.icons.plus} label="Headers">
<OpenAPISchemaProperties <OpenAPISchemaProperties
properties={headers.map(([name, header]) => properties={headers.map(([name, header]) =>
parameterToProperty({ name, ...header }) parameterToProperty({ name, ...header })
@@ -6,10 +6,11 @@ import {
getExampleFromReference, getExampleFromReference,
getExamplesFromMediaTypeObject, getExamplesFromMediaTypeObject,
} from './OpenAPIExample'; } from './OpenAPIExample';
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs'; import { OpenAPIResponseExampleContent } from './OpenAPIResponseExampleContent';
import { StaticSection } from './StaticSection'; import { OpenAPIResponseMediaTypeContent } from './OpenAPIResponseMediaType';
import type { OpenAPIContext, OpenAPIOperationData } from './types'; import type { OpenAPIContext, OpenAPIOperationData } from './types';
import { checkIsReference, createStateKey, resolveDescription } from './utils'; import { getStatusCodeDefaultLabel } from './utils';
import { checkIsReference, resolveDescription } from './utils';
/** /**
* Display an example of the response content. * Display an example of the response content.
@@ -45,11 +46,17 @@ export function OpenAPIResponseExample(props: {
const tabs = responses.map(([key, responseObject]) => { const tabs = responses.map(([key, responseObject]) => {
const description = resolveDescription(responseObject); const description = resolveDescription(responseObject);
const label = description ? (
<Markdown source={description} />
) : (
getStatusCodeDefaultLabel(key)
);
if (checkIsReference(responseObject)) { if (checkIsReference(responseObject)) {
return { return {
key: key, key: key,
label: key, label,
statusCode: key,
body: ( body: (
<OpenAPIExample <OpenAPIExample
example={getExampleFromReference(responseObject)} example={getExampleFromReference(responseObject)}
@@ -57,24 +64,23 @@ export function OpenAPIResponseExample(props: {
syntax="json" syntax="json"
/> />
), ),
footer: description ? <Markdown source={description} /> : undefined,
}; };
} }
if (!responseObject.content || Object.keys(responseObject.content).length === 0) { if (!responseObject.content || Object.keys(responseObject.content).length === 0) {
return { return {
key: key, key: key,
label: key, label,
statusCode: key,
body: <OpenAPIEmptyExample />, body: <OpenAPIEmptyExample />,
footer: description ? <Markdown source={description} /> : undefined,
}; };
} }
return { return {
key: key, key: key,
label: key, label,
statusCode: key,
body: <OpenAPIResponse context={context} content={responseObject.content} />, body: <OpenAPIResponse context={context} content={responseObject.content} />,
footer: description ? <Markdown source={description} /> : undefined,
}; };
}); });
@@ -83,11 +89,11 @@ export function OpenAPIResponseExample(props: {
} }
return ( return (
<OpenAPITabs stateKey={createStateKey('response-example')} items={tabs}> <OpenAPIResponseExampleContent
<StaticSection header={<OpenAPITabsList />} className="openapi-panel"> selectIcon={context.icons.chevronDown}
<OpenAPITabsPanels /> blockKey={context.blockKey}
</StaticSection> items={tabs}
</OpenAPITabs> />
); );
} }
@@ -106,42 +112,30 @@ function OpenAPIResponse(props: {
throw new Error('One media type is required'); throw new Error('One media type is required');
} }
if (entries.length === 1) {
const [mediaType, mediaTypeObject] = firstEntry;
return (
<OpenAPIResponseMediaType
context={context}
mediaType={mediaType}
mediaTypeObject={mediaTypeObject}
/>
);
}
const tabs = entries.map((entry) => { const tabs = entries.map((entry) => {
const [mediaType, mediaTypeObject] = entry; const [mediaType, mediaTypeObject] = entry;
return { return {
key: mediaType, key: mediaType,
label: mediaType, label: mediaType,
body: ( body: <></>,
<OpenAPIResponseMediaType examples: getExamples({
context={context} mediaTypeObject,
mediaType={mediaType} mediaType,
mediaTypeObject={mediaTypeObject} context,
/> }),
),
}; };
}); });
return ( return (
<OpenAPITabs stateKey={createStateKey('response-media-types')} items={tabs}> <OpenAPIResponseMediaTypeContent
<StaticSection header={<OpenAPITabsList />} className="openapi-response-media-types"> selectIcon={context.icons.chevronDown}
<OpenAPITabsPanels /> blockKey={context.blockKey}
</StaticSection> items={tabs}
</OpenAPITabs> />
); );
} }
function OpenAPIResponseMediaType(props: { function getExamples(props: {
mediaTypeObject: OpenAPIV3.MediaTypeObject; mediaTypeObject: OpenAPIV3.MediaTypeObject;
mediaType: string; mediaType: string;
context: OpenAPIContext; context: OpenAPIContext;
@@ -149,23 +143,8 @@ function OpenAPIResponseMediaType(props: {
const { mediaTypeObject, mediaType } = props; const { mediaTypeObject, mediaType } = props;
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType }); const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
const syntax = getSyntaxFromMediaType(mediaType); const syntax = getSyntaxFromMediaType(mediaType);
const firstExample = examples[0];
if (!firstExample) { return examples.map((example) => {
return <OpenAPIEmptyExample />;
}
if (examples.length === 1) {
return (
<OpenAPIExample
example={firstExample.example}
context={props.context}
syntax={syntax}
/>
);
}
const tabs = examples.map((example) => {
return { return {
key: example.key, key: example.key,
label: example.example.summary || example.key, label: example.example.summary || example.key,
@@ -174,17 +153,6 @@ function OpenAPIResponseMediaType(props: {
), ),
}; };
}); });
return (
<OpenAPITabs stateKey={createStateKey('response-media-type-examples')} items={tabs}>
<StaticSection
header={<OpenAPITabsList />}
className="openapi-response-media-type-examples"
>
<OpenAPITabsPanels />
</StaticSection>
</OpenAPITabs>
);
} }
/** /**
@@ -0,0 +1,123 @@
'use client';
import clsx from 'clsx';
import type { Key } from 'react-aria';
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
import { StaticSection } from './StaticSection';
import { createStateKey, getStatusCodeClassName } from './utils';
type OpenAPIResponseExampleItem = OpenAPISelectItem & {
statusCode: string;
body: React.ReactNode;
};
/**
* Get the state of the response examples select.
*/
export function useResponseExamplesState(
blockKey: string | undefined,
initialKey: Key = 'default'
) {
return useSelectState(getResponseExampleStateKey(blockKey), initialKey);
}
export function OpenAPIResponseExampleContent(props: {
items: OpenAPIResponseExampleItem[];
blockKey?: string;
selectIcon?: React.ReactNode;
}) {
const { blockKey, items, selectIcon } = props;
return (
<StaticSection
header={
<OpenAPIResponseExampleHeader
selectIcon={selectIcon}
blockKey={blockKey}
items={items}
/>
}
className="openapi-response-examples"
>
<OpenAPIResponseExampleBody blockKey={blockKey} items={items} />
</StaticSection>
);
}
function OpenAPIResponseExampleHeader(props: {
items: OpenAPIResponseExampleItem[];
blockKey?: string;
selectIcon?: React.ReactNode;
}) {
const { items, blockKey, selectIcon } = props;
if (items.length === 1) {
const item = items[0];
if (!item) {
return null;
}
return (
<span className="openapi-response-examples-statuscode-title">
<span
className={clsx(
'openapi-statuscode',
`openapi-statuscode-${getStatusCodeClassName(item.statusCode)}`,
'openapi-response-examples-statuscode'
)}
>
{item.statusCode}
</span>
<span>{item.label}</span>
</span>
);
}
return (
<OpenAPISelect
items={items}
icon={selectIcon}
stateKey={getResponseExampleStateKey(blockKey)}
placement="bottom start"
>
{items.map((item) => (
<OpenAPISelectItem key={item.key} id={item.key} value={item}>
<span
className={clsx(
'openapi-statuscode',
`openapi-statuscode-${getStatusCodeClassName(item.statusCode)}`,
'openapi-response-examples-statuscode'
)}
>
{item.statusCode}
</span>
<span>{item.label}</span>
</OpenAPISelectItem>
))}
</OpenAPISelect>
);
}
function OpenAPIResponseExampleBody(props: {
items: OpenAPIResponseExampleItem[];
blockKey?: string;
}) {
const { blockKey, items } = props;
const state = useResponseExamplesState(blockKey, items[0]?.key);
const selectedItem = items.find((item) => item.key === state.key) ?? items[0];
if (!selectedItem) {
return null;
}
return <div className="openapi-response-examples-panel">{selectedItem.body}</div>;
}
/**
* Return the state key for the response examples.
*/
function getResponseExampleStateKey(blockKey: string | undefined) {
return createStateKey('openapi-responses', blockKey);
}
@@ -0,0 +1,148 @@
'use client';
import type { Key } from 'react-aria';
import { OpenAPIEmptyExample } from './OpenAPIExample';
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
import { StaticSection } from './StaticSection';
import { createStateKey } from './utils';
type OpenAPIResponseMediaTypeItem = OpenAPISelectItem & {
body: React.ReactNode;
examples?: OpenAPIResponseMediaTypeItem[];
};
/**
* Get the state of the response examples select.
*/
export function useResponseMediaTypesState(
blockKey: string | undefined,
initialKey: Key = 'default'
) {
return useSelectState(getResponseMediaTypeStateKey(blockKey), initialKey);
}
function useResponseMediaTypeExamplesState(
blockKey: string | undefined,
initialKey: Key = 'default'
) {
return useSelectState(getResponseMediaTypeExamplesStateKey(blockKey), initialKey);
}
export function OpenAPIResponseMediaTypeContent(props: {
items: OpenAPIResponseMediaTypeItem[];
blockKey?: string;
selectIcon?: React.ReactNode;
}) {
const { blockKey, items, selectIcon } = props;
const state = useResponseMediaTypesState(blockKey, items[0]?.key);
const examples = items.find((item) => item.key === state.key)?.examples ?? [];
if (!items.length && !examples.length) {
return null;
}
return (
<StaticSection
footer={
items.length > 1 || examples.length > 1 ? (
<OpenAPIResponseMediaTypeFooter
blockKey={blockKey}
items={items}
examples={examples}
selectIcon={selectIcon}
/>
) : null
}
className="openapi-response-media-types-examples"
>
<OpenAPIResponseMediaTypeBody blockKey={blockKey} items={items} examples={examples} />
</StaticSection>
);
}
function OpenAPIResponseMediaTypeFooter(props: {
items: OpenAPIResponseMediaTypeItem[];
examples?: OpenAPIResponseMediaTypeItem[];
blockKey?: string;
selectIcon?: React.ReactNode;
}) {
const { items, examples, blockKey, selectIcon } = props;
return (
<>
{items.length > 1 && (
<OpenAPISelect
icon={selectIcon}
items={items}
stateKey={getResponseMediaTypeStateKey(blockKey)}
placement="bottom start"
>
{items.map((item) => (
<OpenAPISelectItem key={item.key} id={item.key} value={item}>
<span>{item.label}</span>
</OpenAPISelectItem>
))}
</OpenAPISelect>
)}
{examples && examples.length > 1 ? (
<OpenAPISelect
icon={selectIcon}
items={examples}
stateKey={getResponseMediaTypeExamplesStateKey(blockKey)}
placement="bottom start"
>
{examples.map((example) => (
<OpenAPISelectItem key={example.key} id={example.key} value={example}>
<span>{example.label}</span>
</OpenAPISelectItem>
))}
</OpenAPISelect>
) : null}
</>
);
}
function OpenAPIResponseMediaTypeBody(props: {
items: OpenAPIResponseMediaTypeItem[];
examples?: OpenAPIResponseMediaTypeItem[];
blockKey?: string;
}) {
const { blockKey, items, examples } = props;
const state = useResponseMediaTypesState(blockKey, items[0]?.key);
const selectedItem = items.find((item) => item.key === state.key) ?? items[0];
const exampleState = useResponseMediaTypeExamplesState(
blockKey,
selectedItem?.examples?.[0]?.key
);
if (!selectedItem) {
return null;
}
if (examples) {
const selectedExample =
examples.find((example) => example.key === exampleState.key) ?? examples[0];
if (!selectedExample) {
return <OpenAPIEmptyExample />;
}
return selectedExample.body;
}
return selectedItem.body;
}
/**
* Return the state key for the response media types.
*/
function getResponseMediaTypeStateKey(blockKey: string | undefined) {
return createStateKey('response-media-types', blockKey);
}
function getResponseMediaTypeExamplesStateKey(blockKey: string | undefined) {
return createStateKey('response-media-types-examples', blockKey);
}
+80 -60
View File
@@ -1,9 +1,14 @@
'use client';
import type { OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser'; import type { OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
import clsx from 'clsx';
import { Markdown } from './Markdown'; import { Markdown } from './Markdown';
import { OpenAPIDisclosureGroup } from './OpenAPIDisclosureGroup'; import { OpenAPIDisclosureGroup } from './OpenAPIDisclosureGroup';
import { OpenAPIResponse } from './OpenAPIResponse'; import { OpenAPIResponse } from './OpenAPIResponse';
import { useResponseExamplesState } from './OpenAPIResponseExampleContent';
import { StaticSection } from './StaticSection'; import { StaticSection } from './StaticSection';
import type { OpenAPIClientContext } from './types'; import type { OpenAPIClientContext } from './types';
import { createStateKey, getStatusCodeClassName, getStatusCodeDefaultLabel } from './utils';
/** /**
* Display an interactive response body. * Display an interactive response body.
@@ -14,70 +19,85 @@ export function OpenAPIResponses(props: {
}) { }) {
const { responses, context } = props; const { responses, context } = props;
const groups = Object.entries(responses).map(
([statusCode, response]: [string, OpenAPIV3.ResponseObject]) => {
const tabs = (() => {
// If there is no content, but there are headers, we need to show the headers
if (
(!response.content || !Object.keys(response.content).length) &&
response.headers &&
Object.keys(response.headers).length
) {
return [
{
key: 'default',
label: '',
body: (
<OpenAPIResponse
response={response}
mediaType={{}}
context={context}
/>
),
},
];
}
return Object.entries(response.content ?? {}).map(([contentType, mediaType]) => ({
key: contentType,
label: contentType,
body: (
<OpenAPIResponse
response={response}
mediaType={mediaType}
context={context}
/>
),
}));
})();
const description = response.description;
return {
key: statusCode,
label: (
<div className="openapi-response-tab-content">
<span
className={clsx(
'openapi-statuscode',
`openapi-statuscode-${getStatusCodeClassName(statusCode)}`
)}
>
{statusCode}
</span>
{description ? (
<Markdown
source={description}
className="openapi-response-description"
/>
) : (
getStatusCodeDefaultLabel(statusCode)
)}
</div>
),
tabs,
};
}
);
const state = useResponseExamplesState(context.blockKey, groups[0]?.key);
return ( return (
<StaticSection header="Responses" className="openapi-responses"> <StaticSection header="Responses" className="openapi-responses">
<OpenAPIDisclosureGroup <OpenAPIDisclosureGroup
allowsMultipleExpanded
icon={context.icons.chevronRight} icon={context.icons.chevronRight}
groups={Object.entries(responses).map( expandedKeys={state.key ? new Set([state.key]) : new Set()}
([statusCode, response]: [string, OpenAPIV3.ResponseObject]) => { onExpandedChange={(keys) => {
const tabs = (() => { const key = keys.values().next().value ?? null;
// If there is no content, but there are headers, we need to show the headers state.setKey(key);
if ( }}
(!response.content || !Object.keys(response.content).length) && groups={groups}
response.headers && selectStateKey={createStateKey('response-media-types', context.blockKey)}
Object.keys(response.headers).length
) {
return [
{
id: 'default',
body: (
<OpenAPIResponse
response={response}
mediaType={{}}
context={context}
/>
),
},
];
}
return Object.entries(response.content ?? {}).map(
([contentType, mediaType]) => ({
id: contentType,
label: contentType,
body: (
<OpenAPIResponse
response={response}
mediaType={mediaType}
context={context}
/>
),
})
);
})();
const description = response.description;
return {
id: statusCode,
label: (
<div className="openapi-response-tab-content">
<span className="openapi-response-statuscode">
{statusCode}
</span>
{description ? (
<Markdown
source={description}
className="openapi-response-description"
/>
) : null}
</div>
),
tabs,
};
}
)}
/> />
</StaticSection> </StaticSection>
); );
+18 -10
View File
@@ -54,7 +54,10 @@ function OpenAPISchemaProperty(props: {
const properties = getSchemaProperties(schema); const properties = getSchemaProperties(schema);
if (properties?.length) { if (properties?.length) {
return ( return (
<OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}> <OpenAPIDisclosure
icon={context.icons.plus}
label={(isExpanded) => getDisclosureLabel(schema, isExpanded)}
>
<OpenAPISchemaProperties <OpenAPISchemaProperties
properties={properties} properties={properties}
circularRefs={circularRefs} circularRefs={circularRefs}
@@ -208,7 +211,10 @@ function OpenAPISchemaAlternative(props: {
{description ? ( {description ? (
<Markdown source={description} className="openapi-schema-description" /> <Markdown source={description} className="openapi-schema-description" />
) : null} ) : null}
<OpenAPIDisclosure context={context} label={getDisclosureLabel(schema)}> <OpenAPIDisclosure
icon={context.icons.plus}
label={(isExpanded) => getDisclosureLabel(schema, isExpanded)}
>
{properties?.length ? ( {properties?.length ? (
<OpenAPISchemaProperties <OpenAPISchemaProperties
properties={properties} properties={properties}
@@ -502,19 +508,21 @@ function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
return type; return type;
} }
function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string { function getDisclosureLabel(schema: OpenAPIV3.SchemaObject, isExpanded: boolean) {
let label: string;
if (schema.type === 'array' && !!schema.items) { if (schema.type === 'array' && !!schema.items) {
if (schema.items.oneOf) { if (schema.items.oneOf) {
return 'available items'; label = 'available items';
} }
// Fallback to "child attributes" for enums and objects // Fallback to "child attributes" for enums and objects
if (schema.items.enum || schema.items.type === 'object') { else if (schema.items.enum || schema.items.type === 'object') {
return 'child attributes'; label = 'child attributes';
} else {
label = schema.items.title ?? schema.title ?? getSchemaTitle(schema.items);
} }
} else {
return schema.items.title ?? schema.title ?? getSchemaTitle(schema.items); label = schema.title || 'child attributes';
} }
return schema.title || 'child attributes'; return `${isExpanded ? 'Hide' : 'Show'} ${label}`;
} }
@@ -6,7 +6,7 @@ import type {
OpenAPIOperationData, OpenAPIOperationData,
OpenAPISecurityWithRequired, OpenAPISecurityWithRequired,
} from './types'; } from './types';
import { resolveDescription } from './utils'; import { createStateKey, resolveDescription } from './utils';
/** /**
* Present securities authorization that can be used for this operation. * Present securities authorization that can be used for this operation.
@@ -24,9 +24,11 @@ export function OpenAPISecurities(props: {
return ( return (
<InteractiveSection <InteractiveSection
header="Authorizations" header="Authorizations"
stateKey={createStateKey('securities', context.blockKey)}
toggeable toggeable
defaultOpened={false} defaultOpened={false}
toggleIcon={context.icons.chevronRight} toggleIcon={context.icons.chevronRight}
selectIcon={context.icons.chevronDown}
className="openapi-securities" className="openapi-securities"
tabs={securities.map(([key, security]) => { tabs={securities.map(([key, security]) => {
const description = resolveDescription(security); const description = resolveDescription(security);
+42 -16
View File
@@ -1,6 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback } from 'react';
import { import {
Button, Button,
type Key, type Key,
@@ -13,38 +14,63 @@ import {
type SelectProps, type SelectProps,
SelectValue, SelectValue,
} from 'react-aria-components'; } from 'react-aria-components';
import { useStore } from 'zustand';
import { getOrCreateStoreByKey } from './getOrCreateStoreByKey';
export type OpenAPISelectItem = { export type OpenAPISelectItem = {
key: Key; key: Key;
label: string; label: string | React.ReactNode;
}; };
interface OpenAPISelectProps<T extends OpenAPISelectItem> extends Omit<SelectProps<T>, 'children'> { interface OpenAPISelectProps<T extends OpenAPISelectItem> extends Omit<SelectProps<T>, 'children'> {
items: T[]; items: T[];
children: React.ReactNode | ((item: T) => React.ReactNode); children: React.ReactNode | ((item: T) => React.ReactNode);
selectedKey?: Key;
onChange?: (key: string | number) => void;
placement?: PopoverProps['placement']; placement?: PopoverProps['placement'];
stateKey?: string;
/**
* Icon to display in the select button.
*/
icon?: React.ReactNode;
}
export function useSelectState(stateKey = 'select-state', initialKey?: Key) {
const store = useStore(getOrCreateStoreByKey(stateKey, initialKey));
return {
key: store.key,
setKey: useCallback((key: Key | null) => store.setKey(key), [store.setKey]),
};
} }
export function OpenAPISelect<T extends OpenAPISelectItem>(props: OpenAPISelectProps<T>) { export function OpenAPISelect<T extends OpenAPISelectItem>(props: OpenAPISelectProps<T>) {
const { items, children, className, placement } = props; const {
icon = '▼',
items,
children,
className,
placement,
stateKey,
selectedKey,
onSelectionChange,
} = props;
const state = useSelectState(stateKey, items[0]?.key);
const selected = items.find((item) => item.key === state.key) || items[0];
return ( return (
<Select {...props} className={clsx('openapi-select', className)}> <Select
aria-label="OpenAPI Select"
{...props}
selectedKey={selectedKey || selected?.key}
onSelectionChange={(key) => {
onSelectionChange?.(key);
state.setKey(key);
}}
className={clsx('openapi-select', className)}
>
<Button> <Button>
<SelectValue /> <SelectValue />
<span aria-hidden="true"> {icon}
<svg
className="gb-icon"
style={{
maskImage:
"url('https://ka-p.fontawesome.com/releases/v6.6.0/svgs/regular/chevron-down.svg?v=2&token=a463935e93')",
maskRepeat: 'no-repeat',
maskPosition: 'center center',
}}
/>
</span>
</Button> </Button>
<Popover placement={placement} className="openapi-select-popover"> <Popover placement={placement} className="openapi-select-popover">
<ListBox className="openapi-select-listbox" items={items}> <ListBox className="openapi-select-listbox" items={items}>
@@ -42,6 +42,7 @@ export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAP
key="body" key="body"
requestBody={operation.requestBody} requestBody={operation.requestBody}
context={context} context={context}
data={data}
/> />
) : null} ) : null}
{operation.responses ? ( {operation.responses ? (
+37 -5
View File
@@ -42,18 +42,50 @@ export const SectionBody = forwardRef(function SectionBody(
); );
}); });
export function SectionFooter(props: ComponentPropsWithoutRef<'div'>) {
return (
<div
{...props}
className={clsx(
'openapi-section-footer',
props.className && `${props.className}-footer`
)}
/>
);
}
export function SectionFooterContent(props: ComponentPropsWithoutRef<'div'>) {
return (
<div
{...props}
className={clsx(
'openapi-section-footer-content',
props.className && `${props.className}-footer-content`
)}
/>
);
}
export function StaticSection(props: { export function StaticSection(props: {
className: string; className: string;
header: React.ReactNode; header?: React.ReactNode;
children: React.ReactNode; children: React.ReactNode;
footer?: React.ReactNode;
}) { }) {
const { className, header, children } = props; const { className, header, children, footer } = props;
return ( return (
<Section className={className}> <Section className={className}>
<SectionHeader className={className}> {header ? (
<SectionHeaderContent className={className}>{header}</SectionHeaderContent> <SectionHeader className={className}>
</SectionHeader> <SectionHeaderContent className={className}>{header}</SectionHeaderContent>
</SectionHeader>
) : null}
<SectionBody className={className}>{children}</SectionBody> <SectionBody className={className}>{children}</SectionBody>
{footer ? (
<SectionFooter className={className}>
<SectionFooterContent className={className}>{footer}</SectionFooterContent>
</SectionFooter>
) : null}
</Section> </Section>
); );
} }
@@ -413,13 +413,15 @@ describe('python code sample generator', () => {
}, },
body: { body: {
key: 'value', key: 'value',
truethy: true,
falsey: false,
}, },
}; };
const output = generator?.generate(input); const output = generator?.generate(input);
expect(output).toBe( expect(output).toBe(
'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/json"},\n data={"key":"value"}\n)\n\ndata = response.json()' 'import requests\n\nresponse = requests.get(\n "https://example.com/path",\n headers={"Content-Type":"application/json"},\n data=json.dumps({"key":"value","truethy":True,"falsey":False})\n)\n\ndata = response.json()'
); );
}); });
+67 -54
View File
@@ -3,6 +3,7 @@ import {
isFormData, isFormData,
isFormUrlEncoded, isFormUrlEncoded,
isGraphQL, isGraphQL,
isJSON,
isPDF, isPDF,
isPlainObject, isPlainObject,
isText, isText,
@@ -26,6 +27,52 @@ export interface CodeSampleGenerator {
} }
export const codeSampleGenerators: CodeSampleGenerator[] = [ export const codeSampleGenerators: CodeSampleGenerator[] = [
{
id: 'http',
label: 'HTTP',
syntax: 'bash',
generate: ({ method, url, headers = {}, body }: CodeSampleInput) => {
const { host, path } = parseHostAndPath(url);
if (body) {
// if we had a body add a content length header
const bodyContent = body ? stringifyOpenAPI(body) : '';
// handle unicode chars with a text encoder
const encoder = new TextEncoder();
const bodyString = BodyGenerators.getHTTPBody(body, headers);
if (bodyString) {
body = bodyString;
}
headers = {
...headers,
'Content-Length': encoder.encode(bodyContent).length.toString(),
};
}
if (!headers.hasOwnProperty('Accept')) {
headers.Accept = '*/*';
}
const headerString = headers
? `${Object.entries(headers)
.map(([key, value]) =>
key.toLowerCase() !== 'host' ? `${key}: ${value}` : ''
)
.join('\n')}\n`
: '';
const bodyString = body ? `\n${body}` : '';
const httpRequest = `${method.toUpperCase()} ${decodeURI(path)} HTTP/1.1
Host: ${host}
${headerString}${bodyString}`;
return httpRequest;
},
},
{ {
id: 'curl', id: 'curl',
label: 'cURL', label: 'cURL',
@@ -127,11 +174,14 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
code += indent(`headers=${stringifyOpenAPI(headers)},\n`, 4); code += indent(`headers=${stringifyOpenAPI(headers)},\n`, 4);
} }
const contentType = headers?.['Content-Type'];
if (body) { if (body) {
if (body === 'files') { if (body === 'files') {
code += indent(`files=${body}\n`, 4); code += indent(`files=${body}\n`, 4);
} else if (isJSON(contentType)) {
code += indent(`data=json.dumps(${body})\n`, 4);
} else { } else {
code += indent(`data=${stringifyOpenAPI(body)}\n`, 4); code += indent(`data=${body}\n`, 4);
} }
} }
@@ -140,52 +190,6 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
return code; return code;
}, },
}, },
{
id: 'http',
label: 'HTTP',
syntax: 'bash',
generate: ({ method, url, headers = {}, body }: CodeSampleInput) => {
const { host, path } = parseHostAndPath(url);
if (body) {
// if we had a body add a content length header
const bodyContent = body ? stringifyOpenAPI(body) : '';
// handle unicode chars with a text encoder
const encoder = new TextEncoder();
const bodyString = BodyGenerators.getHTTPBody(body, headers);
if (bodyString) {
body = bodyString;
}
headers = {
...headers,
'Content-Length': encoder.encode(bodyContent).length.toString(),
};
}
if (!headers.hasOwnProperty('Accept')) {
headers.Accept = '*/*';
}
const headerString = headers
? `${Object.entries(headers)
.map(([key, value]) =>
key.toLowerCase() !== 'host' ? `${key}: ${value}` : ''
)
.join('\n')}\n`
: '';
const bodyString = body ? `\n${body}` : '';
const httpRequest = `${method.toUpperCase()} ${decodeURI(path)} HTTP/1.1
Host: ${host}
${headerString}${bodyString}`;
return httpRequest;
},
},
]; ];
function indent(code: string, spaces: number) { function indent(code: string, spaces: number) {
@@ -343,18 +347,27 @@ const BodyGenerators = {
} }
code += '}\n\n'; code += '}\n\n';
body = 'files'; body = 'files';
} } else if (isPDF(contentType)) {
if (isPDF(contentType)) {
code += 'files = {\n'; code += 'files = {\n';
code += `${indent(`"file": "${body}",`, 4)}\n`; code += `${indent(`"file": "${body}",`, 4)}\n`;
code += '}\n\n'; code += '}\n\n';
body = 'files'; body = 'files';
} } else if (isXML(contentType)) {
if (isXML(contentType)) {
// Convert JSON to XML if needed // Convert JSON to XML if needed
body = convertBodyToXML(body); body = JSON.stringify(convertBodyToXML(body));
} else {
body = stringifyOpenAPI(body, (_key, value) => {
switch (value) {
case true:
return '$$__TRUE__$$';
case false:
return '$$__FALSE__$$';
default:
return value;
}
})
.replaceAll('"$$__TRUE__$$"', 'True')
.replaceAll('"$$__FALSE__$$"', 'False');
} }
return { body, code, headers }; return { body, code, headers };
@@ -1,5 +1,3 @@
'use client';
import { createStore } from 'zustand'; import { createStore } from 'zustand';
type Key = string | number; type Key = string | number;
@@ -10,7 +8,7 @@ type State = {
type Actions = { setKey: (key: Key | null) => void }; type Actions = { setKey: (key: Key | null) => void };
type Store = State & Actions; export type Store = State & Actions;
const createStateStore = (initial?: Key) => { const createStateStore = (initial?: Key) => {
return createStore<Store>()((set) => ({ return createStore<Store>()((set) => ({
@@ -1,6 +1,6 @@
import type { OpenAPISchema } from '@gitbook/openapi-parser'; import type { OpenAPISchema } from '@gitbook/openapi-parser';
import clsx from 'clsx'; import clsx from 'clsx';
import { OpenAPIDisclosureGroup } from '../OpenAPIDisclosureGroup'; import { OpenAPIDisclosure } from '../OpenAPIDisclosure';
import { OpenAPIExample, getExampleFromSchema } from '../OpenAPIExample'; import { OpenAPIExample, getExampleFromSchema } from '../OpenAPIExample';
import { OpenAPIRootSchema } from '../OpenAPISchemaServer'; import { OpenAPIRootSchema } from '../OpenAPISchemaServer';
import { Section, SectionBody, StaticSection } from '../StaticSection'; import { Section, SectionBody, StaticSection } from '../StaticSection';
@@ -72,33 +72,22 @@ export function OpenAPISchemas(props: {
// If there are multiple schemas, we use a disclosure group to show them all. // If there are multiple schemas, we use a disclosure group to show them all.
return ( return (
<div className={clsx('openapi-schemas', className)}> <div className={clsx('openapi-schemas', className)}>
<OpenAPIDisclosureGroup {schemas.map(({ name, schema }) => {
allowsMultipleExpanded return (
icon={context.icons.chevronRight} <OpenAPIDisclosure
groups={schemas.map(({ name, schema }) => ({ className="openapi-schemas-disclosure"
id: name, key={name}
label: ( icon={context.icons.chevronRight}
<div className="openapi-response-tab-content" key={`model-${name}`}> label={name}
<span className="openapi-response-statuscode">{name}</span> >
</div> <Section className="openapi-section-schemas">
), <SectionBody>
tabs: [ <OpenAPIRootSchema schema={schema} context={clientContext} />
{ </SectionBody>
id: 'model', </Section>
body: ( </OpenAPIDisclosure>
<Section className="openapi-section-schemas"> );
<SectionBody> })}
<OpenAPIRootSchema
schema={schema}
context={clientContext}
/>
</SectionBody>
</Section>
),
},
],
}))}
/>
</div> </div>
); );
} }
+11 -3
View File
@@ -1,17 +1,25 @@
/** /**
* Stringify an OpenAPI object. Same API as JSON.stringify. * Stringify an OpenAPI object. Same API as JSON.stringify.
*/ */
export function stringifyOpenAPI(body: unknown, _?: null, indent?: number): string { export function stringifyOpenAPI(
value: any,
replacer?: ((this: any, key: string, value: any) => any) | null,
space?: string | number
): string {
return JSON.stringify( return JSON.stringify(
body, value,
(key, value) => { (key, value) => {
// Ignore internal keys // Ignore internal keys
if (key.startsWith('x-gitbook-')) { if (key.startsWith('x-gitbook-')) {
return undefined; return undefined;
} }
if (replacer) {
return replacer(key, value);
}
return value; return value;
}, },
indent space
); );
} }
+62
View File
@@ -149,3 +149,65 @@ function shouldDisplayExample(schema: OpenAPIV3.SchemaObject): boolean {
Object.keys(schema.example).length > 0) Object.keys(schema.example).length > 0)
); );
} }
/**
* Get the class name for a status code.
* 1xx: informational
* 2xx: success
* 3xx: redirect
* 4xx, 5xx: error
*/
export function getStatusCodeClassName(statusCode: number | string): string {
const category = getStatusCodeCategory(statusCode);
switch (category) {
case 1:
return 'informational';
case 2:
return 'success';
case 3:
return 'redirect';
case 4:
case 5:
return 'error';
default:
return 'unknown';
}
}
/**
* Get a default label for a status code.
* This is used when there is no label provided in the OpenAPI spec.
* 1xx: Information
* 2xx: Success
* 3xx: Redirect
* 4xx, 5xx: Error
*/
export function getStatusCodeDefaultLabel(statusCode: number | string): string {
const category = getStatusCodeCategory(statusCode);
switch (category) {
case 1:
return 'Information';
case 2:
return 'Success';
case 3:
return 'Redirect';
case 4:
case 5:
return 'Error';
default:
return '';
}
}
function getStatusCodeCategory(statusCode: number | string): number | string {
const code = typeof statusCode === 'string' ? Number.parseInt(statusCode, 10) : statusCode;
if (Number.isNaN(code) || code < 100 || code >= 600) {
return 'unknown';
}
// Determine the category of the status code based on the first digit
const category = Math.floor(code / 100);
return category;
}