Compare commits

...

26 Commits

Author SHA1 Message Date
Samy Pessé 88ffdcc303 Version Packages (#3172)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-22 11:20:21 +02:00
Nolann B. b6b09d42af Fix OpenAPI responses select placement and icon (#3174) 2025-04-22 10:59:42 +02:00
Nolann B. ebc39e9db9 Missing select icon (#3171) 2025-04-18 16:26:59 +02:00
Samy Pessé 0df3849ea2 Version Packages (#3157)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-18 13:40:34 +02:00
Greg Bergé 8ed1bda2f2 Translate OpenAPI blocks (#3166) 2025-04-18 13:29:55 +02:00
Nolann B. d67699a1f1 Add OpenAPI Webhook block (#3160) 2025-04-18 08:35:44 +02:00
Greg Bergé 3363a18856 Merge simple alternatives (#3165) 2025-04-16 09:14:12 +00:00
Amrit Kahlon ad1dc0b914 Bump scalar version (#3161)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-04-15 18:00:07 -07: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
117 changed files with 4215 additions and 1839 deletions
@@ -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.
+2 -1
View File
@@ -20,7 +20,8 @@
"**/.wrangler/**/*", "**/.wrangler/**/*",
"packages/openapi-parser/src/fixtures/**/*", "packages/openapi-parser/src/fixtures/**/*",
"packages/emoji-codepoints/index.ts", "packages/emoji-codepoints/index.ts",
"packages/icons/src/data/*.json" "packages/icons/src/data/*.json",
"packages/cache-do/worker-configuration.d.ts"
] ]
}, },
"formatter": { "formatter": {
+185 -98
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.111.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,11 +231,11 @@
}, },
"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.19",
"@scalar/oas-utils": "^0.2.120", "@scalar/oas-utils": "^0.2.130",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"flatted": "^3.2.9", "flatted": "^3.2.9",
"json-xml-parse": "^1.3.0", "json-xml-parse": "^1.3.0",
@@ -258,7 +259,7 @@
}, },
"overrides": { "overrides": {
"@codemirror/state": "6.4.1", "@codemirror/state": "6.4.1",
"@gitbook/api": "0.109.0", "@gitbook/api": "0.111.0",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
}, },
@@ -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=="],
@@ -619,7 +628,7 @@
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="], "@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
"@gitbook/api": ["@gitbook/api@0.109.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-nzhxmiNR2MMHh+HoU7fFdOJ+u4BFyXjtsVquQf3VhZMjeI71yeLx5JEbjkOcijVd/GMAtvpYlYR2qcPqzq27gw=="], "@gitbook/api": ["@gitbook/api@0.111.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-E5Pk28kPD4p6XNWdwFM9pgDijdByseIZQqcFK+/hoW5tEZa5Yw/plRKJyN1hmwfPL6SKq6Maf0fbIzTQiVXyQQ=="],
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"], "@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
@@ -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=="],
@@ -1067,21 +1076,21 @@
"@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="], "@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
"@scalar/api-client": ["@scalar/api-client@2.3.5", "", { "dependencies": { "@headlessui/tailwindcss": "^0.2.0", "@headlessui/vue": "^1.7.20", "@scalar/components": "0.13.37", "@scalar/draggable": "0.1.11", "@scalar/icons": "0.1.3", "@scalar/import": "0.3.2", "@scalar/oas-utils": "0.2.120", "@scalar/object-utils": "1.1.13", "@scalar/openapi-parser": "0.10.10", "@scalar/openapi-types": "0.1.9", "@scalar/postman-to-openapi": "0.1.43", "@scalar/snippetz": "0.2.16", "@scalar/themes": "0.9.79", "@scalar/types": "0.1.1", "@scalar/use-codemirror": "0.11.82", "@scalar/use-hooks": "0.1.33", "@scalar/use-toasts": "0.7.9", "@scalar/use-tooltip": "1.0.6", "@vueuse/core": "^10.10.0", "@vueuse/integrations": "^11.2.0", "focus-trap": "^7", "fuse.js": "^7.0.0", "microdiff": "^1.4.0", "nanoid": "^5.0.9", "pretty-bytes": "^6.1.1", "pretty-ms": "^8.0.0", "shell-quote": "^1.8.1", "vue": "^3.5.12", "vue-router": "^4.3.0", "whatwg-mimetype": "^4.0.0", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-bVBP8H3laa4VG3dExv4Ak/Kf+q9z8aK/Glpv6CPE0wxgZJWxwkJoSrvwxUE+46JktSObUrhu4xQiqSCmBz8esQ=="], "@scalar/api-client": ["@scalar/api-client@2.3.19", "", { "dependencies": { "@headlessui/tailwindcss": "^0.2.0", "@headlessui/vue": "^1.7.20", "@scalar/components": "0.13.47", "@scalar/draggable": "0.1.11", "@scalar/icons": "0.1.3", "@scalar/import": "0.3.13", "@scalar/oas-utils": "0.2.130", "@scalar/object-utils": "1.1.13", "@scalar/openapi-parser": "0.10.14", "@scalar/openapi-types": "0.2.0", "@scalar/postman-to-openapi": "0.2.3", "@scalar/snippetz": "0.2.19", "@scalar/themes": "0.9.86", "@scalar/types": "0.1.7", "@scalar/use-codemirror": "0.11.92", "@scalar/use-hooks": "0.1.40", "@scalar/use-toasts": "0.7.9", "@scalar/use-tooltip": "1.0.6", "@vueuse/core": "^10.10.0", "@vueuse/integrations": "^11.2.0", "focus-trap": "^7", "fuse.js": "^7.0.0", "microdiff": "^1.4.0", "nanoid": "^5.1.5", "pretty-bytes": "^6.1.1", "pretty-ms": "^8.0.0", "shell-quote": "^1.8.1", "type-fest": "^4.20.0", "vue": "^3.5.12", "vue-router": "^4.3.0", "whatwg-mimetype": "^4.0.0", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-1Scff4QL6UExxcmSYv5j1dktvQZTXbmDUJp99RqmUROEhneNWEeWaZe+GZWsda5mvDoe4vP9zZKaymkulBDKYQ=="],
"@scalar/api-client-react": ["@scalar/api-client-react@1.2.5", "", { "dependencies": { "@scalar/api-client": "2.3.5", "@scalar/types": "0.1.1", "vue": "^3.5.12" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-DpVDWb1rtNeU8ZqLX2Duos018q/++b3qIOYs7MzB2cXNg1802ZO7tPywXbYyizxLuy/dziI3UwGkjBuOKKXMGQ=="], "@scalar/api-client-react": ["@scalar/api-client-react@1.2.19", "", { "dependencies": { "@scalar/api-client": "2.3.19", "@scalar/types": "0.1.7", "vue": "^3.5.12" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-zWUQvzAgOQ+oAbzswOlN42OJhwKku5RIIfBlukFMT31ceun3cp8e7+RT5DS6qwwQ2FROhSaqqsdM+o7VQ6pXkA=="],
"@scalar/code-highlight": ["@scalar/code-highlight@0.0.25", "", { "dependencies": { "hast-util-to-text": "^4.0.2", "highlight.js": "^11.9.0", "highlightjs-curl": "^1.3.0", "highlightjs-vue": "^1.0.0", "lowlight": "^3.1.0", "rehype-external-links": "^3.0.0", "rehype-format": "^5.0.0", "rehype-parse": "^9.0.0", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "rehype-stringify": "^10.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", "remark-stringify": "^11.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-rmiXaAoL3Zl+OycIO1CMj8apaeAU/p41EmCpHTxInZiFVW0++iClce2fun1lK6qjTMZneR6UwE4qBKiUUVLCpg=="], "@scalar/code-highlight": ["@scalar/code-highlight@0.0.27", "", { "dependencies": { "hast-util-to-text": "^4.0.2", "highlight.js": "^11.9.0", "highlightjs-curl": "^1.3.0", "highlightjs-vue": "^1.0.0", "lowlight": "^3.1.0", "rehype-external-links": "^3.0.0", "rehype-format": "^5.0.0", "rehype-parse": "^9.0.0", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "rehype-stringify": "^10.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", "remark-stringify": "^11.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-A61FUxqD278L+iLtdbMl4+Pg72wtMrnAYft8v1FNY44uf6UfmM47eDVmzWrc7bSvDevg3ho5QA8cKiJBHXZHJA=="],
"@scalar/components": ["@scalar/components@0.13.37", "", { "dependencies": { "@floating-ui/utils": "^0.2.2", "@floating-ui/vue": "^1.0.2", "@headlessui/vue": "^1.7.20", "@scalar/code-highlight": "0.0.25", "@scalar/themes": "0.9.79", "@scalar/use-hooks": "0.1.33", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "cva": "1.0.0-beta.2", "nanoid": "^5.0.9", "pretty-bytes": "^6.1.1", "radix-vue": "^1.9.3", "tailwind-merge": "^2.5.5", "vue": "^3.5.12" } }, "sha512-bhJxg0I63nUH0qoZgb8nyHKCSzL8L9widP2WIYymIvXpCFLwCvF64Z0CAbihwgXxq0YblPvNM+g5N3dRtmXqdA=="], "@scalar/components": ["@scalar/components@0.13.47", "", { "dependencies": { "@floating-ui/utils": "^0.2.2", "@floating-ui/vue": "^1.0.2", "@headlessui/vue": "^1.7.20", "@scalar/code-highlight": "0.0.27", "@scalar/themes": "0.9.86", "@scalar/use-hooks": "0.1.40", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "cva": "1.0.0-beta.2", "nanoid": "^5.1.5", "pretty-bytes": "^6.1.1", "radix-vue": "^1.9.3", "tailwind-merge": "^2.5.5", "vue": "^3.5.12" } }, "sha512-e88mKKsCEspd06bpPQPnhtEvCo/jjoFFOX9yUSV9sr0sWFZHi0ihq1zvnpLKpULyS+C5zzyoN/tGVhmaYpXgyg=="],
"@scalar/draggable": ["@scalar/draggable@0.1.11", "", { "dependencies": { "vue": "^3.5.12" } }, "sha512-EQW9N1+mDORhsbjdtCI3XDvmUKsuKw1uf6r3kT1Mm2zQKT+rWwA0ChsAkEN6OG62C0YumMuXpH71h1seAWptxw=="], "@scalar/draggable": ["@scalar/draggable@0.1.11", "", { "dependencies": { "vue": "^3.5.12" } }, "sha512-EQW9N1+mDORhsbjdtCI3XDvmUKsuKw1uf6r3kT1Mm2zQKT+rWwA0ChsAkEN6OG62C0YumMuXpH71h1seAWptxw=="],
"@scalar/icons": ["@scalar/icons@0.1.3", "", { "dependencies": { "vue": "^3.5.12" } }, "sha512-Bl46u7WsJ7NYjW1Fva7SMvw9c/92pGBP8B68tvDc+QevQ04DVNxw6+ny1NU/PnLtpuu1rUpPdtSCAkV1OdQGZQ=="], "@scalar/icons": ["@scalar/icons@0.1.3", "", { "dependencies": { "vue": "^3.5.12" } }, "sha512-Bl46u7WsJ7NYjW1Fva7SMvw9c/92pGBP8B68tvDc+QevQ04DVNxw6+ny1NU/PnLtpuu1rUpPdtSCAkV1OdQGZQ=="],
"@scalar/import": ["@scalar/import@0.3.2", "", { "dependencies": { "@scalar/oas-utils": "0.2.120", "@scalar/openapi-parser": "0.10.10", "yaml": "^2.4.5" } }, "sha512-de7IDZgEYOhhgaq8lFFbfiJ4Hx/ITIXvuLA8SfSl5UQKg+LxiOnnn/5PSKF+pnjWmUf7Kz/ds0eHOXpZv5iMkw=="], "@scalar/import": ["@scalar/import@0.3.13", "", { "dependencies": { "@scalar/oas-utils": "0.2.130", "@scalar/openapi-parser": "0.10.14", "yaml": "^2.4.5" } }, "sha512-ooKyRxwtvMpxBnoLt9mSJF8er5rCR6RzGJaIMRCj7ViN776eY4mbLiYcXre/LO8XfLSHb1p7XbNDhfFyTHaUlw=="],
"@scalar/oas-utils": ["@scalar/oas-utils@0.2.120", "", { "dependencies": { "@hyperjump/json-schema": "^1.9.6", "@scalar/object-utils": "1.1.13", "@scalar/openapi-types": "0.1.9", "@scalar/themes": "0.9.79", "@scalar/types": "0.1.1", "flatted": "^3.3.1", "microdiff": "^1.4.0", "nanoid": "^5.0.9", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-npu0uLClqqXVZfxMdKBWxkWCmONK0jKaUcfmVhGza9Jij5aJyvdfDw6vH/Hh+DghgECwAvQLQeIBZTxjr9ufzg=="], "@scalar/oas-utils": ["@scalar/oas-utils@0.2.130", "", { "dependencies": { "@hyperjump/json-schema": "^1.9.6", "@scalar/object-utils": "1.1.13", "@scalar/openapi-types": "0.2.0", "@scalar/themes": "0.9.86", "@scalar/types": "0.1.7", "flatted": "^3.3.1", "microdiff": "^1.4.0", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "yaml": "^2.4.5", "zod": "^3.23.8" } }, "sha512-sVpdc3+3c/WiNrKEIwzJ+ml2ZQBjarMOTDJCM/IrvYhrJE0nHrdkzxlJgNPi++vJbVl0saYt8LhEItALv7NziA=="],
"@scalar/object-utils": ["@scalar/object-utils@1.1.13", "", { "dependencies": { "flatted": "^3.3.1", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1" } }, "sha512-311eTykIXgOtjCs4VTELj9UMT97jHTWc5qkGNoIzZ5nxjCcvOVe7kDQobIkE8dGT+ybOgHz5qly02Eu7nVHeZQ=="], "@scalar/object-utils": ["@scalar/object-utils@1.1.13", "", { "dependencies": { "flatted": "^3.3.1", "just-clone": "^6.2.0", "ts-deepmerge": "^7.0.1" } }, "sha512-311eTykIXgOtjCs4VTELj9UMT97jHTWc5qkGNoIzZ5nxjCcvOVe7kDQobIkE8dGT+ybOgHz5qly02Eu7nVHeZQ=="],
@@ -1089,17 +1098,17 @@
"@scalar/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="], "@scalar/openapi-types": ["@scalar/openapi-types@0.1.9", "", {}, "sha512-HQQudOSQBU7ewzfnBW9LhDmBE2XOJgSfwrh5PlUB7zJup/kaRkBGNgV2wMjNz9Af/uztiU/xNrO179FysmUT+g=="],
"@scalar/postman-to-openapi": ["@scalar/postman-to-openapi@0.1.43", "", { "dependencies": { "@scalar/oas-utils": "0.2.120", "@scalar/openapi-types": "0.1.9" } }, "sha512-gLOkYYPCTKYFBOwyBOKZDc0seZjntmwPTchJUr3oxGQmLB1Y5VBJ+8fXJCTp5TwKiiztjALJs79y9s7jXBdWMA=="], "@scalar/postman-to-openapi": ["@scalar/postman-to-openapi@0.2.3", "", { "dependencies": { "@scalar/oas-utils": "0.2.130", "@scalar/openapi-types": "0.2.0" } }, "sha512-/I5QbDFy+Sh29EIEgub/ztI+1eNtHRn+mln726hR+uWOyVyaDk0FfNC0R4XOn8SsDNyu5eqPbXBb9vceTVG6jQ=="],
"@scalar/snippetz": ["@scalar/snippetz@0.2.16", "", { "dependencies": { "stringify-object": "^5.0.0" } }, "sha512-xtIY4kvV619IF2uXg6fDw7emtXwuJeWzLunGAaUTIOwNRw5mGSKqJnLcSnIiSdH54YmN8D2CtdJRo2VxPP9/Wg=="], "@scalar/snippetz": ["@scalar/snippetz@0.2.19", "", { "dependencies": { "stringify-object": "^5.0.0" } }, "sha512-fxC5mL3AZWiXAM21sMe1QU1/mu5KceN8ZmzFaP3xmdK26o/MkPKSLGVWW7w6OQkZi5hNloLHXXQiaI235qomEg=="],
"@scalar/themes": ["@scalar/themes@0.9.79", "", { "dependencies": { "@scalar/types": "0.1.1" } }, "sha512-zWiHCZAIjPGa8X9o/NORBPRMTMblLEz2+2RcfW9yIKNO/8H4Gz0rltiGGlJ6vX0o+qHwx7AdgfY+7njmWQR4ng=="], "@scalar/themes": ["@scalar/themes@0.9.86", "", { "dependencies": { "@scalar/types": "0.1.7" } }, "sha512-QUHo9g5oSWi+0Lm1vJY9TaMZRau8LHg+vte7q5BVTBnu6NuQfigCaN+ouQ73FqIVd96TwMO6Db+dilK1B+9row=="],
"@scalar/types": ["@scalar/types@0.1.1", "", { "dependencies": { "@scalar/openapi-types": "0.1.9", "@unhead/schema": "^1.11.11", "zod": "^3.23.8" } }, "sha512-LlUX6AmOOGoRqOMoO835V2FezM1KiO5UlvQC3poT/s7oqD6ranqwRNFxyrPz/IxClPYR+SV1yBUSNKely4ZQhQ=="], "@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="],
"@scalar/use-codemirror": ["@scalar/use-codemirror@0.11.82", "", { "dependencies": { "@codemirror/autocomplete": "^6.18.3", "@codemirror/commands": "^6.7.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.8", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.10.7", "@codemirror/lint": "^6.8.4", "@codemirror/state": "^6.5.0", "@codemirror/view": "^6.35.3", "@lezer/common": "^1.2.3", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", "@replit/codemirror-css-color-picker": "^6.3.0", "@scalar/components": "0.13.37", "codemirror": "^6.0.0", "style-mod": "^4.1.2", "vue": "^3.5.12" } }, "sha512-zFECln7aWKRf6iJO9oovByD59EsrOMenNLfLhneH6L+K1CrBoHFVr4czSDlom1wlr3HPg3xwpZrukoAteHYILQ=="], "@scalar/use-codemirror": ["@scalar/use-codemirror@0.11.92", "", { "dependencies": { "@codemirror/autocomplete": "^6.18.3", "@codemirror/commands": "^6.7.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.8", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.10.7", "@codemirror/lint": "^6.8.4", "@codemirror/state": "^6.5.0", "@codemirror/view": "^6.35.3", "@lezer/common": "^1.2.3", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", "@replit/codemirror-css-color-picker": "^6.3.0", "@scalar/components": "0.13.47", "codemirror": "^6.0.0", "style-mod": "^4.1.2", "vue": "^3.5.12" } }, "sha512-WDd50xGLV+q1T36cKzmhqYP+TyHe4MOW0tIiu09ed9aMXGUk6kQgnf0J3rYPsGeNj9IkVG3znZ7oYT3QplLGVA=="],
"@scalar/use-hooks": ["@scalar/use-hooks@0.1.33", "", { "dependencies": { "@scalar/themes": "0.9.79", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "vue": "^3.5.12", "zod": "^3.23.8" } }, "sha512-ENm0bWwRdAWWF/S6TbE+fFx0vP2mgEpG5APqQBomm0a41/6L2HJ/TN+9ajAvrJXGi0ULWuxihNS4Jue6tpEssA=="], "@scalar/use-hooks": ["@scalar/use-hooks@0.1.40", "", { "dependencies": { "@scalar/themes": "0.9.86", "@scalar/use-toasts": "0.7.9", "@vueuse/core": "^10.10.0", "vue": "^3.5.12", "zod": "^3.23.8" } }, "sha512-z8qtgIcW9Z3PCrP2cbKG+D2EVhpNgl1N0ucGtDg5SMl/fvCyXNfqB9j+u3ygxkouatfQ9zRZuhxreNMkW9/H5g=="],
"@scalar/use-toasts": ["@scalar/use-toasts@0.7.9", "", { "dependencies": { "nanoid": "^5.0.9", "vue": "^3.5.12", "vue-sonner": "^1.0.3" } }, "sha512-EcUDJY8VozLS9sfoQKvvipStQJ9RuH/nKOzf0BBr+mZDmumi1WFZ1iIJnHVXIN3iSLcSAr5ej6rOqa6jIv4bCQ=="], "@scalar/use-toasts": ["@scalar/use-toasts@0.7.9", "", { "dependencies": { "nanoid": "^5.0.9", "vue": "^3.5.12", "vue-sonner": "^1.0.3" } }, "sha512-EcUDJY8VozLS9sfoQKvvipStQJ9RuH/nKOzf0BBr+mZDmumi1WFZ1iIJnHVXIN3iSLcSAr5ej6rOqa6jIv4bCQ=="],
@@ -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=="],
@@ -2309,7 +2320,7 @@
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
"nanoid": ["nanoid@5.1.2", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-b+CiXQCNMUGe0Ri64S9SXFcP9hogjAJ2Rd6GdVxhPLRm7mhGaM7VgOvCAJ1ZshfHbqVDI3uqTI5C8/GaKuLI7g=="], "nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="],
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
@@ -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=="],
@@ -2801,7 +2812,7 @@
"type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="], "type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
"type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], "type-fest": ["type-fest@4.40.0", "", {}, "sha512-ABHZ2/tS2JkvH1PEjxFDTUWC8dB5OsIGZP4IFLhR293GqT5Y5qB1WwL2kMPYhQW9DVgVD8Hd7I8gjwPIf5GFkw=="],
"type-is": ["type-is@2.0.0", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw=="], "type-is": ["type-is@2.0.0", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw=="],
@@ -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=="],
@@ -3701,21 +3714,23 @@
"@rollup/pluginutils/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], "@rollup/pluginutils/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
"@scalar/api-client/@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.14", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-VXr979NMx6wZ+kpFKor2eyCJZOjyMwcBRc6c4Gc92ZMOC7ZNYqjwbw+Ubh2ELJyP5cWAjOFSrNwtylema0pw5w=="],
"@scalar/api-client/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
"@scalar/api-client/pretty-ms": ["pretty-ms@8.0.0", "", { "dependencies": { "parse-ms": "^3.0.0" } }, "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q=="], "@scalar/api-client/pretty-ms": ["pretty-ms@8.0.0", "", { "dependencies": { "parse-ms": "^3.0.0" } }, "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q=="],
"@scalar/api-client/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="], "@scalar/import/@scalar/openapi-parser": ["@scalar/openapi-parser@0.10.14", "", { "dependencies": { "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-formats": "^3.0.1", "jsonpointer": "^5.0.1", "leven": "^4.0.0", "yaml": "^2.4.5" } }, "sha512-VXr979NMx6wZ+kpFKor2eyCJZOjyMwcBRc6c4Gc92ZMOC7ZNYqjwbw+Ubh2ELJyP5cWAjOFSrNwtylema0pw5w=="],
"@scalar/code-highlight/remark-gfm": ["remark-gfm@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="], "@scalar/oas-utils/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
"@scalar/oas-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
"@scalar/oas-utils/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
"@scalar/object-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="], "@scalar/object-utils/flatted": ["flatted@3.3.1", "", {}, "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw=="],
"@scalar/types/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="], "@scalar/postman-to-openapi/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
"@scalar/use-hooks/zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="], "@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
"@scalar/use-toasts/nanoid": ["nanoid@5.1.2", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-b+CiXQCNMUGe0Ri64S9SXFcP9hogjAJ2Rd6GdVxhPLRm7mhGaM7VgOvCAJ1ZshfHbqVDI3uqTI5C8/GaKuLI7g=="],
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], "@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
@@ -4017,7 +4032,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.8", "", { "dependencies": { "@next/env": "15.3.1-canary.8", "@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.8", "@next/swc-darwin-x64": "15.3.1-canary.8", "@next/swc-linux-arm64-gnu": "15.3.1-canary.8", "@next/swc-linux-arm64-musl": "15.3.1-canary.8", "@next/swc-linux-x64-gnu": "15.3.1-canary.8", "@next/swc-linux-x64-musl": "15.3.1-canary.8", "@next/swc-win32-arm64-msvc": "15.3.1-canary.8", "@next/swc-win32-x64-msvc": "15.3.1-canary.8", "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-Of5a3BTTIl/iUvL2a9Jh7m7G/H8z4Pj5Vs54CLvcdadokxSNgLOpjzbDgFR8J4PawLx6+MOMy19m9Cvr6EPGug=="],
"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=="],
@@ -4067,6 +4082,8 @@
"mdast-util-gfm-task-list-item/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="], "mdast-util-gfm-task-list-item/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
"meow/type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="],
"micro/arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="], "micro/arg": ["arg@4.1.0", "", {}, "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg=="],
"micro/content-type": ["content-type@1.0.4", "", {}, "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="], "micro/content-type": ["content-type@1.0.4", "", {}, "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="],
@@ -4087,8 +4104,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 +4118,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 +4218,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 +4654,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 +4908,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.8", "", {}, "sha512-ShZTo0hNhbTRrp7k6oUDSck4Xx4hhfSeLBp35jvGaw1QMZzWYr5v/oc0kEt0bfMdl+833flwKV7kFR3BnrULfg=="],
"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.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZaDynM+pbrnLLlBAxH/CDGp9KN79OFrLcT1ejlWyo86V3SS9Gyqr4nmTuvTevByTTpr1VHReQel8Zbq0Pttu7Q=="],
"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.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-BsMR8WqeCDAX8C9RYAO8TI4ttpuqKk2oYMb1+bCrOYi857SMfB4vWD4PWmMvj7mwFXGqrxly4W6CBQlD66A+fg=="],
"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.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-xL+K2SW+/46j/KnKNf1gizM1bxwcEaE56eCEG9RPoYS/lfxHLuHcR9O2MlcPI90g/rIN2HXmHeuKRbXbnVy15g=="],
"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.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-bzXlCUXkjIRsMTb6rr7OsWEQmdO2rZgKijnMGBJzEpa9ROq95VJTF+rdyrLmHuc4fPsAGDn/C18V3E1YOi4ipQ=="],
"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.8", "", { "os": "linux", "cpu": "x64" }, "sha512-snbPQ9th7eoYYMZpNGhEvX4EbqGjjkiXzTEm2F/oDadlZR2wI6egtfQHiZqeczL7XwG3M66hBJ1/U20wdTDtvA=="],
"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.8", "", { "os": "linux", "cpu": "x64" }, "sha512-5hmcaGazc3w6rg/gbQOrmuw0kKShf4Egs0JlUPop/ZiRDfZO5KlyrGY0hUD+2pLG7Yx3B/fTbj86cHKfQZDMBw=="],
"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.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-t6uwWC/UbQ8CQGyBVbEmpcJC41yAvz2eZZGec9EoO0ZyQ/fbvjOkqfPnf+WcjWifWNeuiWogjN1UBg7YK9IaVA=="],
"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.8", "", { "os": "win32", "cpu": "x64" }, "sha512-93gNqVYwlr9bz6Pm5dQnqjpuOEQyvDre0+H39UqS7h2KuFh3sf2MejhAEr2uFEux4mb7TN0PlohaihO7YxJ3fw=="],
"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 +5124,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=="],
+1 -1
View File
@@ -12,7 +12,7 @@
"@codemirror/state": "6.4.1", "@codemirror/state": "6.4.1",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"@gitbook/api": "0.109.0" "@gitbook/api": "0.111.0"
}, },
"private": true, "private": true,
"scripts": { "scripts": {
+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.111.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",
+34
View File
@@ -1,5 +1,39 @@
# gitbook # gitbook
## 0.11.1
### Patch Changes
- Updated dependencies [ebc39e9]
- Updated dependencies [b6b09d4]
- @gitbook/react-openapi@1.2.1
## 0.11.0
### Minor Changes
- d67699a: Add OpenAPI Webhook block
### Patch Changes
- 4b8a621: Show sections tabs only if there is at least two sections
- 8ed1bda: Translate OpenAPI blocks
- 7588cfe: Improve OpenAPIResponses examples and schemas
- Updated dependencies [eeb977f]
- Updated dependencies [3363a18]
- Updated dependencies [d67699a]
- Updated dependencies [8ed1bda]
- Updated dependencies [7588cfe]
- Updated dependencies [ad1dc0b]
- @gitbook/react-openapi@1.2.0
## 0.10.1
### Patch Changes
- Updated dependencies [77397ca]
- @gitbook/cache-tags@0.3.1
## 0.10.0 ## 0.10.0
### Minor Changes ### Minor Changes
+17 -17
View File
@@ -10,18 +10,18 @@ const testCases: TestsCase[] = [
{ name: 'OpenAPI', url: '/snyk-api/reference/apps', run: waitForCookiesDialog }, { name: 'OpenAPI', url: '/snyk-api/reference/apps', run: waitForCookiesDialog },
], ],
}, },
{ // {
name: 'Nexthink', // name: 'Nexthink',
contentBaseURL: 'https://docs.nexthink.com', // contentBaseURL: 'https://docs.nexthink.com',
tests: [ // tests: [
{ // {
name: 'Home', // name: 'Home',
url: '/', // url: '/',
screenshot: { waitForTOCScrolling: false }, // screenshot: { waitForTOCScrolling: false },
run: waitForCookiesDialog, // run: waitForCookiesDialog,
}, // },
], // ],
}, // },
{ {
name: 'asiksupport-stg.dto.kemkes.go.id', name: 'asiksupport-stg.dto.kemkes.go.id',
contentBaseURL: 'https://asiksupport-stg.dto.kemkes.go.id', contentBaseURL: 'https://asiksupport-stg.dto.kemkes.go.id',
@@ -157,11 +157,11 @@ const testCases: TestsCase[] = [
contentBaseURL: 'https://wiki.redmodding.org', contentBaseURL: 'https://wiki.redmodding.org',
tests: [{ name: 'Home', url: '/' }], tests: [{ name: 'Home', url: '/' }],
}, },
{ // {
name: 'docs.cherry-ai.com', // name: 'docs.cherry-ai.com',
contentBaseURL: 'https://docs.cherry-ai.com', // contentBaseURL: 'https://docs.cherry-ai.com',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }], // tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
}, // },
{ {
name: 'docs.snyk.io', name: 'docs.snyk.io',
contentBaseURL: 'https://docs.snyk.io', contentBaseURL: 'https://docs.snyk.io',
+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.11.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: *
@@ -25,7 +25,7 @@ import { IntegrationBlock } from './Integration';
import { List } from './List'; import { List } from './List';
import { ListItem } from './ListItem'; import { ListItem } from './ListItem';
import { BlockMath } from './Math'; import { BlockMath } from './Math';
import { OpenAPIOperation, OpenAPISchemas } from './OpenAPI'; import { OpenAPIOperation, OpenAPISchemas, OpenAPIWebhook } from './OpenAPI';
import { Paragraph } from './Paragraph'; import { Paragraph } from './Paragraph';
import { Quote } from './Quote'; import { Quote } from './Quote';
import { ReusableContent } from './ReusableContent'; import { ReusableContent } from './ReusableContent';
@@ -85,6 +85,8 @@ export function Block<T extends DocumentBlock>(props: BlockProps<T>) {
return <OpenAPIOperation {...props} block={block} />; return <OpenAPIOperation {...props} block={block} />;
case 'openapi-schemas': case 'openapi-schemas':
return <OpenAPISchemas {...props} block={block} />; return <OpenAPISchemas {...props} block={block} />;
case 'openapi-webhook':
return <OpenAPIWebhook {...props} block={block} />;
case 'embed': case 'embed':
return <Embed {...props} block={block} />; return <Embed {...props} block={block} />;
case 'blockquote': case 'blockquote':
@@ -159,6 +161,7 @@ export function BlockSkeleton(props: { block: DocumentBlock; style: ClassValue }
case 'swagger': case 'swagger':
case 'openapi-operation': case 'openapi-operation':
case 'openapi-schemas': case 'openapi-schemas':
case 'openapi-webhook':
case 'math': case 'math':
case 'divider': case 'divider':
case 'content-ref': case 'content-ref':
@@ -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">
@@ -51,7 +51,7 @@ async function OpenAPIOperationBody(props: BlockProps<AnyOpenAPIOperationsBlock>
return ( return (
<BaseOpenAPIOperation <BaseOpenAPIOperation
data={data} data={data}
context={getOpenAPIContext({ props, specUrl })} context={getOpenAPIContext({ props, specUrl, context: context.contentContext })}
className="openapi-block" className="openapi-block"
/> />
); );
@@ -51,7 +51,7 @@ async function OpenAPISchemasBody(props: BlockProps<OpenAPISchemasBlock>) {
<BaseOpenAPISchemas <BaseOpenAPISchemas
schemas={data.schemas} schemas={data.schemas}
grouped={block.data.grouped} grouped={block.data.grouped}
context={getOpenAPIContext({ props, specUrl })} context={getOpenAPIContext({ props, specUrl, context: context.contentContext })}
className="openapi-block" className="openapi-block"
/> />
); );
@@ -0,0 +1,58 @@
import { OpenAPIWebhook as BaseOpenAPIWebhook } from '@gitbook/react-openapi';
import { resolveOpenAPIWebhookBlock } from '@/lib/openapi/resolveOpenAPIWebhookBlock';
import { tcls } from '@/lib/tailwind';
import type { BlockProps } from '../Block';
import './scalar.css';
import './style.css';
import type { OpenAPIWebhookBlock } from '@/lib/openapi/types';
import { getOpenAPIContext } from './context';
/**
* Render an openapi block or an openapi-webhook block.
*/
export async function OpenAPIWebhook(props: BlockProps<OpenAPIWebhookBlock>) {
const { style } = props;
return (
<div className={tcls('flex w-full min-w-0', style, 'max-w-full')}>
<OpenAPIWebhookBody {...props} />
</div>
);
}
async function OpenAPIWebhookBody(props: BlockProps<OpenAPIWebhookBlock>) {
const { block, context } = props;
if (!context.contentContext) {
return null;
}
const { data, specUrl, error } = await resolveOpenAPIWebhookBlock({
block,
context: context.contentContext,
});
if (error) {
return (
<div className="hidden">
<p>
Error with {specUrl}: {error.message}
</p>
</div>
);
}
if (!data || !specUrl) {
return null;
}
return (
<BaseOpenAPIWebhook
data={data}
context={getOpenAPIContext({ props, specUrl, context: context.contentContext })}
className="openapi-block"
/>
);
}
@@ -1,6 +1,6 @@
import type { JSONDocument } from '@gitbook/api'; import type { JSONDocument } from '@gitbook/api';
import { Icon } from '@gitbook/icons'; import { Icon } from '@gitbook/icons';
import type { OpenAPIContext } from '@gitbook/react-openapi'; import { type OpenAPIContextInput, checkIsValidLocale } from '@gitbook/react-openapi';
import { tcls } from '@/lib/tailwind'; import { tcls } from '@/lib/tailwind';
@@ -11,17 +11,31 @@ import { Heading } from '../Heading';
import './scalar.css'; import './scalar.css';
import './style.css'; import './style.css';
import type { AnyOpenAPIOperationsBlock, OpenAPISchemasBlock } from '@/lib/openapi/types'; import { DEFAULT_LOCALE, getCustomizationLocale } from '@/intl/server';
import type {
AnyOpenAPIOperationsBlock,
OpenAPISchemasBlock,
OpenAPIWebhookBlock,
} from '@/lib/openapi/types';
import type { GitBookAnyContext } from '@v2/lib/context';
/** /**
* Get the OpenAPI context to render a block. * Get the OpenAPI context to render a block.
*/ */
export function getOpenAPIContext(args: { export function getOpenAPIContext(args: {
props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock>; props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock | OpenAPIWebhookBlock>;
specUrl: string; specUrl: string;
}): OpenAPIContext { context: GitBookAnyContext | undefined;
const { props, specUrl } = args; }): OpenAPIContextInput {
const { props, specUrl, context } = args;
const { block } = props; const { block } = props;
const customization = context && 'customization' in context ? context.customization : null;
const customizationLocale = customization
? getCustomizationLocale(customization)
: DEFAULT_LOCALE;
const locale = checkIsValidLocale(customizationLocale) ? customizationLocale : DEFAULT_LOCALE;
return { return {
specUrl, specUrl,
icons: { icons: {
@@ -69,5 +83,6 @@ export function getOpenAPIContext(args: {
defaultInteractiveOpened: props.context.mode === 'print', defaultInteractiveOpened: props.context.mode === 'print',
id: block.meta?.id, id: block.meta?.id,
blockKey: block.key, blockKey: block.key,
locale,
}; };
} }
@@ -1,2 +1,3 @@
export * from './OpenAPIOperation'; export * from './OpenAPIOperation';
export * from './OpenAPISchemas'; export * from './OpenAPISchemas';
export * from './OpenAPIWebhook';
@@ -1,6 +1,7 @@
/* Layout Components */ /* Layout Components */
.openapi-operation, .openapi-operation,
.openapi-schemas { .openapi-schemas,
.openapi-webhook {
@apply flex-1 flex flex-col gap-8 mb-14 min-w-0; @apply flex-1 flex flex-col gap-8 mb-14 min-w-0;
} }
@@ -8,6 +9,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 +51,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 +91,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 +115,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 +145,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 +162,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 {
@@ -224,19 +229,19 @@
} }
.openapi-schema-required { .openapi-schema-required {
@apply text-warning-subtle text-[0.813rem]; @apply text-warning-subtle text-[0.813rem] lowercase;
} }
.openapi-schema-optional { .openapi-schema-optional {
@apply text-info-subtle text-[0.813rem]; @apply text-info-subtle text-[0.813rem] lowercase;
} }
.openapi-schema-readonly { .openapi-schema-readonly {
@apply text-primary-subtle/9 text-[0.813rem]; @apply text-primary-subtle/9 text-[0.813rem] lowercase;
} }
.openapi-schema-writeonly { .openapi-schema-writeonly {
@apply text-success dark:text-success-subtle/9 text-[0.813rem]; @apply text-success dark:text-success-subtle/9 text-[0.813rem] lowercase;
} }
.openapi-schema-type { .openapi-schema-type {
@@ -360,7 +365,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 +373,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 +389,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 +418,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 +442,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 +473,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 +504,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 +526,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 +569,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 +607,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 +652,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 +681,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 +693,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 +711,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 +735,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 +786,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 +794,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 +814,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 {
@@ -215,7 +215,7 @@ export function Header(props: { context: GitBookSiteContext; withTopHeader?: boo
/> />
</div> </div>
)} )}
{sections && ( {sections && sections.list.length > 1 && (
<SiteSectionTabs <SiteSectionTabs
sections={encodeClientSiteSections(context, sections)} sections={encodeClientSiteSections(context, sections)}
/> />
@@ -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';
+12 -3
View File
@@ -4,17 +4,26 @@ import { type TranslationLanguage, languages } from './translations';
export * from './translate'; export * from './translate';
export const DEFAULT_LOCALE = 'en';
/**
* Get the locale of the customization.
*/
export function getCustomizationLocale(customization: SiteCustomizationSettings): string {
return customization.internationalization.locale;
}
/** /**
* Create the translation context for a space to use in the server components. * Create the translation context for a space to use in the server components.
*/ */
export function getSpaceLanguage(customization: SiteCustomizationSettings): TranslationLanguage { export function getSpaceLanguage(customization: SiteCustomizationSettings): TranslationLanguage {
const fallback = languages.en; const fallback = languages[DEFAULT_LOCALE];
const { locale } = customization.internationalization; const locale = getCustomizationLocale(customization);
let language = fallback; let language = fallback;
// @ts-ignore // @ts-ignore
if (locale !== 'en' && languages[locale]) { if (locale !== DEFAULT_LOCALE && languages[locale]) {
// @ts-ignore // @ts-ignore
language = languages[locale]; language = languages[locale];
} }
+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)
);
} }
+19 -16
View File
@@ -5,17 +5,19 @@ import { type CacheFunctionOptions, cache, noCacheFetchOptions } from '@/lib/cac
import type { import type {
AnyOpenAPIOperationsBlock, AnyOpenAPIOperationsBlock,
OpenAPISchemasBlock, OpenAPISchemasBlock,
OpenAPIWebhookBlock,
ResolveOpenAPIBlockArgs, ResolveOpenAPIBlockArgs,
} 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';
import { enrichFilesystem } from './enrich'; import { enrichFilesystem } from './enrich';
import type { FetchOpenAPIFilesystemResult } from './types'; import type { FetchOpenAPIFilesystemResult } from './types';
type AnyOpenAPIBlock = AnyOpenAPIOperationsBlock | OpenAPISchemasBlock; type AnyOpenAPIBlock = AnyOpenAPIOperationsBlock | OpenAPISchemasBlock | OpenAPIWebhookBlock;
/** /**
* Fetch OpenAPI block. * Fetch OpenAPI block.
@@ -68,26 +70,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?: {
@@ -0,0 +1,61 @@
import { fetchOpenAPIFilesystem } from '@/lib/openapi/fetch';
import { OpenAPIParseError } from '@gitbook/openapi-parser';
import { type OpenAPIWebhookData, resolveOpenAPIWebhook } from '@gitbook/react-openapi';
import type {
OpenAPIWebhookBlock,
ResolveOpenAPIBlockArgs,
ResolveOpenAPIBlockResult,
} from './types';
type ResolveOpenAPIWebhookBlockResult = ResolveOpenAPIBlockResult<OpenAPIWebhookData>;
const weakmap = new WeakMap<OpenAPIWebhookBlock, Promise<ResolveOpenAPIWebhookBlockResult>>();
/**
* Cache the result of resolving an OpenAPI block.
* It is important because the resolve is called in sections and in the block itself.
*/
export function resolveOpenAPIWebhookBlock(
args: ResolveOpenAPIBlockArgs<OpenAPIWebhookBlock>
): Promise<ResolveOpenAPIWebhookBlockResult> {
if (weakmap.has(args.block)) {
return weakmap.get(args.block)!;
}
const result = baseResolveOpenAPIWebhookBlock(args);
weakmap.set(args.block, result);
return result;
}
/**
* Resolve OpenAPI webhook block.
*/
async function baseResolveOpenAPIWebhookBlock(
args: ResolveOpenAPIBlockArgs<OpenAPIWebhookBlock>
): Promise<ResolveOpenAPIWebhookBlockResult> {
const { context, block } = args;
if (!block.data.name || !block.data.method) {
return { data: null, specUrl: null };
}
try {
const { filesystem, specUrl } = await fetchOpenAPIFilesystem({ block, context });
if (!filesystem) {
return { data: null, specUrl: null };
}
const data = await resolveOpenAPIWebhook(filesystem, {
name: block.data.name,
method: block.data.method,
});
return { data, specUrl };
} catch (error) {
if (error instanceof OpenAPIParseError) {
return { error };
}
throw error;
}
}
@@ -2,6 +2,7 @@ import type {
DocumentBlockOpenAPI, DocumentBlockOpenAPI,
DocumentBlockOpenAPIOperation, DocumentBlockOpenAPIOperation,
DocumentBlockOpenAPISchemas, DocumentBlockOpenAPISchemas,
DocumentBlockOpenAPIWebhook,
} from '@gitbook/api'; } from '@gitbook/api';
import type { Filesystem, OpenAPIParseError, OpenAPIV3xDocument } from '@gitbook/openapi-parser'; import type { Filesystem, OpenAPIParseError, OpenAPIV3xDocument } from '@gitbook/openapi-parser';
import type { GitBookAnyContext } from '@v2/lib/context'; import type { GitBookAnyContext } from '@v2/lib/context';
@@ -16,6 +17,11 @@ export type AnyOpenAPIOperationsBlock = DocumentBlockOpenAPI | DocumentBlockOpen
*/ */
export type OpenAPISchemasBlock = DocumentBlockOpenAPISchemas; export type OpenAPISchemasBlock = DocumentBlockOpenAPISchemas;
/**
* Type for OpenAPI Webhook block
*/
export type OpenAPIWebhookBlock = DocumentBlockOpenAPIWebhook;
/** /**
* Arguments for resolving OpenAPI block. * Arguments for resolving OpenAPI block.
*/ */
+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"]
} }
} }
+21
View File
@@ -1,5 +1,26 @@
# @gitbook/react-openapi # @gitbook/react-openapi
## 1.2.1
### Patch Changes
- ebc39e9: Missing select icon
- b6b09d4: Fix OpenAPI responses select placement and icon
## 1.2.0
### Minor Changes
- d67699a: Add OpenAPI Webhook block
### Patch Changes
- eeb977f: Fix Python code example for JSON payload
- 3363a18: Merge simple alternatives
- 8ed1bda: Translate OpenAPI blocks
- 7588cfe: Improve OpenAPIResponses examples and schemas
- ad1dc0b: Bump scalar packages
## 1.1.10 ## 1.1.10
### Patch Changes ### Patch Changes
+3 -3
View File
@@ -8,12 +8,12 @@
"default": "./dist/index.js" "default": "./dist/index.js"
} }
}, },
"version": "1.1.10", "version": "1.2.1",
"sideEffects": false, "sideEffects": false,
"dependencies": { "dependencies": {
"@gitbook/openapi-parser": "workspace:*", "@gitbook/openapi-parser": "workspace:*",
"@scalar/api-client-react": "^1.2.5", "@scalar/api-client-react": "^1.2.19",
"@scalar/oas-utils": "^0.2.120", "@scalar/oas-utils": "^0.2.130",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"flatted": "^3.2.9", "flatted": "^3.2.9",
"json-xml-parse": "^1.3.0", "json-xml-parse": "^1.3.0",
@@ -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
@@ -97,6 +102,7 @@ export function InteractiveSection(props: {
) : null} ) : null}
{header} {header}
</SectionHeaderContent> </SectionHeaderContent>
{/* biome-ignore lint/a11y/useKeyWithClickEvents: we prevent default here */}
<div <div
className={clsx( className={clsx(
'openapi-section-header-controls', 'openapi-section-header-controls',
@@ -108,16 +114,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) => (
@@ -6,9 +6,10 @@ import {
import { OpenAPICodeSampleBody } from './OpenAPICodeSampleSelector'; import { OpenAPICodeSampleBody } from './OpenAPICodeSampleSelector';
import { ScalarApiButton } from './ScalarApiButton'; import { ScalarApiButton } from './ScalarApiButton';
import { type CodeSampleGenerator, codeSampleGenerators } from './code-samples'; import { type CodeSampleGenerator, codeSampleGenerators } from './code-samples';
import { type OpenAPIContext, getOpenAPIClientContext } from './context';
import { generateMediaTypeExamples, generateSchemaExample } from './generateSchemaExample'; import { generateMediaTypeExamples, generateSchemaExample } from './generateSchemaExample';
import { stringifyOpenAPI } from './stringifyOpenAPI'; import { stringifyOpenAPI } from './stringifyOpenAPI';
import type { OpenAPIContext, OpenAPIOperationData } from './types'; import type { OpenAPIOperationData } from './types';
import { getDefaultServerURL } from './util/server'; import { getDefaultServerURL } from './util/server';
import { checkIsReference } from './utils'; import { checkIsReference } from './utils';
@@ -22,7 +23,7 @@ export function OpenAPICodeSample(props: {
data: OpenAPIOperationData; data: OpenAPIOperationData;
context: OpenAPIContext; context: OpenAPIContext;
}) { }) {
const { data } = props; const { data, context } = props;
// If code samples are disabled at operation level, we don't display the code samples. // If code samples are disabled at operation level, we don't display the code samples.
if (data.operation['x-codeSamples'] === false) { if (data.operation['x-codeSamples'] === false) {
@@ -43,7 +44,14 @@ export function OpenAPICodeSample(props: {
return null; return null;
} }
return <OpenAPICodeSampleBody data={data} items={samples} />; return (
<OpenAPICodeSampleBody
context={getOpenAPIClientContext(context)}
data={data}
items={samples}
selectIcon={context.icons.chevronDown}
/>
);
} }
/** /**
@@ -146,6 +154,7 @@ function generateCodeSamples(props: {
method={data.method} method={data.method}
path={data.path} path={data.path}
renderers={renderers} renderers={renderers}
blockKey={context.blockKey}
/> />
), ),
footer: ( footer: (
@@ -206,11 +215,20 @@ function OpenAPICodeSampleFooter(props: {
method={data.method} method={data.method}
path={data.path} path={data.path}
renderers={renderers} renderers={renderers}
selectIcon={context.icons.chevronDown}
blockKey={context.blockKey}
/> />
) : ( ) : (
<span /> <span />
)} )}
{!hideTryItPanel && <ScalarApiButton method={method} path={path} specUrl={specUrl} />} {!hideTryItPanel && (
<ScalarApiButton
context={getOpenAPIClientContext(context)}
method={method}
path={path}
specUrl={specUrl}
/>
)}
</div> </div>
); );
} }
@@ -1,70 +1,43 @@
'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'; import { createStateKey } from './utils';
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;
blockKey?: string;
}) { }) {
const { method, path, renderers } = props; const { method, path, renderers, selectIcon, blockKey } = 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 = createStateKey('request-body-media-type', blockKey);
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 +51,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 +72,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 +87,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) => (
@@ -131,14 +104,18 @@ export function OpenAPIMediaTypeExamplesBody(props: {
method: string; method: string;
path: string; path: string;
renderers: MediaTypeRenderer[]; renderers: MediaTypeRenderer[];
blockKey?: string;
}) { }) {
const { renderers, method, path } = props; const { renderers, method, path, blockKey } = 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(
renderers.find((r) => r.mediaType === mediaTypeState.mediaType) ?? renderers[0]; createStateKey('request-body-media-type', blockKey),
renderers[0].mediaType
);
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 +124,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;
} }
@@ -6,6 +6,7 @@ import { useStore } from 'zustand';
import { OpenAPIPath } from './OpenAPIPath'; import { OpenAPIPath } from './OpenAPIPath';
import { OpenAPISelect, OpenAPISelectItem } from './OpenAPISelect'; import { OpenAPISelect, OpenAPISelectItem } from './OpenAPISelect';
import { StaticSection } from './StaticSection'; import { StaticSection } from './StaticSection';
import type { OpenAPIClientContext } from './context';
import { getOrCreateStoreByKey } from './getOrCreateStoreByKey'; import { getOrCreateStoreByKey } from './getOrCreateStoreByKey';
import type { OpenAPIOperationData } from './types'; import type { OpenAPIOperationData } from './types';
@@ -22,25 +23,22 @@ 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;
context: OpenAPIClientContext;
}) { }) {
const { data, items } = props; const { data, items, selectIcon, context } = 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 context={context} 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 +57,10 @@ export function OpenAPICodeSampleHeader(props: {
export function OpenAPICodeSampleBody(props: { export function OpenAPICodeSampleBody(props: {
items: CodeSampleItem[]; items: CodeSampleItem[];
data: OpenAPIOperationData; data: OpenAPIOperationData;
selectIcon?: React.ReactNode;
context: OpenAPIClientContext;
}) { }) {
const { items, data } = props; const { items, data, selectIcon, context } = props;
if (!items[0]) { if (!items[0]) {
throw new Error('No items provided'); throw new Error('No items provided');
} }
@@ -75,7 +75,14 @@ export function OpenAPICodeSampleBody(props: {
return ( return (
<StaticSection <StaticSection
header={<OpenAPICodeSampleHeader data={data} items={items} />} header={
<OpenAPICodeSampleHeader
context={context}
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">
@@ -2,11 +2,14 @@
import { useState } from 'react'; import { useState } from 'react';
import { Button, type ButtonProps, Tooltip, TooltipTrigger } from 'react-aria-components'; import { Button, type ButtonProps, Tooltip, TooltipTrigger } from 'react-aria-components';
import type { OpenAPIClientContext } from './context';
import { t } from './translate';
export function OpenAPICopyButton( export function OpenAPICopyButton(
props: ButtonProps & { props: ButtonProps & {
value: string; value: string;
children: React.ReactNode; children: React.ReactNode;
context: OpenAPIClientContext;
label?: string; label?: string;
/** /**
* Whether to show a tooltip. * Whether to show a tooltip.
@@ -15,7 +18,7 @@ export function OpenAPICopyButton(
withTooltip?: boolean; withTooltip?: boolean;
} }
) { ) {
const { value, label, children, onPress, className, withTooltip = true } = props; const { value, label, children, onPress, className, context, withTooltip = true } = props;
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@@ -60,7 +63,9 @@ export function OpenAPICopyButton(
offset={4} offset={4}
className="openapi-tooltip" className="openapi-tooltip"
> >
{copied ? 'Copied' : label || 'Copy to clipboard'} {copied
? t(context.translation, 'copied')
: label || t(context.translation, 'copy_to_clipboard')}
</Tooltip> </Tooltip>
</TooltipTrigger> </TooltipTrigger>
); );
@@ -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,26 @@ 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}
placement="bottom end"
> >
{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}
+8 -82
View File
@@ -1,9 +1,8 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser'; import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { generateSchemaExample } from './generateSchemaExample'; import type { OpenAPIContext, OpenAPIUniversalContext } from './context';
import { json2xml } from './json2xml'; import { json2xml } from './json2xml';
import { stringifyOpenAPI } from './stringifyOpenAPI'; import { stringifyOpenAPI } from './stringifyOpenAPI';
import type { OpenAPIContext } from './types'; import { t } from './translate';
import { checkIsReference } from './utils';
/** /**
* Display an example. * Display an example.
@@ -17,7 +16,7 @@ export function OpenAPIExample(props: {
const code = stringifyExample({ example, xml: syntax === 'xml' }); const code = stringifyExample({ example, xml: syntax === 'xml' });
if (code === null) { if (code === null) {
return <OpenAPIEmptyExample />; return <OpenAPIEmptyExample context={context} />;
} }
return context.renderCodeBlock({ code, syntax }); return context.renderCodeBlock({ code, syntax });
@@ -44,86 +43,13 @@ function stringifyExample(args: { example: OpenAPIV3.ExampleObject; xml: boolean
/** /**
* Empty response example. * Empty response example.
*/ */
export function OpenAPIEmptyExample() { export function OpenAPIEmptyExample(props: {
context: OpenAPIUniversalContext;
}) {
const { context } = props;
return ( return (
<pre className="openapi-example-empty"> <pre className="openapi-example-empty">
<p>No Content</p> <p>{t(context.translation, 'no_content')}</p>
</pre> </pre>
); );
} }
/**
* Generate an example from a reference object.
*/
export function getExampleFromReference(ref: OpenAPIV3.ReferenceObject): OpenAPIV3.ExampleObject {
return { summary: 'Unresolved reference', value: { $ref: ref.$ref } };
}
/**
* Get examples from a media type object.
*/
export function getExamplesFromMediaTypeObject(args: {
mediaType: string;
mediaTypeObject: OpenAPIV3.MediaTypeObject;
}): { key: string; example: OpenAPIV3.ExampleObject }[] {
const { mediaTypeObject, mediaType } = args;
if (mediaTypeObject.examples) {
return Object.entries(mediaTypeObject.examples).map(([key, example]) => {
return {
key,
example: checkIsReference(example) ? getExampleFromReference(example) : example,
};
});
}
if (mediaTypeObject.example) {
return [{ key: 'default', example: { value: mediaTypeObject.example } }];
}
if (mediaTypeObject.schema) {
if (mediaType === 'application/xml') {
// @TODO normally we should use the name of the schema but we don't have it
// fix it when we got the reference name
const root = mediaTypeObject.schema.xml?.name ?? 'object';
return [
{
key: 'default',
example: {
value: {
[root]: generateSchemaExample(mediaTypeObject.schema, {
xml: mediaType === 'application/xml',
mode: 'read',
}),
},
},
},
];
}
return [
{
key: 'default',
example: {
value: generateSchemaExample(mediaTypeObject.schema, {
mode: 'read',
}),
},
},
];
}
return [];
}
/**
* Get example from a schema object.
*/
export function getExampleFromSchema(args: {
schema: OpenAPIV3.SchemaObject;
}): OpenAPIV3.ExampleObject {
const { schema } = args;
if (schema.example) {
return { value: schema.example };
}
return { value: generateSchemaExample(schema, { mode: 'read' }) };
}
@@ -0,0 +1,139 @@
'use client';
import type { Key } from 'react-aria';
import { OpenAPIEmptyExample } from './OpenAPIExample';
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from './OpenAPISelect';
import { StaticSection } from './StaticSection';
import type { OpenAPIClientContext } from './context';
type OpenAPIMediaTypeItem = OpenAPISelectItem & {
body: React.ReactNode;
examples?: OpenAPIMediaTypeItem[];
};
/**
* Get the state of the response examples select.
*/
export function useMediaTypesState(stateKey: string | undefined, initialKey: Key = 'default') {
return useSelectState(stateKey, initialKey);
}
function useMediaTypeExamplesState(stateKey: string | undefined, initialKey: Key = 'default') {
return useSelectState(stateKey, initialKey);
}
export function OpenAPIMediaTypeContent(props: {
items: OpenAPIMediaTypeItem[];
selectIcon?: React.ReactNode;
stateKey: string;
context: OpenAPIClientContext;
}) {
const { stateKey, items, selectIcon, context } = props;
const state = useMediaTypesState(stateKey, 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 ? (
<OpenAPIMediaTypeFooter
items={items}
examples={examples}
selectIcon={selectIcon}
stateKey={stateKey}
/>
) : null
}
className="openapi-response-media-types-examples"
>
<OpenAPIMediaTypeBody
context={context}
stateKey={stateKey}
items={items}
examples={examples}
/>
</StaticSection>
);
}
function OpenAPIMediaTypeFooter(props: {
items: OpenAPIMediaTypeItem[];
examples?: OpenAPIMediaTypeItem[];
selectIcon?: React.ReactNode;
stateKey: string;
}) {
const { items, examples, stateKey, selectIcon } = props;
return (
<>
{items.length > 1 && (
<OpenAPISelect
icon={selectIcon}
items={items}
stateKey={stateKey}
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={`${stateKey}-examples`}
placement="bottom start"
>
{examples.map((example) => (
<OpenAPISelectItem key={example.key} id={example.key} value={example}>
<span>{example.label}</span>
</OpenAPISelectItem>
))}
</OpenAPISelect>
) : null}
</>
);
}
function OpenAPIMediaTypeBody(props: {
items: OpenAPIMediaTypeItem[];
examples?: OpenAPIMediaTypeItem[];
stateKey: string;
context: OpenAPIClientContext;
}) {
const { stateKey, items, examples, context } = props;
const state = useMediaTypesState(stateKey, items[0]?.key);
const selectedItem = items.find((item) => item.key === state.key) ?? items[0];
const exampleState = useMediaTypeExamplesState(
`${stateKey}-examples`,
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 context={context} />;
}
return selectedExample.body;
}
return selectedItem.body;
}
+11 -100
View File
@@ -1,18 +1,10 @@
import clsx from 'clsx'; import clsx from 'clsx';
import type {
OpenAPICustomOperationProperties,
OpenAPIStability,
OpenAPIV3,
} from '@gitbook/openapi-parser';
import { Markdown } from './Markdown';
import { OpenAPICodeSample } from './OpenAPICodeSample'; import { OpenAPICodeSample } from './OpenAPICodeSample';
import { OpenAPIPath } from './OpenAPIPath';
import { OpenAPIResponseExample } from './OpenAPIResponseExample'; import { OpenAPIResponseExample } from './OpenAPIResponseExample';
import { OpenAPISpec } from './OpenAPISpec'; import { OpenAPIColumnSpec } from './common/OpenAPIColumnSpec';
import { getOpenAPIClientContext } from './context'; import { OpenAPISummary } from './common/OpenAPISummary';
import type { OpenAPIContext, OpenAPIOperationData } from './types'; import { type OpenAPIContextInput, resolveOpenAPIContext } from './context';
import { resolveDescription } from './utils'; import type { OpenAPIOperationData } from './types';
/** /**
* Display an interactive OpenAPI operation. * Display an interactive OpenAPI operation.
@@ -20,105 +12,24 @@ import { resolveDescription } from './utils';
export function OpenAPIOperation(props: { export function OpenAPIOperation(props: {
className?: string; className?: string;
data: OpenAPIOperationData; data: OpenAPIOperationData;
context: OpenAPIContext; context: OpenAPIContextInput;
}) { }) {
const { className, data, context } = props; const { className, data, context: contextInput } = props;
const { operation } = data;
const clientContext = getOpenAPIClientContext(context); const context = resolveOpenAPIContext(contextInput);
return ( return (
<div className={clsx('openapi-operation', className)}> <div className={clsx('openapi-operation', className)}>
<div className="openapi-summary" id={operation.summary ? undefined : context.id}> <OpenAPISummary data={data} context={context} />
{(operation.deprecated || operation['x-stability']) && (
<div className="openapi-summary-tags">
{operation.deprecated && (
<div className="openapi-deprecated">Deprecated</div>
)}
{operation['x-stability'] && (
<OpenAPIOperationStability stability={operation['x-stability']} />
)}
</div>
)}
{operation.summary
? context.renderHeading({
deprecated: operation.deprecated ?? false,
stability: operation['x-stability'],
title: operation.summary,
})
: null}
<OpenAPIPath data={data} />
</div>
<div className="openapi-columns"> <div className="openapi-columns">
<div className="openapi-column-spec"> <OpenAPIColumnSpec data={data} context={context} />
{operation['x-deprecated-sunset'] ? (
<div className="openapi-deprecated-sunset openapi-description openapi-markdown">
This operation is deprecated and will be sunset on{' '}
<span className="openapi-deprecated-sunset-date">
{operation['x-deprecated-sunset']}
</span>
{'.'}
</div>
) : null}
<OpenAPIOperationDescription operation={operation} context={context} />
<OpenAPISpec data={data} context={clientContext} />
</div>
<div className="openapi-column-preview"> <div className="openapi-column-preview">
<div className="openapi-column-preview-body"> <div className="openapi-column-preview-body">
<OpenAPICodeSample {...props} /> <OpenAPICodeSample data={data} context={context} />
<OpenAPIResponseExample {...props} /> <OpenAPIResponseExample data={data} context={context} />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); );
} }
function OpenAPIOperationDescription(props: {
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
context: OpenAPIContext;
}) {
const { operation } = props;
if (operation['x-gitbook-description-document']) {
return (
<div className="openapi-intro">
{props.context.renderDocument({
document: operation['x-gitbook-description-document'],
})}
</div>
);
}
const description = resolveDescription(operation);
if (!description) {
return null;
}
return (
<div className="openapi-intro">
<Markdown className="openapi-description" source={description} />
</div>
);
}
const stabilityEnum = {
experimental: 'Experimental',
alpha: 'Alpha',
beta: 'Beta',
} as const;
function OpenAPIOperationStability(props: { stability: OpenAPIStability }) {
const { stability } = props;
const foundStability = stabilityEnum[stability];
if (!foundStability) {
return null;
}
return (
<div className={`openapi-stability openapi-stability-${foundStability.toLowerCase()}`}>
{foundStability}
</div>
);
}
@@ -0,0 +1,34 @@
import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
import { Markdown } from './Markdown';
import type { OpenAPIContext } from './context';
import { resolveDescription } from './utils';
/**
* Display the description of an OpenAPI operation.
*/
export function OpenAPIOperationDescription(props: {
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
context: OpenAPIContext;
}) {
const { operation } = props;
if (operation['x-gitbook-description-document']) {
return (
<div className="openapi-intro">
{props.context.renderDocument({
document: operation['x-gitbook-description-document'],
})}
</div>
);
}
const description = resolveDescription(operation);
if (!description) {
return null;
}
return (
<div className="openapi-intro">
<Markdown className="openapi-description" source={description} />
</div>
);
}
@@ -0,0 +1,39 @@
import type { OpenAPIStability } from '@gitbook/openapi-parser';
import type { OpenAPIContext } from './context';
import { t } from './translate';
/**
* Display the stability of an OpenAPI operation.
*/
export function OpenAPIOperationStability(props: {
stability: OpenAPIStability;
context: OpenAPIContext;
}) {
const { stability, context } = props;
const stabilityLabel = getStabilityLabel(stability, context);
if (!stabilityLabel) {
return null;
}
return (
<div className={`openapi-stability openapi-stability-${stability}`}>{stabilityLabel}</div>
);
}
/**
* Get the stability label for the given stability level.
*/
function getStabilityLabel(stability: OpenAPIStability, context: OpenAPIContext) {
switch (stability) {
case 'experimental':
return t(context.translation, 'stability_experimental');
case 'alpha':
return t(context.translation, 'stability_alpha');
case 'beta':
return t(context.translation, 'stability_beta');
default:
return null;
}
}
+4 -1
View File
@@ -1,4 +1,5 @@
import { OpenAPICopyButton } from './OpenAPICopyButton'; import { OpenAPICopyButton } from './OpenAPICopyButton';
import { type OpenAPIUniversalContext, getOpenAPIClientContext } from './context';
import type { OpenAPIOperationData } from './types'; import type { OpenAPIOperationData } from './types';
import { getDefaultServerURL } from './util/server'; import { getDefaultServerURL } from './util/server';
@@ -7,6 +8,7 @@ import { getDefaultServerURL } from './util/server';
*/ */
export function OpenAPIPath(props: { export function OpenAPIPath(props: {
data: OpenAPIOperationData; data: OpenAPIOperationData;
context: OpenAPIUniversalContext;
/** Whether to show the server URL. /** Whether to show the server URL.
* @default true * @default true
*/ */
@@ -17,7 +19,7 @@ export function OpenAPIPath(props: {
*/ */
canCopy?: boolean; canCopy?: boolean;
}) { }) {
const { data, withServer = true, canCopy = true } = props; const { data, context, withServer = true, canCopy = true } = props;
const { method, path, operation } = data; const { method, path, operation } = data;
const server = getDefaultServerURL(data.servers); const server = getDefaultServerURL(data.servers);
@@ -41,6 +43,7 @@ export function OpenAPIPath(props: {
className="openapi-path-title" className="openapi-path-title"
data-deprecated={operation.deprecated} data-deprecated={operation.deprecated}
isDisabled={!canCopy} isDisabled={!canCopy}
context={getOpenAPIClientContext(context)}
> >
{element} {element}
</OpenAPICopyButton> </OpenAPICopyButton>
@@ -1,8 +1,10 @@
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 } from './context';
import { checkIsReference } from './utils'; import { t } from './translate';
import type { OpenAPIOperationData, OpenAPIWebhookData } from './types';
import { checkIsReference, createStateKey } from './utils';
/** /**
* Display an interactive request body. * Display an interactive request body.
@@ -10,8 +12,9 @@ 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 | OpenAPIWebhookData;
}) { }) {
const { requestBody, context } = props; const { requestBody, context, data } = props;
if (checkIsReference(requestBody)) { if (checkIsReference(requestBody)) {
return null; return null;
@@ -19,8 +22,10 @@ export function OpenAPIRequestBody(props: {
return ( return (
<InteractiveSection <InteractiveSection
header="Body" header={t(context.translation, 'name' in data ? 'payload' : 'body')}
className="openapi-requestbody" className="openapi-requestbody"
stateKey={createStateKey('request-body-media-type', context.blockKey)}
selectIcon={context.icons.chevronDown}
tabs={Object.entries(requestBody.content ?? {}).map( tabs={Object.entries(requestBody.content ?? {}).map(
([contentType, mediaTypeObject]) => { ([contentType, mediaTypeObject]) => {
return { return {
@@ -1,7 +1,7 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser'; import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { OpenAPIDisclosure } from './OpenAPIDisclosure'; import { OpenAPIDisclosure } from './OpenAPIDisclosure';
import { OpenAPISchemaProperties } from './OpenAPISchemaServer'; import { OpenAPISchemaProperties } from './OpenAPISchemaServer';
import type { OpenAPIClientContext } from './types'; import type { OpenAPIClientContext } from './context';
import { parameterToProperty, resolveDescription } from './utils'; import { parameterToProperty, resolveDescription } from './utils';
/** /**
@@ -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 })
@@ -1,21 +1,19 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser'; import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { Markdown } from './Markdown'; import { Markdown } from './Markdown';
import { import { OpenAPIEmptyExample, OpenAPIExample } from './OpenAPIExample';
OpenAPIEmptyExample, import { OpenAPIMediaTypeContent } from './OpenAPIMediaType';
OpenAPIExample, import { OpenAPIResponseExampleContent } from './OpenAPIResponseExampleContent';
getExampleFromReference, import { type OpenAPIContext, getOpenAPIClientContext } from './context';
getExamplesFromMediaTypeObject, import type { OpenAPIOperationData, OpenAPIWebhookData } from './types';
} from './OpenAPIExample'; import { getExampleFromReference, getExamples } from './util/example';
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs'; import { createStateKey, getStatusCodeDefaultLabel } from './utils';
import { StaticSection } from './StaticSection'; import { checkIsReference, resolveDescription } from './utils';
import type { OpenAPIContext, OpenAPIOperationData } from './types';
import { checkIsReference, createStateKey, resolveDescription } from './utils';
/** /**
* Display an example of the response content. * Display an example of the response content.
*/ */
export function OpenAPIResponseExample(props: { export function OpenAPIResponseExample(props: {
data: OpenAPIOperationData; data: OpenAPIOperationData | OpenAPIWebhookData;
context: OpenAPIContext; context: OpenAPIContext;
}) { }) {
const { data, context } = props; const { data, context } = props;
@@ -45,36 +43,41 @@ 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, context)
);
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, context)}
context={context} context={context}
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,
body: <OpenAPIEmptyExample />, statusCode: key,
footer: description ? <Markdown source={description} /> : undefined, body: <OpenAPIEmptyExample context={context} />,
}; };
} }
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 +86,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,98 +109,26 @@ 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}> <OpenAPIMediaTypeContent
<StaticSection header={<OpenAPITabsList />} className="openapi-response-media-types"> selectIcon={context.icons.chevronDown}
<OpenAPITabsPanels /> stateKey={createStateKey('response-media-types', context.blockKey)}
</StaticSection> items={tabs}
</OpenAPITabs> context={getOpenAPIClientContext(context)}
/>
); );
} }
function OpenAPIResponseMediaType(props: {
mediaTypeObject: OpenAPIV3.MediaTypeObject;
mediaType: string;
context: OpenAPIContext;
}) {
const { mediaTypeObject, mediaType } = props;
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
const syntax = getSyntaxFromMediaType(mediaType);
const firstExample = examples[0];
if (!firstExample) {
return <OpenAPIEmptyExample />;
}
if (examples.length === 1) {
return (
<OpenAPIExample
example={firstExample.example}
context={props.context}
syntax={syntax}
/>
);
}
const tabs = examples.map((example) => {
return {
key: example.key,
label: example.example.summary || example.key,
body: (
<OpenAPIExample example={example.example} context={props.context} syntax={syntax} />
),
};
});
return (
<OpenAPITabs stateKey={createStateKey('response-media-type-examples')} items={tabs}>
<StaticSection
header={<OpenAPITabsList />}
className="openapi-response-media-type-examples"
>
<OpenAPITabsPanels />
</StaticSection>
</OpenAPITabs>
);
}
/**
* Get the syntax from a media type.
*/
function getSyntaxFromMediaType(mediaType: string): string {
if (mediaType.includes('json')) {
return 'json';
}
if (mediaType === 'application/xml') {
return 'xml';
}
return 'text';
}
@@ -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);
}
+86 -64
View File
@@ -1,9 +1,15 @@
'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 './context';
import { t } from './translate';
import { createStateKey, getStatusCodeClassName, getStatusCodeDefaultLabel } from './utils';
/** /**
* Display an interactive response body. * Display an interactive response body.
@@ -14,70 +20,86 @@ export function OpenAPIResponses(props: {
}) { }) {
const { responses, context } = props; const { responses, context } = props;
return ( const groups = Object.entries(responses).map(
<StaticSection header="Responses" className="openapi-responses"> ([statusCode, response]: [string, OpenAPIV3.ResponseObject]) => {
<OpenAPIDisclosureGroup const tabs = (() => {
allowsMultipleExpanded // If there is no content, but there are headers, we need to show the headers
icon={context.icons.chevronRight} if (
groups={Object.entries(responses).map( (!response.content || !Object.keys(response.content).length) &&
([statusCode, response]: [string, OpenAPIV3.ResponseObject]) => { response.headers &&
const tabs = (() => { Object.keys(response.headers).length
// If there is no content, but there are headers, we need to show the headers ) {
if ( return [
(!response.content || !Object.keys(response.content).length) && {
response.headers && key: 'default',
Object.keys(response.headers).length label: '',
) { body: (
return [ <OpenAPIResponse
{ response={response}
id: 'default', mediaType={{}}
body: ( context={context}
<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, },
}; ];
} }
)}
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, context)
)}
</div>
),
tabs,
};
}
);
const state = useResponseExamplesState(context.blockKey, groups[0]?.key);
return (
<StaticSection header={t(context.translation, 'responses')} className="openapi-responses">
<OpenAPIDisclosureGroup
icon={context.icons.chevronRight}
expandedKeys={state.key ? new Set([state.key]) : new Set()}
onExpandedChange={(keys) => {
const key = keys.values().next().value ?? null;
state.setKey(key);
}}
groups={groups}
selectIcon={context.icons.chevronDown}
selectStateKey={createStateKey('response-media-types', context.blockKey)}
/> />
</StaticSection> </StaticSection>
); );
@@ -35,6 +35,86 @@ describe('getSchemaAlternatives', () => {
]); ]);
}); });
it('merges string enum', () => {
expect(
getSchemaAlternatives({
oneOf: [
{
oneOf: [
{
type: 'string',
enum: ['a', 'b'],
},
{
type: 'string',
enum: ['c', 'd'],
nullable: true,
},
],
},
],
})
).toEqual([
{
type: 'string',
enum: ['a', 'b', 'c', 'd'],
nullable: true,
},
]);
});
it('merges objects with allOf', () => {
expect(
getSchemaAlternatives({
allOf: [
{
type: 'object',
properties: {
name: {
type: 'string',
},
map: {
type: 'string',
},
description: {
type: 'string',
},
},
required: ['name'],
},
{
type: 'object',
properties: {
externalId: {
type: 'string',
},
},
required: ['map', 'externalId'],
},
],
})
).toEqual([
{
type: 'object',
properties: {
name: {
type: 'string',
},
map: {
type: 'string',
},
description: {
type: 'string',
},
externalId: {
type: 'string',
},
},
required: ['name', 'map', 'externalId'],
},
]);
});
it('should not flatten oneOf and allOf', () => { it('should not flatten oneOf and allOf', () => {
expect( expect(
getSchemaAlternatives({ getSchemaAlternatives({
+123 -16
View File
@@ -10,9 +10,10 @@ import { Markdown } from './Markdown';
import { OpenAPICopyButton } from './OpenAPICopyButton'; import { OpenAPICopyButton } from './OpenAPICopyButton';
import { OpenAPIDisclosure } from './OpenAPIDisclosure'; import { OpenAPIDisclosure } from './OpenAPIDisclosure';
import { OpenAPISchemaName } from './OpenAPISchemaName'; import { OpenAPISchemaName } from './OpenAPISchemaName';
import type { OpenAPIClientContext } from './context';
import { retrocycle } from './decycle'; import { retrocycle } from './decycle';
import { stringifyOpenAPI } from './stringifyOpenAPI'; import { stringifyOpenAPI } from './stringifyOpenAPI';
import type { OpenAPIClientContext } from './types'; import { tString } from './translate';
import { checkIsReference, resolveDescription, resolveFirstExample } from './utils'; import { checkIsReference, resolveDescription, resolveFirstExample } from './utils';
type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>; type CircularRefsIds = Map<OpenAPIV3.SchemaObject, string>;
@@ -40,7 +41,7 @@ function OpenAPISchemaProperty(props: {
return ( return (
<div id={id} className={clsx('openapi-schema', className)}> <div id={id} className={clsx('openapi-schema', className)}>
<OpenAPISchemaPresentation property={property} /> <OpenAPISchemaPresentation context={context} property={property} />
{(() => { {(() => {
const circularRefId = parentCircularRefs.get(schema); const circularRefId = parentCircularRefs.get(schema);
// Avoid recursing infinitely, and instead render a link to the parent schema // Avoid recursing infinitely, and instead render a link to the parent schema
@@ -54,7 +55,12 @@ 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, context })
}
>
<OpenAPISchemaProperties <OpenAPISchemaProperties
properties={properties} properties={properties}
circularRefs={circularRefs} circularRefs={circularRefs}
@@ -208,7 +214,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, context })}
>
{properties?.length ? ( {properties?.length ? (
<OpenAPISchemaProperties <OpenAPISchemaProperties
properties={properties} properties={properties}
@@ -246,8 +255,9 @@ function OpenAPISchemaCircularRef(props: { id: string; schema: OpenAPIV3.SchemaO
*/ */
function OpenAPISchemaEnum(props: { function OpenAPISchemaEnum(props: {
schema: OpenAPIV3.SchemaObject & OpenAPICustomOperationProperties; schema: OpenAPIV3.SchemaObject & OpenAPICustomOperationProperties;
context: OpenAPIClientContext;
}) { }) {
const { schema } = props; const { schema, context } = props;
const enumValues = (() => { const enumValues = (() => {
// Render x-gitbook-enum first, as it has a different format // Render x-gitbook-enum first, as it has a different format
@@ -290,6 +300,7 @@ function OpenAPISchemaEnum(props: {
value={item.value} value={item.value}
label={item.description} label={item.description}
withTooltip={!!item.description} withTooltip={!!item.description}
context={context}
> >
<code>{`${item.value}`}</code> <code>{`${item.value}`}</code>
</OpenAPICopyButton> </OpenAPICopyButton>
@@ -302,9 +313,13 @@ function OpenAPISchemaEnum(props: {
/** /**
* Render the top row of a schema. e.g: name, type, and required status. * Render the top row of a schema. e.g: name, type, and required status.
*/ */
function OpenAPISchemaPresentation(props: { property: OpenAPISchemaPropertyEntry }) { function OpenAPISchemaPresentation(props: {
property: OpenAPISchemaPropertyEntry;
context: OpenAPIClientContext;
}) {
const { const {
property: { schema, propertyName, required }, property: { schema, propertyName, required },
context,
} = props; } = props;
const description = resolveDescription(schema); const description = resolveDescription(schema);
@@ -317,6 +332,7 @@ function OpenAPISchemaPresentation(props: { property: OpenAPISchemaPropertyEntry
type={getSchemaTitle(schema)} type={getSchemaTitle(schema)}
propertyName={propertyName} propertyName={propertyName}
required={required} required={required}
context={context}
/> />
{typeof schema['x-deprecated-sunset'] === 'string' ? ( {typeof schema['x-deprecated-sunset'] === 'string' ? (
<div className="openapi-deprecated-sunset openapi-schema-description openapi-markdown"> <div className="openapi-deprecated-sunset openapi-schema-description openapi-markdown">
@@ -349,7 +365,7 @@ function OpenAPISchemaPresentation(props: { property: OpenAPISchemaPropertyEntry
Pattern: <code>{schema.pattern}</code> Pattern: <code>{schema.pattern}</code>
</span> </span>
) : null} ) : null}
<OpenAPISchemaEnum schema={schema} /> <OpenAPISchemaEnum schema={schema} context={context} />
</div> </div>
); );
} }
@@ -444,7 +460,91 @@ export function getSchemaAlternatives(
} }
const [type, schemas] = alternatives; const [type, schemas] = alternatives;
return flattenAlternatives(type, schemas, new Set(ancestors).add(schema)); return mergeAlternatives(
type,
flattenAlternatives(type, schemas, new Set(ancestors).add(schema))
);
}
/**
* Merge alternatives of the same type into a single schema.
* - Merge string enums
*/
function mergeAlternatives(
alternativeType: AlternativeType,
schemasOrRefs: OpenAPIV3.SchemaObject[]
): OpenAPIV3.SchemaObject[] | null {
switch (alternativeType) {
case 'oneOf': {
return schemasOrRefs.reduce<OpenAPIV3.SchemaObject[]>((acc, schemaOrRef) => {
const latest = acc.at(-1);
if (
latest &&
latest.type === 'string' &&
latest.enum &&
schemaOrRef.type === 'string' &&
schemaOrRef.enum
) {
latest.enum = Array.from(new Set([...latest.enum, ...schemaOrRef.enum]));
latest.nullable = latest.nullable || schemaOrRef.nullable;
return acc;
}
acc.push(schemaOrRef);
return acc;
}, []);
}
case 'allOf': {
return schemasOrRefs.reduce<OpenAPIV3.SchemaObject[]>((acc, schemaOrRef) => {
const latest = acc.at(-1);
if (
latest &&
latest.type === 'string' &&
latest.enum &&
schemaOrRef.type === 'string' &&
schemaOrRef.enum
) {
const keys = Object.keys(schemaOrRef);
if (keys.every((key) => ['type', 'enum', 'nullable'].includes(key))) {
latest.enum = Array.from(new Set([...latest.enum, ...schemaOrRef.enum]));
latest.nullable = latest.nullable || schemaOrRef.nullable;
return acc;
}
}
if (latest && latest.type === 'object' && schemaOrRef.type === 'object') {
const keys = Object.keys(schemaOrRef);
if (
keys.every((key) =>
['type', 'properties', 'required', 'nullable'].includes(key)
)
) {
latest.properties = {
...latest.properties,
...schemaOrRef.properties,
};
latest.required = Array.from(
new Set([
...(Array.isArray(latest.required) ? latest.required : []),
...(Array.isArray(schemaOrRef.required)
? schemaOrRef.required
: []),
])
);
latest.nullable = latest.nullable || schemaOrRef.nullable;
return acc;
}
}
acc.push(schemaOrRef);
return acc;
}, []);
}
default:
return schemasOrRefs;
}
} }
function flattenAlternatives( function flattenAlternatives(
@@ -502,19 +602,26 @@ function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
return type; return type;
} }
function getDisclosureLabel(schema: OpenAPIV3.SchemaObject): string { function getDisclosureLabel(props: {
schema: OpenAPIV3.SchemaObject;
isExpanded: boolean;
context: OpenAPIClientContext;
}) {
const { schema, isExpanded, context } = props;
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 = tString(context.translation, 'available_items').toLowerCase();
} }
// 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 = tString(context.translation, 'child_attributes').toLowerCase();
} else {
label = schema.items.title ?? schema.title ?? getSchemaTitle(schema.items);
} }
} else {
return schema.items.title ?? schema.title ?? getSchemaTitle(schema.items); label = schema.title || tString(context.translation, 'child_attributes').toLowerCase();
} }
return schema.title || 'child attributes'; return `${isExpanded ? tString(context.translation, 'hide') : tString(context.translation, 'show')} ${label}`;
} }
@@ -1,11 +1,14 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser'; import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import type React from 'react'; import type React from 'react';
import type { OpenAPIClientContext } from './context';
import { t, tString } from './translate';
interface OpenAPISchemaNameProps { interface OpenAPISchemaNameProps {
schema?: OpenAPIV3.SchemaObject; schema?: OpenAPIV3.SchemaObject;
propertyName?: string | React.JSX.Element; propertyName?: string | React.JSX.Element;
required?: boolean; required?: boolean;
type?: string; type?: string;
context: OpenAPIClientContext;
} }
/** /**
@@ -13,9 +16,9 @@ interface OpenAPISchemaNameProps {
* It includes the property name, type, required and deprecated status. * It includes the property name, type, required and deprecated status.
*/ */
export function OpenAPISchemaName(props: OpenAPISchemaNameProps) { export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
const { schema, type, propertyName, required } = props; const { schema, type, propertyName, required, context } = props;
const additionalItems = schema && getAdditionalItems(schema); const additionalItems = schema && getAdditionalItems(schema, context);
return ( return (
<span className="openapi-schema-name"> <span className="openapi-schema-name">
@@ -30,33 +33,45 @@ export function OpenAPISchemaName(props: OpenAPISchemaNameProps) {
<span className="openapi-schema-type">{additionalItems}</span> <span className="openapi-schema-type">{additionalItems}</span>
) : null} ) : null}
</span> </span>
{schema?.readOnly ? <span className="openapi-schema-readonly">read-only</span> : null} {schema?.readOnly ? (
<span className="openapi-schema-readonly">
{t(context.translation, 'read_only')}
</span>
) : null}
{schema?.writeOnly ? ( {schema?.writeOnly ? (
<span className="openapi-schema-writeonly">write-only</span> <span className="openapi-schema-writeonly">
{t(context.translation, 'write_only')}
</span>
) : null} ) : null}
{required ? ( {required ? (
<span className="openapi-schema-required">required</span> <span className="openapi-schema-required">
{t(context.translation, 'required')}
</span>
) : ( ) : (
<span className="openapi-schema-optional">optional</span> <span className="openapi-schema-optional">
{t(context.translation, 'optional')}
</span>
)} )}
{schema?.deprecated ? <span className="openapi-deprecated">Deprecated</span> : null} {schema?.deprecated ? (
<span className="openapi-deprecated">{t(context.translation, 'deprecated')}</span>
) : null}
</span> </span>
); );
} }
function getAdditionalItems(schema: OpenAPIV3.SchemaObject): string { function getAdditionalItems(schema: OpenAPIV3.SchemaObject, context: OpenAPIClientContext): string {
let additionalItems = ''; let additionalItems = '';
if (schema.minimum || schema.minLength || schema.minItems) { if (schema.minimum || schema.minLength || schema.minItems) {
additionalItems += ` · min: ${schema.minimum || schema.minLength || schema.minItems}`; additionalItems += ` · ${tString(context.translation, 'min').toLowerCase()}: ${schema.minimum || schema.minLength || schema.minItems}`;
} }
if (schema.maximum || schema.maxLength || schema.maxItems) { if (schema.maximum || schema.maxLength || schema.maxItems) {
additionalItems += ` · max: ${schema.maximum || schema.maxLength || schema.maxItems}`; additionalItems += ` · ${tString(context.translation, 'max').toLowerCase()}: ${schema.maximum || schema.maxLength || schema.maxItems}`;
} }
if (schema.nullable) { if (schema.nullable) {
additionalItems = ' | nullable'; additionalItems = ` | ${tString(context.translation, 'nullable').toLowerCase()}`;
} }
return additionalItems; return additionalItems;
@@ -4,8 +4,8 @@ import {
OpenAPISchemaPropertiesFromServer, OpenAPISchemaPropertiesFromServer,
type OpenAPISchemaPropertyEntry, type OpenAPISchemaPropertyEntry,
} from './OpenAPISchema'; } from './OpenAPISchema';
import type { OpenAPIClientContext } from './context';
import { decycle } from './decycle'; import { decycle } from './decycle';
import type { OpenAPIClientContext } from './types';
export function OpenAPISchemaProperties(props: { export function OpenAPISchemaProperties(props: {
id?: string; id?: string;
@@ -1,12 +1,10 @@
import { InteractiveSection } from './InteractiveSection'; import { InteractiveSection } from './InteractiveSection';
import { Markdown } from './Markdown'; import { Markdown } from './Markdown';
import { OpenAPISchemaName } from './OpenAPISchemaName'; import { OpenAPISchemaName } from './OpenAPISchemaName';
import type { import type { OpenAPIClientContext } from './context';
OpenAPIClientContext, import { t } from './translate';
OpenAPIOperationData, import type { OpenAPIOperationData, OpenAPISecurityWithRequired } from './types';
OpenAPISecurityWithRequired, import { createStateKey, resolveDescription } from './utils';
} from './types';
import { resolveDescription } from './utils';
/** /**
* Present securities authorization that can be used for this operation. * Present securities authorization that can be used for this operation.
@@ -23,10 +21,12 @@ export function OpenAPISecurities(props: {
return ( return (
<InteractiveSection <InteractiveSection
header="Authorizations" header={t(context.translation, '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);
@@ -36,7 +36,7 @@ export function OpenAPISecurities(props: {
body: ( body: (
<div className="openapi-schema"> <div className="openapi-schema">
<div className="openapi-schema-presentation"> <div className="openapi-schema-presentation">
{getLabelForType(security)} {getLabelForType(security, context)}
{description ? ( {description ? (
<Markdown <Markdown
@@ -53,11 +53,12 @@ export function OpenAPISecurities(props: {
); );
} }
function getLabelForType(security: OpenAPISecurityWithRequired) { function getLabelForType(security: OpenAPISecurityWithRequired, context: OpenAPIClientContext) {
switch (security.type) { switch (security.type) {
case 'apiKey': case 'apiKey':
return ( return (
<OpenAPISchemaName <OpenAPISchemaName
context={context}
propertyName={security.name ?? 'apiKey'} propertyName={security.name ?? 'apiKey'}
type="string" type="string"
required={security.required} required={security.required}
@@ -67,6 +68,7 @@ function getLabelForType(security: OpenAPISecurityWithRequired) {
if (security.scheme === 'basic') { if (security.scheme === 'basic') {
return ( return (
<OpenAPISchemaName <OpenAPISchemaName
context={context}
propertyName="Authorization" propertyName="Authorization"
type="string" type="string"
required={security.required} required={security.required}
@@ -79,6 +81,7 @@ function getLabelForType(security: OpenAPISecurityWithRequired) {
return ( return (
<> <>
<OpenAPISchemaName <OpenAPISchemaName
context={context}
propertyName="Authorization" propertyName="Authorization"
type="string" type="string"
required={security.required} required={security.required}
@@ -94,11 +97,29 @@ function getLabelForType(security: OpenAPISecurityWithRequired) {
); );
} }
return <OpenAPISchemaName propertyName="HTTP" required={security.required} />; return (
<OpenAPISchemaName
context={context}
propertyName="HTTP"
required={security.required}
/>
);
case 'oauth2': case 'oauth2':
return <OpenAPISchemaName propertyName="OAuth2" required={security.required} />; return (
<OpenAPISchemaName
context={context}
propertyName="OAuth2"
required={security.required}
/>
);
case 'openIdConnect': case 'openIdConnect':
return <OpenAPISchemaName propertyName="OpenID Connect" required={security.required} />; return (
<OpenAPISchemaName
context={context}
propertyName="OpenID Connect"
required={security.required}
/>
);
default: default:
// @ts-ignore // @ts-ignore
return security.type; return security.type;
+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}>
+21 -10
View File
@@ -5,16 +5,23 @@ import { OpenAPIResponses } from './OpenAPIResponses';
import { OpenAPISchemaProperties } from './OpenAPISchemaServer'; import { OpenAPISchemaProperties } from './OpenAPISchemaServer';
import { OpenAPISecurities } from './OpenAPISecurities'; import { OpenAPISecurities } from './OpenAPISecurities';
import { StaticSection } from './StaticSection'; import { StaticSection } from './StaticSection';
import type { OpenAPIClientContext, OpenAPIOperationData } from './types'; import type { OpenAPIClientContext } from './context';
import { tString } from './translate';
import type { OpenAPIOperationData, OpenAPIWebhookData } from './types';
import { parameterToProperty } from './utils'; import { parameterToProperty } from './utils';
export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAPIClientContext }) { export function OpenAPISpec(props: {
data: OpenAPIOperationData | OpenAPIWebhookData;
context: OpenAPIClientContext;
}) {
const { data, context } = props; const { data, context } = props;
const { operation, securities } = data; const { operation } = data;
const parameters = operation.parameters ?? []; const parameters = operation.parameters ?? [];
const parameterGroups = groupParameters(parameters); const parameterGroups = groupParameters(parameters, context);
const securities = 'securities' in data ? data.securities : [];
return ( return (
<> <>
@@ -42,6 +49,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 ? (
@@ -55,7 +63,10 @@ export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAP
); );
} }
function groupParameters(parameters: OpenAPI.Parameters): Array<{ function groupParameters(
parameters: OpenAPI.Parameters,
context: OpenAPIClientContext
): Array<{
key: string; key: string;
label: string; label: string;
parameters: OpenAPI.Parameters; parameters: OpenAPI.Parameters;
@@ -72,7 +83,7 @@ function groupParameters(parameters: OpenAPI.Parameters): Array<{
.filter((parameter) => parameter.in) .filter((parameter) => parameter.in)
.forEach((parameter) => { .forEach((parameter) => {
const key = parameter.in; const key = parameter.in;
const label = getParameterGroupName(parameter.in); const label = getParameterGroupName(parameter.in, context);
const group = groups.find((group) => group.key === key); const group = groups.find((group) => group.key === key);
if (group) { if (group) {
group.parameters.push(parameter); group.parameters.push(parameter);
@@ -90,14 +101,14 @@ function groupParameters(parameters: OpenAPI.Parameters): Array<{
return groups; return groups;
} }
function getParameterGroupName(paramIn: string): string { function getParameterGroupName(paramIn: string, context: OpenAPIClientContext): string {
switch (paramIn) { switch (paramIn) {
case 'path': case 'path':
return 'Path parameters'; return tString(context.translation, 'path_parameters');
case 'query': case 'query':
return 'Query parameters'; return tString(context.translation, 'query_parameters');
case 'header': case 'header':
return 'Header parameters'; return tString(context.translation, 'header_parameters');
default: default:
return paramIn; return paramIn;
} }
@@ -0,0 +1,33 @@
import clsx from 'clsx';
import { OpenAPIWebhookExample } from './OpenAPIWebhookExample';
import { OpenAPIColumnSpec } from './common/OpenAPIColumnSpec';
import { OpenAPISummary } from './common/OpenAPISummary';
import { type OpenAPIContextInput, resolveOpenAPIContext } from './context';
import type { OpenAPIWebhookData } from './types';
/**
* Display an interactive OpenAPI webhook.
*/
export function OpenAPIWebhook(props: {
className?: string;
data: OpenAPIWebhookData;
context: OpenAPIContextInput;
}) {
const { className, data, context: contextInput } = props;
const context = resolveOpenAPIContext(contextInput);
return (
<div className={clsx('openapi-webhook', className)}>
<OpenAPISummary data={data} context={context} />
<div className="openapi-columns">
<OpenAPIColumnSpec data={data} context={context} />
<div className="openapi-column-preview">
<div className="openapi-column-preview-body">
<OpenAPIWebhookExample data={data} context={context} />
</div>
</div>
</div>
</div>
);
}
@@ -0,0 +1,60 @@
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
import { OpenAPIEmptyExample } from './OpenAPIExample';
import { OpenAPIMediaTypeContent } from './OpenAPIMediaType';
import { type OpenAPIContext, getOpenAPIClientContext } from './context';
import type { OpenAPIWebhookData } from './types';
import { getExamples } from './util/example';
import { createStateKey } from './utils';
export function OpenAPIWebhookExample(props: {
data: OpenAPIWebhookData;
context: OpenAPIContext;
}) {
const { data, context } = props;
const { operation } = data;
const items = (() => {
if (!operation.requestBody) {
return [];
}
return Object.entries(
operation.requestBody.content as Record<string, OpenAPIV3.MediaTypeObject>
).map(([key, value]) => {
const schema = value.schema;
if (!schema) {
return {
key,
label: key,
body: <OpenAPIEmptyExample context={context} />,
};
}
return {
key,
label: key,
body: <></>,
examples: getExamples({
mediaTypeObject: value,
mediaType: key,
context,
}),
};
});
})();
return (
<div className="openapi-panel">
<h4 className="openapi-panel-heading">Payload</h4>
<div className="openapi-panel-body">
<OpenAPIMediaTypeContent
selectIcon={context.icons.chevronDown}
stateKey={createStateKey('request-body-media-type', context.blockKey)}
items={items}
context={getOpenAPIClientContext(context)}
/>
</div>
</div>
);
}
@@ -6,6 +6,8 @@ import { createPortal } from 'react-dom';
import type { OpenAPIV3_1 } from '@gitbook/openapi-parser'; import type { OpenAPIV3_1 } from '@gitbook/openapi-parser';
import { useOpenAPIOperationContext } from './OpenAPIOperationContext'; import { useOpenAPIOperationContext } from './OpenAPIOperationContext';
import type { OpenAPIClientContext } from './context';
import { t } from './translate';
/** /**
* Button which launches the Scalar API Client * Button which launches the Scalar API Client
@@ -14,8 +16,9 @@ export function ScalarApiButton(props: {
method: OpenAPIV3_1.HttpMethods; method: OpenAPIV3_1.HttpMethods;
path: string; path: string;
specUrl: string; specUrl: string;
context: OpenAPIClientContext;
}) { }) {
const { method, path, specUrl } = props; const { method, path, specUrl, context } = props;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const controllerRef = useRef<ScalarModalControllerRef>(null); const controllerRef = useRef<ScalarModalControllerRef>(null);
return ( return (
@@ -27,7 +30,7 @@ export function ScalarApiButton(props: {
setIsOpen(true); setIsOpen(true);
}} }}
> >
Test it {t(context.translation, 'test_it')}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 12" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 12" fill="currentColor">
<path <path
stroke="currentColor" stroke="currentColor"
@@ -59,10 +62,7 @@ function ScalarModal(props: {
}) { }) {
const { method, path, specUrl, controllerRef } = props; const { method, path, specUrl, controllerRef } = props;
return ( return (
<ApiClientModalProvider <ApiClientModalProvider configuration={{ url: specUrl }} initialRequest={{ method, path }}>
configuration={{ spec: { url: specUrl } }}
initialRequest={{ method, path }}
>
<ScalarModalController method={method} path={path} controllerRef={controllerRef} /> <ScalarModalController method={method} path={path} controllerRef={controllerRef} />
</ApiClientModalProvider> </ApiClientModalProvider>
); );
+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 };
@@ -0,0 +1,31 @@
import { OpenAPISpec } from '../OpenAPISpec';
import { type OpenAPIContext, getOpenAPIClientContext } from '../context';
import { t } from '../translate';
import type { OpenAPIOperationData, OpenAPIWebhookData } from '../types';
import { OpenAPIOperationDescription } from './OpenAPIOperationDescription';
export function OpenAPIColumnSpec(props: {
data: OpenAPIOperationData | OpenAPIWebhookData;
context: OpenAPIContext;
}) {
const { data, context } = props;
const { operation } = data;
const clientContext = getOpenAPIClientContext(context);
return (
<div className="openapi-column-spec">
{operation['x-deprecated-sunset'] ? (
<div className="openapi-deprecated-sunset openapi-description openapi-markdown">
{t(context.translation, 'deprecated_and_sunset_on', [
<span key="date" className="openapi-deprecated-sunset-date">
{operation['x-deprecated-sunset']}
</span>,
])}
</div>
) : null}
<OpenAPIOperationDescription operation={operation} context={context} />
<OpenAPISpec data={data} context={clientContext} />
</div>
);
}
@@ -0,0 +1,31 @@
import type { OpenAPICustomOperationProperties, OpenAPIV3 } from '@gitbook/openapi-parser';
import { Markdown } from '../Markdown';
import type { OpenAPIContext } from '../context';
import { resolveDescription } from '../utils';
export function OpenAPIOperationDescription(props: {
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
context: OpenAPIContext;
}) {
const { operation } = props;
if (operation['x-gitbook-description-document']) {
return (
<div className="openapi-intro">
{props.context.renderDocument({
document: operation['x-gitbook-description-document'],
})}
</div>
);
}
const description = resolveDescription(operation);
if (!description) {
return null;
}
return (
<div className="openapi-intro">
<Markdown className="openapi-description" source={description} />
</div>
);
}
@@ -0,0 +1,23 @@
import type { OpenAPIStability as OpenAPIStabilityType } from '@gitbook/openapi-parser';
const stabilityEnum: Record<OpenAPIStabilityType, string> = {
experimental: 'Experimental',
alpha: 'Alpha',
beta: 'Beta',
} as const;
export function OpenAPIStability(props: { stability: OpenAPIStabilityType }) {
const { stability } = props;
const foundStability = stabilityEnum[stability];
if (!foundStability) {
return null;
}
return (
<div className={`openapi-stability openapi-stability-${foundStability.toLowerCase()}`}>
{foundStability}
</div>
);
}
@@ -0,0 +1,45 @@
import { OpenAPIPath } from '../OpenAPIPath';
import type { OpenAPIContext } from '../context';
import type { OpenAPIOperationData, OpenAPIWebhookData } from '../types';
import { OpenAPIStability } from './OpenAPIStability';
export function OpenAPISummary(props: {
data: OpenAPIOperationData | OpenAPIWebhookData;
context: OpenAPIContext;
}) {
const { data, context } = props;
const { operation } = data;
const title = (() => {
if (operation.summary) {
return operation.summary;
}
if ('name' in data) {
return data.name;
}
return undefined;
})();
return (
<div className="openapi-summary" id={operation.summary ? undefined : context.id}>
{(operation.deprecated || operation['x-stability']) && (
<div className="openapi-summary-tags">
{operation.deprecated && <div className="openapi-deprecated">Deprecated</div>}
{operation['x-stability'] && (
<OpenAPIStability stability={operation['x-stability']} />
)}
</div>
)}
{title
? context.renderHeading({
deprecated: operation.deprecated ?? false,
stability: operation['x-stability'],
title,
})
: null}
{'path' in data ? <OpenAPIPath data={data} context={context} /> : null}
</div>
);
}
+37 -2
View File
@@ -1,4 +1,11 @@
import { type Translation, type TranslationLocale, translations } from './translations';
export interface OpenAPIClientContext { export interface OpenAPIClientContext {
/**
* The translation language to use.
*/
translation: Translation;
/** /**
* Icons used in the block. * Icons used in the block.
*/ */
@@ -23,9 +30,14 @@ export interface OpenAPIClientContext {
* Optional id attached to the heading and used as an anchor. * Optional id attached to the heading and used as an anchor.
*/ */
id?: string; id?: string;
/**
* Mark the context as a client context.
*/
$$isClientContext$$: true;
} }
export interface OpenAPIContext extends OpenAPIClientContext { export interface OpenAPIContext extends Omit<OpenAPIClientContext, '$$isClientContext$$'> {
/** /**
* Render a code block. * Render a code block.
*/ */
@@ -51,14 +63,37 @@ export interface OpenAPIContext extends OpenAPIClientContext {
specUrl: string; specUrl: string;
} }
export type OpenAPIUniversalContext = OpenAPIClientContext | OpenAPIContext;
export interface OpenAPIContextInput extends Omit<OpenAPIContext, 'translation'> {
/**
* The translation language to use.
* @default 'en'
*/
locale?: TranslationLocale | undefined;
}
/**
* Resolve OpenAPI context from the input.
*/
export function resolveOpenAPIContext(context: OpenAPIContextInput): OpenAPIContext {
const { locale, ...rest } = context;
return {
...rest,
translation: translations[locale ?? 'en'],
};
}
/** /**
* Get the client context from the OpenAPI context. * Get the client context from the OpenAPI context.
*/ */
export function getOpenAPIClientContext(context: OpenAPIContext): OpenAPIClientContext { export function getOpenAPIClientContext(context: OpenAPIUniversalContext): OpenAPIClientContext {
return { return {
translation: context.translation,
icons: context.icons, icons: context.icons,
defaultInteractiveOpened: context.defaultInteractiveOpened, defaultInteractiveOpened: context.defaultInteractiveOpened,
blockKey: context.blockKey, blockKey: context.blockKey,
id: context.id, id: context.id,
$$isClientContext$$: true,
}; };
} }
@@ -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) => ({
+5 -1
View File
@@ -1,5 +1,9 @@
export * from './schemas'; export * from './schemas';
export * from './OpenAPIOperation'; export * from './OpenAPIOperation';
export * from './OpenAPIWebhook';
export * from './OpenAPIOperationContext'; export * from './OpenAPIOperationContext';
export * from './resolveOpenAPIOperation'; export * from './resolveOpenAPIOperation';
export type { OpenAPIOperationData, OpenAPIContext } from './types'; export * from './resolveOpenAPIWebhook';
export type { OpenAPIOperationData, OpenAPIWebhookData } from './types';
export type { OpenAPIContextInput } from './context';
export { checkIsValidLocale } from './translations';
@@ -0,0 +1,99 @@
import { fromJSON, toJSON } from 'flatted';
import type {
Filesystem,
OpenAPIV3,
OpenAPIV3_1,
OpenAPIV3xDocument,
} from '@gitbook/openapi-parser';
import { dereferenceFilesystem } from './dereference';
import type { OpenAPIWebhookData } from './types';
export { fromJSON, toJSON };
/**
* Resolve an OpenAPI webhook in a file and compile it to a more usable format.
*/
export async function resolveOpenAPIWebhook(
filesystem: Filesystem<OpenAPIV3xDocument>,
webhookDescriptor: {
name: string;
method: string;
}
): Promise<OpenAPIWebhookData | null> {
const { name, method } = webhookDescriptor;
const schema = await dereferenceFilesystem(filesystem);
let operation = getWebhookByNameAndMethod(schema, name, method);
if (!operation) {
return null;
}
// Resolve common parameters
const commonParameters = getPathObjectParameter(schema, name);
if (commonParameters) {
operation = {
...operation,
parameters: [...commonParameters, ...(operation.parameters ?? [])],
};
}
const servers = 'servers' in schema ? (schema.servers ?? []) : [];
return {
servers,
operation,
method,
name,
};
}
/**
* Get a path object from its path.
*/
function getPathObject(
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
name: string
): OpenAPIV3.PathItemObject | OpenAPIV3_1.PathItemObject | null {
if (schema.webhooks?.[name]) {
return schema.webhooks[name];
}
return null;
}
/**
* Resolve parameters from a path in an OpenAPI schema.
*/
function getPathObjectParameter(
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
path: string
):
| (OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject)[]
| (OpenAPIV3.ParameterObject | OpenAPIV3_1.ReferenceObject)[]
| null {
const pathObject = getPathObject(schema, path);
if (pathObject?.parameters) {
return pathObject.parameters;
}
return null;
}
/**
* Get an operation by its path and method.
*/
function getWebhookByNameAndMethod(
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
name: string,
method: string
): OpenAPIV3.OperationObject | null {
// Types are buffy for OpenAPIV3_1.OperationObject, so we use v3
const pathObject = getPathObject(schema, name);
if (!pathObject) {
return null;
}
const normalizedMethod = method.toLowerCase();
if (!pathObject[normalizedMethod]) {
return null;
}
return pathObject[normalizedMethod];
}

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