Compare commits

..

34 Commits

Author SHA1 Message Date
Samy Pessé eac554014a Continue 2025-06-10 12:08:19 +02:00
Samy Pessé fef62b12b4 Continue 2025-06-09 00:45:31 +02:00
Samy Pessé 6a10b6185b Start 2025-06-09 00:24:27 +02:00
Samy Pessé fa12f9eeb6 Page cover dark-mode specific (#3287) 2025-06-08 14:57:50 +02:00
Samy Pessé 33726c88a0 Generate a llms-full version of the docs (#3285) 2025-06-08 12:13:14 +02:00
Zeno Kapitein 6aa3ff9c5e Fix various styling issues (#3283) 2025-06-08 00:38:41 +01:00
Zeno Kapitein 7d3fe23195 Add circular corners and depth styles (#3280)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-06-07 13:48:16 +02:00
Samy Pessé 521052d84f Remove withoutConcurrentExecution and withCacheKey (#3281) 2025-06-05 20:43:15 +01:00
Samy Pessé c730845e17 Fix missing title for button to close announcement banner (#3279) 2025-06-04 16:33:01 +01:00
Addison 7b5a3616c4 Add UTM tags to the preview site badge (#3277) 2025-06-03 16:07:55 +01:00
Zeno Kapitein c9373efdb5 Fix bold header links hover color (#3255) 2025-06-03 15:18:04 +02:00
Brett Jephson 015615de8a fix: defaultWidth/fullWidth options in images (#3274) 2025-06-03 12:18:01 +01:00
conico974 0f867e533a bump OpenNext to latest (#3276)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-02 17:40:04 +02:00
conico974 7d5a6d2255 Fix hydration error on V2 (#3272)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-02 14:23:15 +01:00
conico974 a1da4f023d Move DO to a 3rd worker (#3270)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-30 16:19:11 +02:00
conico974 666e8426e1 External middleware in cloudflare (#3254)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-30 14:56:00 +02:00
conico974 df2fa42bda Chores: create server worker (#3265)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-30 14:31:21 +02:00
conico974 b3a7ad6d2c Fix href being empty in TOC (#3269)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-28 17:07:33 +02:00
conico974 2932077bf9 Remove trailing slash from linker (#3268)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-27 21:13:55 +02:00
Nolann B. 957afd967f Add authorization header for OAuth2 (#3266) 2025-05-26 20:54:07 +02:00
Nolann B. 4c9a9d0416 Handle nested deprecated properties in generateSchemaExample (#3267) 2025-05-26 20:28:26 +02:00
Nolann B. 40df91a363 Deduplicate path parameters from OpenAPI spec (#3264) 2025-05-26 18:55:11 +02:00
conico974 af66ff71ed Add a force-revalidate api route (#3263)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-25 16:55:45 +02:00
Samy Pessé 6f8ecd6111 Use more strict context ID for images during preview (#3261) 2025-05-23 13:16:43 +02:00
conico974 fa3eb07617 Cache fonts and static image in memory for OG Image (#3258)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-23 10:20:07 +02:00
Samy Pessé dc4268db64 Compute correct urls for variants/sections when previewing unpublished site (#3259) 2025-05-23 10:01:15 +02:00
Nolann B. a0c06a72cc Indent JSON python code sample (#3260) 2025-05-23 09:13:20 +02:00
conico974 19c20c0be3 Fix OGImage icon fetch on cloudflare (#3253)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-21 15:39:52 +02:00
Viktor Renkema fae34e56ac Calculate class more explicitly & separately to fix horizontal table cell alignment issue (#3252) 2025-05-21 14:20:39 +02:00
conico974 ba0094a862 fix ISR in preview in cloudflare (#3250)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-21 09:52:05 +02:00
conico974 b259009c9c Add temporary logging for Cloudflare debugging (#3249)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-20 19:33:33 +02:00
conico974 d88dd5ed8a Update OpenNext (#3248)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-20 19:04:07 +02:00
Zeno Kapitein 57bb146075 Dynamic TOC height (#3233) 2025-05-20 14:13:57 +02:00
Zeno Kapitein 4b67fe5317 V2: Add urlObject.hash to linker.toLinkForContent (#3246) 2025-05-20 10:25:35 +02:00
139 changed files with 3304 additions and 1179 deletions
+9
View File
@@ -0,0 +1,9 @@
---
"gitbook": patch
---
Fix three small visual issues
- Fix sidebar showing on `no-toc` pages in the gradient theme
- Fix variant selector truncating incorrectly in header when sections are present
- Fix page cover alignment on `lg` screens without TOC
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Respect fullWidth and defaultWidth for images
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
fix nested a tag causing hydration error
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
fix href being empty in TOC
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix navigation between sections/variants when previewing a site in v2
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": minor
---
Add circular corners and depth styling
+5
View File
@@ -0,0 +1,5 @@
---
'@gitbook/react-openapi': patch
---
Add authorization header for OAuth2
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": minor
---
Support dark-mode specific page cover image
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook-v2": patch
---
add a force-revalidate api route to force bust the cache in case of errors
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook-v2": patch
---
fix ISR on preview env
+6
View File
@@ -0,0 +1,6 @@
---
"gitbook-v2": patch
"gitbook": patch
---
Fix concurrent execution in Vercel causing pages to not be attached to the proper tags.
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook-v2": patch
---
Generate a llms-full.txt version of the docs site
+5
View File
@@ -0,0 +1,5 @@
---
'@gitbook/react-openapi': patch
---
Indent JSON python code sample
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix missing title on button to close the announcement banner.
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Make TOC height dynamic based on visible header and footer elements
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix bold header links hover color
+6
View File
@@ -0,0 +1,6 @@
---
"gitbook": patch
"gitbook-v2": patch
---
cache fonts and static image used in OGImage in memory
+5
View File
@@ -0,0 +1,5 @@
---
'@gitbook/react-openapi': patch
---
Handle nested deprecated properties in generateSchemaExample
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook-v2": patch
---
Add `urlObject.hash` to `linker.toLinkForContent` to pass through URL fragment identifiers, used in search
+5
View File
@@ -0,0 +1,5 @@
---
'@gitbook/react-openapi': patch
---
Deduplicate path parameters from OpenAPI spec
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook-v2": patch
---
remove trailing slash from linker
@@ -0,0 +1,83 @@
name: Gradual Deploy to Cloudflare
description: Use gradual deployment to deploy to Cloudflare. This action will upload the middleware and server versions to Cloudflare and kept them bound together
inputs:
apiToken:
description: 'Cloudflare API token'
required: true
accountId:
description: 'Cloudflare account ID'
required: true
environment:
description: 'Cloudflare environment to deploy to (staging, production, preview)'
required: true
middlewareVersionId:
description: 'Middleware version ID to deploy'
required: true
serverVersionId:
description: 'Server version ID to deploy'
required: true
outputs:
deployment-url:
description: "Deployment URL"
value: ${{ steps.deploy_middleware.outputs.deployment-url }}
runs:
using: 'composite'
steps:
- id: wrangler_status
name: Check wrangler deployment status
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: deployments status --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc
# This step is used to get the version ID that is currently deployed to Cloudflare.
- id: extract_current_version
name: Extract current version
shell: bash
run: |
version_id=$(echo "${{ steps.wrangler_status.outputs.command-output }}" | grep -A 3 "(100%)" | grep -oP '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
echo "version_id=$version_id" >> $GITHUB_OUTPUT
- id: deploy_server
name: Deploy server to Cloudflare at 0%
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: versions deploy ${{ steps.extract_current_version.outputs.version_id }}@100% ${{ inputs.serverVersionId }}@0% -y --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc
# Since we use version overrides headers, we can directly deploy the middleware to 100%.
- id: deploy_middleware
name: Deploy middleware to Cloudflare at 100%
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: versions deploy ${{ inputs.middlewareVersionId }}@100% -y --config ./packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc
- name: Deploy server to Cloudflare at 100%
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: versions deploy ${{ inputs.serverVersionId }}@100% -y --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc
- name: Outputs
shell: bash
env:
DEPLOYMENT_URL: ${{ steps.deploy_middleware.outputs.deployment-url }}
run: |
echo "URL: ${{ steps.deploy_middleware.outputs.deployment-url }}"
@@ -28,7 +28,7 @@ inputs:
outputs:
deployment-url:
description: "Deployment URL"
value: ${{ steps.deploy.outputs.deployment-url }}
value: ${{ steps.upload_middleware.outputs.deployment-url }}
runs:
using: 'composite'
steps:
@@ -63,8 +63,8 @@ runs:
env:
GITBOOK_RUNTIME: cloudflare
shell: bash
- id: deploy
name: Deploy to Cloudflare
- name: Upload the DO worker
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
@@ -72,10 +72,67 @@ runs:
workingDirectory: ./
wranglerVersion: '4.10.0'
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: deploy --config ./packages/gitbook-v2/openNext/customWorkers/doWrangler.jsonc
- id: upload_server
name: Upload server to Cloudflare
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/defaultWrangler.jsonc
- name: Extract server version worker ID
shell: bash
id: extract_server_version_id
run: |
version_id=$(echo '${{ steps.upload_server.outputs.command-output }}' | grep "Worker Version ID" | awk '{print $4}')
echo "version_id=$version_id" >> $GITHUB_OUTPUT
- name: Run updateWrangler scripts
shell: bash
run: |
bun run ./packages/gitbook-v2/openNext/customWorkers/script/updateWrangler.ts ${{ steps.extract_server_version_id.outputs.version_id }}
- id: upload_middleware
name: Upload middleware to Cloudflare
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/openNext/customWorkers/middlewareWrangler.jsonc
- name: Extract middleware version worker ID
shell: bash
id: extract_middleware_version_id
run: |
version_id=$(echo '${{ steps.upload_middleware.outputs.command-output }}' | grep "Worker Version ID" | awk '{print $4}')
echo "version_id=$version_id" >> $GITHUB_OUTPUT
- name: Deploy server and middleware to Cloudflare
if: ${{ inputs.deploy == 'true' }}
uses: ./.github/actions/gradual-deploy-cloudflare
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
opServiceAccount: ${{ inputs.opServiceAccount }}
opItem: ${{ inputs.opItem }}
environment: ${{ inputs.environment }}
serverVersionId: ${{ steps.extract_server_version_id.outputs.version_id }}
middlewareVersionId: ${{ steps.extract_middleware_version_id.outputs.version_id }}
deploy: ${{ inputs.deploy }}
- name: Outputs
shell: bash
env:
DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }}
DEPLOYMENT_URL: ${{ steps.upload_middleware.outputs.deployment-url }}
run: |
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
echo "URL: ${{ steps.upload_middleware.outputs.deployment-url }}"
echo "Output server: ${{ steps.upload_server.outputs.command-output }}"
+2 -2
View File
@@ -150,11 +150,11 @@ See `LICENSE` for more information.
</p>
```md
[![GitBook](https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=gitbook&logoColor=ffffff&label=%20&labelColor=5c5c5c&color=3F89A1)](https://gitbook.com/)
[![GitBook](https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=gitbook&logoColor=ffffff&label=%20&labelColor=5c5c5c&color=3F89A1)](https://www.gitbook.com/preview?utm_source=gitbook_readme_badge&utm_medium=organic&utm_campaign=preview_documentation&utm_content=link)
```
```html
<a href="https://gitbook.com">
<a href="https://www.gitbook.com/preview?utm_source=gitbook_readme_badge&utm_medium=organic&utm_campaign=preview_documentation&utm_content=link">
<img
src="https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=gitbook&logoColor=ffffff&label=%20&labelColor=5c5c5c&color=3F89A1"
/>
+79 -42
View File
@@ -26,7 +26,7 @@
"name": "@gitbook/cache-tags",
"version": "0.3.1",
"dependencies": {
"@gitbook/api": "^0.115.0",
"@gitbook/api": "^0.119.0",
"assert-never": "^1.2.1",
},
"devDependencies": {
@@ -35,7 +35,7 @@
},
"packages/colors": {
"name": "@gitbook/colors",
"version": "0.3.2",
"version": "0.3.3",
"devDependencies": {
"typescript": "^5.5.3",
},
@@ -49,9 +49,9 @@
},
"packages/gitbook": {
"name": "gitbook",
"version": "0.11.1",
"version": "0.12.0",
"dependencies": {
"@gitbook/api": "^0.115.0",
"@gitbook/api": "^0.119.0",
"@gitbook/cache-do": "workspace:*",
"@gitbook/cache-tags": "workspace:*",
"@gitbook/colors": "workspace:*",
@@ -80,14 +80,19 @@
"jwt-decode": "^4.0.0",
"katex": "^0.16.9",
"mathjax": "^3.2.2",
"mdast-util-from-markdown": "^2.0.2",
"mdast-util-frontmatter": "^2.0.1",
"mdast-util-gfm": "^3.1.0",
"mdast-util-to-markdown": "^2.1.2",
"memoizee": "^0.4.17",
"micromark-extension-frontmatter": "^2.0.0",
"micromark-extension-gfm": "^3.0.0",
"next": "14.2.26",
"next-themes": "^0.2.1",
"nuqs": "^2.2.3",
"object-hash": "^3.0.0",
"openapi-types": "^12.1.3",
"p-map": "^7.0.0",
"p-map": "^7.0.3",
"parse-cache-control": "^1.0.1",
"partial-json": "^0.1.7",
"react": "^19.0.0",
@@ -104,6 +109,8 @@
"tailwind-merge": "^2.2.0",
"tailwind-shades": "^1.1.2",
"unified": "^11.0.5",
"unist-util-remove": "^4.0.0",
"unist-util-visit": "^5.0.0",
"url-join": "^5.0.0",
"usehooks-ts": "^3.1.0",
"zod": "^3.24.2",
@@ -141,11 +148,11 @@
},
"packages/gitbook-v2": {
"name": "gitbook-v2",
"version": "0.2.5",
"version": "0.3.0",
"dependencies": {
"@gitbook/api": "^0.115.0",
"@gitbook/api": "^0.119.0",
"@gitbook/cache-tags": "workspace:*",
"@opennextjs/cloudflare": "1.0.3",
"@opennextjs/cloudflare": "1.1.0",
"@sindresorhus/fnv1a": "^3.1.0",
"assert-never": "^1.2.1",
"jwt-decode": "^4.0.0",
@@ -185,7 +192,7 @@
},
"packages/openapi-parser": {
"name": "@gitbook/openapi-parser",
"version": "2.1.3",
"version": "2.1.4",
"dependencies": {
"@scalar/openapi-parser": "^0.10.10",
"@scalar/openapi-types": "^0.1.9",
@@ -202,7 +209,7 @@
"name": "@gitbook/react-contentkit",
"version": "0.7.0",
"dependencies": {
"@gitbook/api": "^0.115.0",
"@gitbook/api": "^0.119.0",
"@gitbook/icons": "workspace:*",
"classnames": "^2.5.1",
},
@@ -232,7 +239,7 @@
},
"packages/react-openapi": {
"name": "@gitbook/react-openapi",
"version": "1.2.1",
"version": "1.3.0",
"dependencies": {
"@gitbook/openapi-parser": "workspace:*",
"@scalar/api-client-react": "^1.2.19",
@@ -260,7 +267,7 @@
},
"overrides": {
"@codemirror/state": "6.4.1",
"@gitbook/api": "^0.115.0",
"@gitbook/api": "^0.119.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
},
@@ -625,7 +632,7 @@
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
"@gitbook/api": ["@gitbook/api@0.115.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-Lyj+1WVNnE/Zuuqa/1ZdnUQfUiNE6es89RFK6CJ+Tb36TFwls6mbHKXCZsBwSYyoMYTVK39WQ3Nob6Nw6+TWCA=="],
"@gitbook/api": ["@gitbook/api@0.119.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-l2gfJ5+1HT3Sj1OQCh72lUOGU9LV9WbDjgKPjxIFz+NgOhSest1bJ3JhmHP66Xd4ETpmm0YQqnq4y18wKRMjBQ=="],
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
@@ -791,9 +798,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=="],
"@opennextjs/aws": ["@opennextjs/aws@3.6.1", "", { "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-RYU9K58vEUPXqc3pZO6kr9vBy1MmJZFQZLe0oXBskC005oGju/m4e3DCCP4eZ/Q/HdYQXCoqNXgSGi8VCAYgew=="],
"@opennextjs/aws": ["@opennextjs/aws@3.6.4", "", { "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.25.4", "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-/bn9N/6dVu9+sC7AptaGJylKUzyDDgqe3yKfvUxXOpy7whIq/+3Bw+q2/bilyQg6FcskbCWUt9nLvNf1hAVmfA=="],
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.0.3", "", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "^3.6.1", "enquirer": "^2.4.1", "glob": "^11.0.0", "ts-tqdm": "^0.8.6" }, "peerDependencies": { "wrangler": "^4.14.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-+SNhz2JmKkMlWzndTSYZeq0eseAPfEXUUYJA7H9MkgFe36i1no0OdiLuwu9e4uR3yuOzaNWB+skCYHsD6Im9nA=="],
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.1.0", "", { "dependencies": { "@dotenvx/dotenvx": "1.31.0", "@opennextjs/aws": "3.6.4", "enquirer": "^2.4.1", "glob": "^11.0.0", "ts-tqdm": "^0.8.6" }, "peerDependencies": { "wrangler": "^4.14.0" }, "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-8DOzpLiewivA1pwRNGVPDXbrF79bzJiHscY+M1tekwvKqEqM26CRYafhrA5IDCeyteNaL9AZD6X4DLuCn/eeYQ=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
@@ -1809,6 +1816,8 @@
"fastq": ["fastq@1.17.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w=="],
"fault": ["fault@2.0.1", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ=="],
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
"fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="],
@@ -1835,6 +1844,8 @@
"form-data": ["form-data@4.0.1", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw=="],
"format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
@@ -2181,9 +2192,11 @@
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA=="],
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA=="],
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
"mdast-util-gfm": ["mdast-util-gfm@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw=="],
"mdast-util-frontmatter": ["mdast-util-frontmatter@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "escape-string-regexp": "^5.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-extension-frontmatter": "^2.0.0" } }, "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA=="],
"mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
"mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
@@ -2227,6 +2240,8 @@
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA=="],
"micromark-extension-frontmatter": ["micromark-extension-frontmatter@2.0.0", "", { "dependencies": { "fault": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg=="],
"micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
"micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
@@ -2395,7 +2410,7 @@
"p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
"p-map": ["p-map@7.0.2", "", {}, "sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q=="],
"p-map": ["p-map@7.0.3", "", {}, "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA=="],
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
@@ -2829,6 +2844,8 @@
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
"unist-util-remove": ["unist-util-remove@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg=="],
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
"unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
@@ -3649,7 +3666,7 @@
"@node-minify/core/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
"@opennextjs/aws/esbuild": ["esbuild@0.19.2", "", { "optionalDependencies": { "@esbuild/android-arm": "0.19.2", "@esbuild/android-arm64": "0.19.2", "@esbuild/android-x64": "0.19.2", "@esbuild/darwin-arm64": "0.19.2", "@esbuild/darwin-x64": "0.19.2", "@esbuild/freebsd-arm64": "0.19.2", "@esbuild/freebsd-x64": "0.19.2", "@esbuild/linux-arm": "0.19.2", "@esbuild/linux-arm64": "0.19.2", "@esbuild/linux-ia32": "0.19.2", "@esbuild/linux-loong64": "0.19.2", "@esbuild/linux-mips64el": "0.19.2", "@esbuild/linux-ppc64": "0.19.2", "@esbuild/linux-riscv64": "0.19.2", "@esbuild/linux-s390x": "0.19.2", "@esbuild/linux-x64": "0.19.2", "@esbuild/netbsd-x64": "0.19.2", "@esbuild/openbsd-x64": "0.19.2", "@esbuild/sunos-x64": "0.19.2", "@esbuild/win32-arm64": "0.19.2", "@esbuild/win32-ia32": "0.19.2", "@esbuild/win32-x64": "0.19.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-G6hPax8UbFakEj3hWO0Vs52LQ8k3lnBhxZWomUJDxfz3rZTLqF5k/FCzuNdLx2RbpBiQQF9H9onlDDH1lZsnjg=="],
"@opennextjs/aws/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
"@radix-ui/react-collection/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
@@ -4115,14 +4132,20 @@
"make-dir/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"mdast-util-gfm/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
"mdast-util-gfm-footnote/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA=="],
"mdast-util-gfm-footnote/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
"mdast-util-gfm-strikethrough/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA=="],
"mdast-util-gfm-strikethrough/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-table/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA=="],
"mdast-util-gfm-table/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-from-markdown": ["mdast-util-from-markdown@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA=="],
"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=="],
@@ -4191,6 +4214,10 @@
"read-yaml-file/pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="],
"remark-gfm/mdast-util-gfm": ["mdast-util-gfm@3.0.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw=="],
"remark-parse/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA=="],
"remark-stringify/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
"rimraf/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
@@ -4721,49 +4748,55 @@
"@node-minify/core/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"@opennextjs/aws/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.2", "", { "os": "android", "cpu": "arm" }, "sha512-tM8yLeYVe7pRyAu9VMi/Q7aunpLwD139EY1S99xbQkT4/q2qa6eA4ige/WJQYdJ8GBL1K33pPFhPfPdJ/WzT8Q=="],
"@opennextjs/aws/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
"@opennextjs/aws/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.2", "", { "os": "android", "cpu": "arm64" }, "sha512-lsB65vAbe90I/Qe10OjkmrdxSX4UJDjosDgb8sZUKcg3oefEuW2OT2Vozz8ef7wrJbMcmhvCC+hciF8jY/uAkw=="],
"@opennextjs/aws/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
"@opennextjs/aws/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.19.2", "", { "os": "android", "cpu": "x64" }, "sha512-qK/TpmHt2M/Hg82WXHRc/W/2SGo/l1thtDHZWqFq7oi24AjZ4O/CpPSu6ZuYKFkEgmZlFoa7CooAyYmuvnaG8w=="],
"@opennextjs/aws/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="],
"@opennextjs/aws/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Ora8JokrvrzEPEpZO18ZYXkH4asCdc1DLdcVy8TGf5eWtPO1Ie4WroEJzwI52ZGtpODy3+m0a2yEX9l+KUn0tA=="],
"@opennextjs/aws/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="],
"@opennextjs/aws/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-tP+B5UuIbbFMj2hQaUr6EALlHOIOmlLM2FK7jeFBobPy2ERdohI4Ka6ZFjZ1ZYsrHE/hZimGuU90jusRE0pwDw=="],
"@opennextjs/aws/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="],
"@opennextjs/aws/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YbPY2kc0acfzL1VPVK6EnAlig4f+l8xmq36OZkU0jzBVHcOTyQDhnKQaLzZudNJQyymd9OqQezeaBgkTGdTGeQ=="],
"@opennextjs/aws/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="],
"@opennextjs/aws/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-nSO5uZT2clM6hosjWHAsS15hLrwCvIWx+b2e3lZ3MwbYSaXwvfO528OF+dLjas1g3bZonciivI8qKR/Hm7IWGw=="],
"@opennextjs/aws/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="],
"@opennextjs/aws/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.2", "", { "os": "linux", "cpu": "arm" }, "sha512-Odalh8hICg7SOD7XCj0YLpYCEc+6mkoq63UnExDCiRA2wXEmGlK5JVrW50vZR9Qz4qkvqnHcpH+OFEggO3PgTg=="],
"@opennextjs/aws/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="],
"@opennextjs/aws/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-ig2P7GeG//zWlU0AggA3pV1h5gdix0MA3wgB+NsnBXViwiGgY77fuN9Wr5uoCrs2YzaYfogXgsWZbm+HGr09xg=="],
"@opennextjs/aws/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="],
"@opennextjs/aws/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-mLfp0ziRPOLSTek0Gd9T5B8AtzKAkoZE70fneiiyPlSnUKKI4lp+mGEnQXcQEHLJAcIYDPSyBvsUbKUG2ri/XQ=="],
"@opennextjs/aws/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="],
"@opennextjs/aws/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.2", "", { "os": "linux", "cpu": "none" }, "sha512-hn28+JNDTxxCpnYjdDYVMNTR3SKavyLlCHHkufHV91fkewpIyQchS1d8wSbmXhs1fiYDpNww8KTFlJ1dHsxeSw=="],
"@opennextjs/aws/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="],
"@opennextjs/aws/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.2", "", { "os": "linux", "cpu": "none" }, "sha512-KbXaC0Sejt7vD2fEgPoIKb6nxkfYW9OmFUK9XQE4//PvGIxNIfPk1NmlHmMg6f25x57rpmEFrn1OotASYIAaTg=="],
"@opennextjs/aws/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="],
"@opennextjs/aws/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-dJ0kE8KTqbiHtA3Fc/zn7lCd7pqVr4JcT0JqOnbj4LLzYnp+7h8Qi4yjfq42ZlHfhOCM42rBh0EwHYLL6LEzcw=="],
"@opennextjs/aws/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="],
"@opennextjs/aws/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.2", "", { "os": "linux", "cpu": "none" }, "sha512-7Z/jKNFufZ/bbu4INqqCN6DDlrmOTmdw6D0gH+6Y7auok2r02Ur661qPuXidPOJ+FSgbEeQnnAGgsVynfLuOEw=="],
"@opennextjs/aws/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="],
"@opennextjs/aws/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-U+RinR6aXXABFCcAY4gSlv4CL1oOVvSSCdseQmGO66H+XyuQGZIUdhG56SZaDJQcLmrSfRmx5XZOWyCJPRqS7g=="],
"@opennextjs/aws/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="],
"@opennextjs/aws/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.2", "", { "os": "linux", "cpu": "x64" }, "sha512-oxzHTEv6VPm3XXNaHPyUTTte+3wGv7qVQtqaZCrgstI16gCuhNOtBXLEBkBREP57YTd68P0VgDgG73jSD8bwXQ=="],
"@opennextjs/aws/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="],
"@opennextjs/aws/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.2", "", { "os": "none", "cpu": "x64" }, "sha512-WNa5zZk1XpTTwMDompZmvQLHszDDDN7lYjEHCUmAGB83Bgs20EMs7ICD+oKeT6xt4phV4NDdSi/8OfjPbSbZfQ=="],
"@opennextjs/aws/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="],
"@opennextjs/aws/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-S6kI1aT3S++Dedb7vxIuUOb3oAxqxk2Rh5rOXOTYnzN8JzW1VzBd+IqPiSpgitu45042SYD3HCoEyhLKQcDFDw=="],
"@opennextjs/aws/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="],
"@opennextjs/aws/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-VXSSMsmb+Z8LbsQGcBMiM+fYObDNRm8p7tkUDMPG/g4fhFX5DEFmjxIEa3N8Zr96SjsJ1woAhF0DUnS3MF3ARw=="],
"@opennextjs/aws/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="],
"@opennextjs/aws/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-5NayUlSAyb5PQYFAU9x3bHdsqB88RC3aM9lKDAz4X1mo/EchMIT1Q+pSeBXNgkfNmRecLXA0O8xP+x8V+g/LKg=="],
"@opennextjs/aws/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="],
"@opennextjs/aws/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-47gL/ek1v36iN0wL9L4Q2MFdujR0poLZMJwhO2/N3gA89jgHp4MR8DKCmwYtGNksbfJb9JoTtbkoe6sDhg2QTA=="],
"@opennextjs/aws/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="],
"@opennextjs/aws/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.2", "", { "os": "win32", "cpu": "x64" }, "sha512-tcuhV7ncXBqbt/Ybf0IyrMcwVOAPDckMK9rXNHtF17UTK18OKLpg08glminN06pt2WCoALhXdLfSPbVvK/6fxw=="],
"@opennextjs/aws/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="],
"@opennextjs/aws/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="],
"@opennextjs/aws/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="],
"@opennextjs/aws/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
"@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
@@ -5013,6 +5046,10 @@
"read-yaml-file/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
"remark-gfm/mdast-util-gfm/mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA=="],
"remark-gfm/mdast-util-gfm/mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ=="],
"rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+1 -1
View File
@@ -10,7 +10,7 @@
"packageManager": "bun@1.2.11",
"overrides": {
"@codemirror/state": "6.4.1",
"@gitbook/api": "^0.115.0",
"@gitbook/api": "^0.119.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
+1 -1
View File
@@ -10,7 +10,7 @@
},
"version": "0.3.1",
"dependencies": {
"@gitbook/api": "^0.115.0",
"@gitbook/api": "^0.119.0",
"assert-never": "^1.2.1"
},
"devDependencies": {
+1
View File
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
+28 -26
View File
@@ -1,27 +1,29 @@
import { defineCloudflareConfig } from '@opennextjs/cloudflare';
import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue';
import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache';
import {
softTagFilter,
withFilter,
} from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter';
import type { OpenNextConfig } from '@opennextjs/cloudflare';
export default defineCloudflareConfig({
incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default),
tagCache: withFilter({
tagCache: doShardedTagCache({
baseShardSize: 12,
regionalCache: true,
shardReplication: {
numberOfSoftReplicas: 2,
numberOfHardReplicas: 1,
},
}),
// We don't use `revalidatePath`, so we filter out soft tags
filterFn: softTagFilter,
}),
queue: doQueue,
// Performance improvements as we don't use PPR
enableCacheInterception: true,
});
export default {
default: {
override: {
wrapper: 'cloudflare-node',
converter: 'edge',
proxyExternalRequest: 'fetch',
queue: () => import('./openNext/queue/middleware').then((m) => m.default),
incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default),
tagCache: () => import('./openNext/tagCache/middleware').then((m) => m.default),
},
},
middleware: {
external: true,
override: {
wrapper: 'cloudflare-edge',
converter: 'edge',
proxyExternalRequest: 'fetch',
queue: () => import('./openNext/queue/middleware').then((m) => m.default),
incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default),
tagCache: () => import('./openNext/tagCache/middleware').then((m) => m.default),
},
},
dangerous: {
enableCacheInterception: true,
},
edgeExternals: ['node:crypto'],
} satisfies OpenNextConfig;
@@ -0,0 +1,36 @@
import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js';
import { DurableObject } from 'cloudflare:workers';
// Only needed to run locally, in prod we'll use the one from do.js
export class R2WriteBuffer extends DurableObject {
writePromise;
async write(cacheKey, value) {
// We are already writing to this key
if (this.writePromise) {
return;
}
this.writePromise = this.env.NEXT_INC_CACHE_R2_BUCKET.put(cacheKey, value);
this.ctx.waitUntil(
this.writePromise.finally(() => {
this.writePromise = undefined;
})
);
}
}
export default {
async fetch(request, env, ctx) {
return runWithCloudflareRequestContext(request, env, ctx, async () => {
// We can't move the handler import to the top level, otherwise the runtime will not be properly initialized
const { handler } = await import(
'../../.open-next/server-functions/default/handler.mjs'
);
// - `Request`s are handled by the Next server
return handler(request, env, ctx);
});
},
};
@@ -0,0 +1,163 @@
{
"main": "default.js",
"name": "gitbook-open-v2-server",
"compatibility_date": "2025-04-14",
"compatibility_flags": [
"nodejs_compat",
"allow_importable_env",
"global_fetch_strictly_public"
],
"observability": {
"enabled": true
},
"vars": {
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
},
"env": {
"dev": {
"vars": {
"STAGE": "dev"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-preview"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-server-dev"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["R2WriteBuffer"]
}
]
},
"preview": {
"vars": {
"STAGE": "preview",
// Just as a test for the preview environment to check that everything works
"NEXT_PRIVATE_DEBUG_CACHE": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-preview"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-server-preview"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-preview"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-preview"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-preview"
}
]
}
},
"staging": {
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-staging"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-server-staging"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-staging"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-staging"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-staging"
}
]
},
"tail_consumers": [
{
"service": "gitbook-x-staging-tail"
}
]
},
"production": {
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-production"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-server-production"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-production"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-production"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-production"
}
]
},
"tail_consumers": [
{
"service": "gitbook-x-prod-tail"
}
]
}
}
}
@@ -0,0 +1,38 @@
// This worker only purposes it to host the different DO that we will need in the other workers.
import { DurableObject } from 'cloudflare:workers';
// `use cache` could cause multiple writes to the same key to happen concurrently, there is a limit of 1 write per key/second
// so we need to buffer writes to the R2 bucket to avoid hitting this limit.
export class R2WriteBuffer extends DurableObject {
writePromise;
async write(cacheKey, value) {
// We are already writing to this key
if (this.writePromise) {
return;
}
this.writePromise = this.env.NEXT_INC_CACHE_R2_BUCKET.put(cacheKey, value);
this.ctx.waitUntil(
this.writePromise.finally(() => {
this.writePromise = undefined;
})
);
}
}
export { DOQueueHandler } from '../../.open-next/.build/durable-objects/queue.js';
export { DOShardedTagCache } from '../../.open-next/.build/durable-objects/sharded-tag-cache.js';
export default {
async fetch() {
// This worker does not handle any requests, it only provides Durable Objects
return new Response('This worker is not meant to handle requests directly', {
status: 400,
headers: {
'Content-Type': 'text/plain',
},
});
},
};
@@ -0,0 +1,127 @@
{
"main": "do.js",
"name": "gitbook-open-v2-do",
"compatibility_date": "2025-04-14",
"compatibility_flags": [
"nodejs_compat",
"allow_importable_env",
"global_fetch_strictly_public"
],
"observability": {
"enabled": true
},
"env": {
"preview": {
"vars": {
"STAGE": "preview",
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-preview"
}
],
"durable_objects": {
"bindings": [
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache"
},
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"]
}
]
},
"staging": {
"vars": {
"STAGE": "staging",
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-staging"
}
],
"tail_consumers": [
{
"service": "gitbook-x-staging-tail"
}
],
"durable_objects": {
"bindings": [
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache"
},
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"]
}
]
},
"production": {
"vars": {
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true",
"STAGE": "production"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-production"
}
],
"tail_consumers": [
{
"service": "gitbook-x-prod-tail"
}
],
"durable_objects": {
"bindings": [
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache"
},
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"]
}
]
}
}
}
@@ -0,0 +1,42 @@
import { WorkerEntrypoint } from 'cloudflare:workers';
import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js';
import { handler as middlewareHandler } from '../../.open-next/middleware/handler.mjs';
export { DOQueueHandler } from '../../.open-next/.build/durable-objects/queue.js';
export { DOShardedTagCache } from '../../.open-next/.build/durable-objects/sharded-tag-cache.js';
export default class extends WorkerEntrypoint {
async fetch(request) {
return runWithCloudflareRequestContext(request, this.env, this.ctx, async () => {
// - `Request`s are handled by the Next server
const reqOrResp = await middlewareHandler(request, this.env, this.ctx);
if (reqOrResp instanceof Response) {
return reqOrResp;
}
if (this.env.STAGE !== 'preview') {
// https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#version-affinity
reqOrResp.headers.set(
'Cloudflare-Workers-Version-Overrides',
`gitbook-open-v2-${this.env.STAGE}="${this.env.WORKER_VERSION_ID}"`
);
return this.env.DEFAULT_WORKER?.fetch(reqOrResp, {
cf: {
cacheEverything: false,
},
});
}
// If we are in preview mode, we need to send the request to the preview URL
const modifiedUrl = new URL(reqOrResp.url);
modifiedUrl.hostname = this.env.PREVIEW_HOSTNAME;
const nextRequest = new Request(modifiedUrl, reqOrResp);
return fetch(nextRequest, {
cf: {
cacheEverything: false,
},
});
});
}
}
@@ -0,0 +1,216 @@
{
"main": "middleware.js",
"name": "gitbook-open-v2",
"compatibility_date": "2025-04-14",
"compatibility_flags": [
"nodejs_compat",
"allow_importable_env",
"global_fetch_strictly_public"
],
"assets": {
"directory": "../../.open-next/assets",
"binding": "ASSETS"
},
"observability": {
"enabled": true
},
"vars": {
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
},
"env": {
"dev": {
"vars": {
"STAGE": "dev",
"NEXT_PRIVATE_DEBUG_CACHE": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-preview"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-dev"
},
{
"binding": "DEFAULT_WORKER",
"service": "gitbook-open-v2-server-dev"
}
]
},
"preview": {
"vars": {
"STAGE": "preview",
"PREVIEW_HOSTNAME": "TO_REPLACE",
"WORKER_VERSION_ID": "TO_REPLACE"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-preview"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-preview"
},
{
"binding": "DEFAULT_WORKER",
"service": "gitbook-open-v2-server-preview"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-preview"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-preview"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-preview"
}
]
}
},
"staging": {
"vars": {
"STAGE": "staging",
"WORKER_VERSION_ID": "TO_REPLACE"
},
"routes": [
{
"pattern": "open-2c.gitbook-staging.com/*",
"zone_name": "gitbook-staging.com"
},
{
"pattern": "static-2c.gitbook-staging.com/*",
"zone_name": "gitbook-staging.com"
}
],
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-staging"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-staging"
},
{
"binding": "DEFAULT_WORKER",
"service": "gitbook-open-v2-server-staging"
}
],
"tail_consumers": [
{
"service": "gitbook-x-staging-tail"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-staging"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-staging"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-staging"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"]
}
]
},
"production": {
"vars": {
// This is a bit misleading, but it means that we can have 500 concurrent revalidations
// This means that we'll have up to 100 durable objects instance running at the same time
"MAX_REVALIDATE_CONCURRENCY": "100",
// Temporary variable to find the issue once deployed
// TODO: remove this once the issue is fixed
"DEBUG_CLOUDFLARE": "true",
"WORKER_VERSION_ID": "TO_REPLACE",
"STAGE": "production"
},
"routes": [
{
"pattern": "open-2c.gitbook.com/*",
"zone_name": "gitbook.com"
},
{
"pattern": "static-2c.gitbook.com/*",
"zone_name": "gitbook.com"
}
],
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-production"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-production"
},
{
"binding": "DEFAULT_WORKER",
"service": "gitbook-open-v2-server-production"
}
],
"tail_consumers": [
{
"service": "gitbook-x-prod-tail"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-production"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-production"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-production"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"]
}
]
}
}
}
@@ -0,0 +1,26 @@
// In this script, we use the args from the cli to update the PREVIEW_URL vars in the wrangler config file for the middleware
import fs from 'node:fs';
import path from 'node:path';
const wranglerConfigPath = path.join(__dirname, '../middlewareWrangler.jsonc');
const file = fs.readFileSync(wranglerConfigPath, 'utf-8');
const args = process.argv.slice(2);
// The versionId is in the format xxx-xxx-xxx-xxx, we need the first part to reconstruct the preview URL
const versionId = args[0];
// The preview URL is in the format https://<versionId>-gitbook-open-v2-server-preview.gitbook.workers.dev
const previewHostname = `${versionId.split('-')[0]}-gitbook-open-v2-server-preview.gitbook.workers.dev`;
let updatedFile = file.replace(
/"PREVIEW_HOSTNAME": "TO_REPLACE"/,
`"PREVIEW_HOSTNAME": "${previewHostname}"`
);
updatedFile = updatedFile.replaceAll(
/"WORKER_VERSION_ID": "TO_REPLACE"/g,
`"WORKER_VERSION_ID": "${versionId}"`
);
fs.writeFileSync(wranglerConfigPath, updatedFile);
@@ -9,6 +9,8 @@ import type {
} from '@opennextjs/aws/types/overrides.js';
import { getCloudflareContext } from '@opennextjs/cloudflare';
import type { DurableObjectNamespace, Rpc } from '@cloudflare/workers-types';
export const BINDING_NAME = 'NEXT_INC_CACHE_R2_BUCKET';
export const DEFAULT_PREFIX = 'incremental-cache';
@@ -79,12 +81,10 @@ class GitbookIncrementalCache implements IncrementalCache {
},
async (span) => {
span.setAttribute('cacheType', cacheType ?? 'cache');
const r2 = getCloudflareContext().env[BINDING_NAME];
const localCache = await this.getCacheInstance();
if (!r2) throw new Error('No R2 bucket');
try {
await r2.put(cacheKey, JSON.stringify(value));
await this.writeToR2(cacheKey, JSON.stringify(value));
//TODO: Check if there is any places where we don't have tags
// Ideally we should always have tags, but in case we don't, we need to decide how to handle it
@@ -145,6 +145,22 @@ class GitbookIncrementalCache implements IncrementalCache {
);
}
async writeToR2(key: string, value: string): Promise<void> {
const env = getCloudflareContext().env as {
WRITE_BUFFER: DurableObjectNamespace<
Rpc.DurableObjectBranded & {
write: (key: string, value: string) => Promise<void>;
}
>;
};
const id = env.WRITE_BUFFER.idFromName(key);
// A stub is a client used to invoke methods on the Durable Object
const stub = env.WRITE_BUFFER.get(id);
await stub.write(key, value);
}
async getCacheInstance(): Promise<Cache> {
if (this.localCache) return this.localCache;
this.localCache = await caches.open('incremental-cache');
@@ -0,0 +1,14 @@
import { trace } from '@/lib/tracing';
import type { Queue } from '@opennextjs/aws/types/overrides.js';
import { getCloudflareContext } from '@opennextjs/cloudflare';
import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue';
export default {
name: 'GitbookISRQueue',
send: async (msg) => {
return trace({ operation: 'gitbookISRQueueSend', name: msg.MessageBody.url }, async () => {
const { ctx } = getCloudflareContext();
ctx.waitUntil(doQueue.send(msg));
});
},
} satisfies Queue;
@@ -0,0 +1,9 @@
import type { Queue } from '@opennextjs/aws/types/overrides.js';
export default {
name: 'GitbookISRQueue',
send: async (msg) => {
// We should never reach this point in the server. If that's the case, we should log it.
console.warn('GitbookISRQueue: send called on server side, this should not happen.', msg);
},
} satisfies Queue;
@@ -0,0 +1,81 @@
import { trace } from '@/lib/tracing';
import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js';
import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache';
import { softTagFilter } from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter';
const originalTagCache = doShardedTagCache({
baseShardSize: 12,
regionalCache: true,
// We can probably increase this value even further
regionalCacheTtlSec: 60,
shardReplication: {
numberOfSoftReplicas: 2,
numberOfHardReplicas: 1,
regionalReplication: {
defaultRegion: 'enam',
},
},
});
export default {
name: 'GitbookTagCache',
mode: 'nextMode',
getLastRevalidated: async (tags: string[]) => {
const tagsToCheck = tags.filter(softTagFilter);
if (tagsToCheck.length === 0) {
// If we reach here, it probably means that there is an issue that we'll need to address.
console.warn(
'getLastRevalidated - No valid tags to check for last revalidation, original tags:',
tags
);
return 0; // If no tags to check, return 0
}
return trace(
{
operation: 'gitbookTagCacheGetLastRevalidated',
name: tagsToCheck.join(', '),
},
async () => {
return await originalTagCache.getLastRevalidated(tagsToCheck);
}
);
},
hasBeenRevalidated: async (tags: string[], lastModified?: number) => {
const tagsToCheck = tags.filter(softTagFilter);
if (tagsToCheck.length === 0) {
// If we reach here, it probably means that there is an issue that we'll need to address.
console.warn(
'hasBeenRevalidated - No valid tags to check for revalidation, original tags:',
tags
);
return false; // If no tags to check, return false
}
return trace(
{
operation: 'gitbookTagCacheHasBeenRevalidated',
name: tagsToCheck.join(', '),
},
async () => {
const result = await originalTagCache.hasBeenRevalidated(tagsToCheck, lastModified);
return result;
}
);
},
writeTags: async (tags: string[]) => {
return trace(
{
operation: 'gitbookTagCacheWriteTags',
name: tags.join(', '),
},
async () => {
const tagsToWrite = tags.filter(softTagFilter);
if (tagsToWrite.length === 0) {
console.warn('writeTags - No valid tags to write');
return; // If no tags to write, exit early
}
// Write only the filtered tags
await originalTagCache.writeTags(tagsToWrite);
}
);
},
} satisfies NextModeTagCache;
+4 -2
View File
@@ -3,9 +3,9 @@
"version": "0.3.0",
"private": true,
"dependencies": {
"@gitbook/api": "^0.115.0",
"@gitbook/api": "^0.119.0",
"@gitbook/cache-tags": "workspace:*",
"@opennextjs/cloudflare": "1.0.3",
"@opennextjs/cloudflare": "1.1.0",
"@sindresorhus/fnv1a": "^3.1.0",
"assert-never": "^1.2.1",
"jwt-decode": "^4.0.0",
@@ -30,6 +30,8 @@
"start": "next start",
"build:v2:cloudflare": "opennextjs-cloudflare build",
"dev:v2:cloudflare": "wrangler dev --port 8771 --env preview",
"dev:v2:cf:middleware": "wrangler dev --port 8771 --inspector-port 9230 --env dev --config ./openNext/customWorkers/middlewareWrangler.jsonc",
"dev:v2:cf:server": "wrangler dev --port 8772 --env dev --config ./openNext/customWorkers/defaultWrangler.jsonc",
"unit": "bun test",
"typecheck": "tsc --noEmit"
}
@@ -0,0 +1,14 @@
import type { NextRequest } from 'next/server';
import { serveLLMsFullTxt } from '@/routes/llms-full';
import { type RouteLayoutParams, getStaticSiteContext } from '@v2/app/utils';
export const dynamic = 'force-static';
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<RouteLayoutParams> }
) {
const { context } = await getStaticSiteContext(await params);
return serveLLMsFullTxt(context);
}
+1 -1
View File
@@ -165,7 +165,7 @@ export function getBaseContext(input: {
// Create link in the same format for links to other sites/sections.
linker.toLinkForContent = (rawURL: string) => {
const urlObject = new URL(rawURL);
return `/url/${urlObject.host}${urlObject.pathname}${urlObject.search}`;
return `/url/${urlObject.host}${urlObject.pathname}${urlObject.search}${urlObject.hash}`;
};
}
File diff suppressed because it is too large Load Diff
@@ -11,15 +11,3 @@ export function getCloudflareContext() {
return getCloudflareContextOpenNext();
}
/**
* Return an object representing the current request.
*/
export function getCloudflareRequestGlobal() {
const context = getCloudflareContext();
if (!context) {
return null;
}
return context.cf;
}
@@ -4,5 +4,4 @@ export * from './pages';
export * from './urls';
export * from './errors';
export * from './lookup';
export * from './proxy';
export * from './visitor';
@@ -1,52 +0,0 @@
import { describe, expect, it, mock } from 'bun:test';
import { AsyncLocalStorage } from 'node:async_hooks';
import { withCacheKey, withoutConcurrentExecution } from './memoize';
describe('withoutConcurrentExecution', () => {
it('should memoize the function based on the cache key', async () => {
const fn = mock(async (_cacheKey: string, a: number, b: number) => a + b);
const memoized = withoutConcurrentExecution(() => null, fn);
const p1 = memoized('c1', 1, 2);
const p2 = memoized('c1', 1, 2);
const p3 = memoized('c3', 2, 3);
expect(await p1).toBe(await p2);
expect(await p1).not.toBe(await p3);
expect(fn.mock.calls.length).toBe(2);
});
it('should support caching per request', async () => {
const fn = mock(async () => Math.random());
const request1 = { id: 'request1' };
const request2 = { id: 'request2' };
const requestContext = new AsyncLocalStorage<{ id: string }>();
const memoized = withoutConcurrentExecution(() => requestContext.getStore(), fn);
// Both in the same request
const promise1 = requestContext.run(request1, () => memoized('c1'));
const promise2 = requestContext.run(request1, () => memoized('c1'));
// In a different request
const promise3 = requestContext.run(request2, () => memoized('c1'));
expect(await promise1).toBe(await promise2);
expect(await promise1).not.toBe(await promise3);
expect(fn.mock.calls.length).toBe(2);
});
});
describe('withCacheKey', () => {
it('should wrap the function by passing the cache key', async () => {
const fn = mock(
async (cacheKey: string, arg: { a: number; b: number }, c: number) =>
`${cacheKey}, result=${arg.a + arg.b + c}`
);
const memoized = withCacheKey(fn);
expect(await memoized({ a: 1, b: 2 }, 4)).toBe('[[["a",1],["b",2]],4], result=7');
expect(fn.mock.calls.length).toBe(1);
});
});
@@ -1,87 +0,0 @@
/**
* Wrap a function by preventing concurrent executions of the same function.
* With a logic to work per-request in Cloudflare Workers.
*/
export function withoutConcurrentExecution<ArgsType extends any[], ReturnType>(
getGlobalContext: () => object | null | undefined,
wrapped: (key: string, ...args: ArgsType) => Promise<ReturnType>
): (cacheKey: string, ...args: ArgsType) => Promise<ReturnType> {
const globalPromiseCache = new WeakMap<object, Map<string, Promise<ReturnType>>>();
return (key: string, ...args: ArgsType) => {
const globalContext = getGlobalContext() ?? globalThis;
/**
* Cache storage that is scoped to the current request when executed in Cloudflare Workers,
* to avoid "Cannot perform I/O on behalf of a different request" errors.
*/
const promiseCache =
globalPromiseCache.get(globalContext) ?? new Map<string, Promise<ReturnType>>();
globalPromiseCache.set(globalContext, promiseCache);
const concurrent = promiseCache.get(key);
if (concurrent) {
return concurrent;
}
const promise = (async () => {
try {
const result = await wrapped(key, ...args);
return result;
} finally {
promiseCache.delete(key);
}
})();
promiseCache.set(key, promise);
return promise;
};
}
/**
* Wrap a function by passing it a cache key that is computed from the function arguments.
*/
export function withCacheKey<ArgsType extends any[], ReturnType>(
wrapped: (cacheKey: string, ...args: ArgsType) => Promise<ReturnType>
): (...args: ArgsType) => Promise<ReturnType> {
return (...args: ArgsType) => {
const cacheKey = getCacheKey(args);
return wrapped(cacheKey, ...args);
};
}
/**
* Compute a cache key from the function arguments.
*/
function getCacheKey(args: any[]) {
return JSON.stringify(deepSortValue(args));
}
function deepSortValue(value: unknown): unknown {
if (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean' ||
value === null ||
value === undefined
) {
return value;
}
if (Array.isArray(value)) {
return value.map(deepSortValue);
}
if (value && typeof value === 'object') {
return Object.entries(value)
.map(([key, subValue]) => {
return [key, deepSortValue(subValue)] as const;
})
.sort((a, b) => {
return a[0].localeCompare(b[0]);
});
}
return value;
}
@@ -189,5 +189,7 @@ export interface GitBookDataFetcher {
input: api.AIMessageInput[];
output: api.AIOutputFormat;
model: api.AIModel;
instructions?: string;
previousResponseId?: string;
}): AsyncGenerator<api.AIStreamResponse, void, unknown>;
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { withLeadingSlash, withTrailingSlash } from '@/lib/paths';
import type { PublishedSiteContent } from '@gitbook/api';
import { getProxyRequestIdentifier, isProxyRequest } from './proxy';
import { getProxyRequestIdentifier, isProxyRequest } from '@v2/lib/proxy';
/**
* Get the appropriate base path for the visitor authentication cookie.
@@ -0,0 +1,19 @@
import { describe, expect, it } from 'bun:test';
import { getImageResizingContextId } from './getImageResizingContextId';
describe('getImageResizingContextId', () => {
it('should return proxy identifier for proxy requests', () => {
const proxyRequestURL = new URL('https://proxy.gitbook.site/sites/site_foo/hello/world');
expect(getImageResizingContextId(proxyRequestURL)).toBe('sites/site_foo');
});
it('should return preview identifier for preview requests', () => {
const previewRequestURL = new URL('https://preview/site_foo/hello/world');
expect(getImageResizingContextId(previewRequestURL)).toBe('site_foo');
});
it('should return host for regular requests', () => {
const regularRequestURL = new URL('https://example.com/docs/foo/hello/world');
expect(getImageResizingContextId(regularRequestURL)).toBe('example.com');
});
});
@@ -1,4 +1,5 @@
import { getProxyRequestIdentifier, isProxyRequest } from '../data';
import { getPreviewRequestIdentifier, isPreviewRequest } from '@v2/lib/preview';
import { getProxyRequestIdentifier, isProxyRequest } from '@v2/lib/proxy';
/**
* Get the site identifier to use for image resizing for an incoming request.
@@ -8,6 +9,9 @@ export function getImageResizingContextId(url: URL): string {
if (isProxyRequest(url)) {
return getProxyRequestIdentifier(url);
}
if (isPreviewRequest(url)) {
return getPreviewRequestIdentifier(url);
}
return url.host;
}
+21 -1
View File
@@ -19,7 +19,7 @@ const siteGitBookIO = createLinker({
siteBasePath: '/sitename/',
});
describe('toPathInContent', () => {
describe('toPathInSpace', () => {
it('should return the correct path', () => {
expect(root.toPathInSpace('some/path')).toBe('/some/path');
expect(variantInSection.toPathInSpace('some/path')).toBe('/section/variant/some/path');
@@ -29,6 +29,16 @@ describe('toPathInContent', () => {
expect(root.toPathInSpace('/some/path')).toBe('/some/path');
expect(variantInSection.toPathInSpace('/some/path')).toBe('/section/variant/some/path');
});
it('should remove the trailing slash', () => {
expect(root.toPathInSpace('some/path/')).toBe('/some/path');
expect(variantInSection.toPathInSpace('some/path/')).toBe('/section/variant/some/path');
});
it('should not add a trailing slash', () => {
expect(root.toPathInSpace('')).toBe('');
expect(variantInSection.toPathInSpace('')).toBe('/section/variant');
});
});
describe('toPathInSite', () => {
@@ -36,6 +46,16 @@ describe('toPathInSite', () => {
expect(root.toPathInSite('some/path')).toBe('/some/path');
expect(siteGitBookIO.toPathInSite('some/path')).toBe('/sitename/some/path');
});
it('should remove the trailing slash', () => {
expect(root.toPathInSite('some/path/')).toBe('/some/path');
expect(siteGitBookIO.toPathInSite('some/path/')).toBe('/sitename/some/path');
});
it('should not add a trailing slash', () => {
expect(root.toPathInSite('')).toBe('');
expect(siteGitBookIO.toPathInSite('')).toBe('/sitename');
});
});
describe('toRelativePathInSite', () => {
+5 -1
View File
@@ -128,5 +128,9 @@ export function createLinker(
function joinPaths(prefix: string, path: string): string {
const prefixPath = prefix.endsWith('/') ? prefix : `${prefix}/`;
const suffixPath = path.startsWith('/') ? path.slice(1) : path;
return prefixPath + suffixPath;
return removeTrailingSlash(prefixPath + suffixPath);
}
function removeTrailingSlash(path: string): string {
return path.endsWith('/') ? path.slice(0, -1) : path;
}
@@ -0,0 +1,21 @@
import { describe, expect, it } from 'bun:test';
import { getPreviewRequestIdentifier, isPreviewRequest } from './preview';
describe('isPreviewRequest', () => {
it('should return true for preview requests', () => {
const previewRequestURL = new URL('https://preview/site_foo/hello/world');
expect(isPreviewRequest(previewRequestURL)).toBe(true);
});
it('should return false for non-preview requests', () => {
const nonPreviewRequestURL = new URL('https://example.com/docs/foo/hello/world');
expect(isPreviewRequest(nonPreviewRequestURL)).toBe(false);
});
});
describe('getPreviewRequestIdentifier', () => {
it('should return the correct identifier for preview requests', () => {
const previewRequestURL = new URL('https://preview/site_foo/hello/world');
expect(getPreviewRequestIdentifier(previewRequestURL)).toBe('site_foo');
});
});
+13
View File
@@ -0,0 +1,13 @@
/**
* Check if the request to the site is a preview request.
*/
export function isPreviewRequest(requestURL: URL): boolean {
return requestURL.host === 'preview';
}
export function getPreviewRequestIdentifier(requestURL: URL): string {
// For preview requests, we extract the site ID from the pathname
// e.g. https://preview/site_id/...
const pathname = requestURL.pathname.slice(1).split('/');
return pathname[0];
}
@@ -0,0 +1,49 @@
import crypto from 'node:crypto';
import type { NextApiRequest, NextApiResponse } from 'next';
interface JsonBody {
// The paths need to be the rewritten one, `res.revalidate` call don't go through the middleware
paths: string[];
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
// Only allow POST requests
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const signatureHeader = req.headers['x-gitbook-signature'] as string | undefined;
if (!signatureHeader) {
return res.status(400).json({ error: 'Missing signature header' });
}
// We cannot use env from `@/v2/lib/env` here as it make it crash because of the import "server-only" in the file.
if (process.env.GITBOOK_SECRET) {
try {
const computedSignature = crypto
.createHmac('sha256', process.env.GITBOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
if (computedSignature === signatureHeader) {
const results = await Promise.allSettled(
(req.body as JsonBody).paths.map((path) => {
// biome-ignore lint/suspicious/noConsole: we want to log here
console.log(`Revalidating path: ${path}`);
return res.revalidate(path);
})
);
return res.status(200).json({
success: results.every((result) => result.status === 'fulfilled'),
errors: results
.filter((result) => result.status === 'rejected')
.map((result) => (result as PromiseRejectedResult).reason),
});
}
return res.status(401).json({ error: 'Invalid signature' });
} catch (error) {
console.error('Error during revalidation:', error);
return res.status(400).json({ error: 'Invalid request or unable to parse JSON' });
}
}
// If no secret is set, we do not allow revalidation
return res.status(403).json({ error: 'Revalidation is disabled' });
}
+11 -4
View File
@@ -15,10 +15,14 @@
"enabled": true
},
"vars": {
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true",
"IS_PREVIEW": "false"
},
"env": {
"preview": {
"vars": {
"IS_PREVIEW": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
@@ -91,9 +95,12 @@
},
"production": {
"vars": {
// This is a bit misleading, but it means that we can have 100 concurrent revalidations
// This means that we'll have up to 20 durable objects instance running at the same time
"MAX_REVALIDATE_CONCURRENCY": "20"
// This is a bit misleading, but it means that we can have 500 concurrent revalidations
// This means that we'll have up to 100 durable objects instance running at the same time
"MAX_REVALIDATE_CONCURRENCY": "100",
// Temporary variable to find the issue once deployed
// TODO: remove this once the issue is fixed
"DEBUG_CLOUDFLARE": "true"
},
"routes": [
{
+54
View File
@@ -1,9 +1,11 @@
import {
CustomizationBackground,
CustomizationCorners,
CustomizationDepth,
CustomizationHeaderPreset,
CustomizationIconsStyle,
CustomizationSidebarListStyle,
CustomizationThemeMode,
} from '@gitbook/api';
import { expect } from '@playwright/test';
import jwt from 'jsonwebtoken';
@@ -430,6 +432,38 @@ const testCases: TestsCase[] = [
},
],
},
{
name: 'llms.txt',
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
tests: [
{
name: 'llms.txt',
url: 'llms.txt',
screenshot: false,
run: async (_page, response) => {
expect(response?.status()).toBe(200);
expect(response?.headers()['content-type']).toContain('text/markdown');
},
},
],
},
{
name: 'llms-full.txt',
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
contentBaseURL: 'https://gitbook.gitbook.io/test-gitbook-open/',
tests: [
{
name: 'llms-full.txt',
url: 'llms-full.txt',
screenshot: false,
run: async (_page, response) => {
expect(response?.status()).toBe(200);
expect(response?.headers()['content-type']).toContain('text/markdown');
},
},
],
},
{
name: 'Site subdirectory (proxy)',
skip: process.env.ARGOS_BUILD_NAME !== 'v2-vercel',
@@ -648,6 +682,16 @@ const testCases: TestsCase[] = [
url: 'page-options/page-with-cover',
run: waitForCookiesDialog,
},
{
name: 'With cover for dark mode',
url: `page-options/page-with-dark-cover${getCustomizationURL({
themes: {
default: CustomizationThemeMode.Dark,
toggeable: false,
},
})}`,
run: waitForCookiesDialog,
},
{
name: 'With hero cover',
url: 'page-options/page-with-hero-cover',
@@ -817,6 +861,16 @@ const testCases: TestsCase[] = [
}),
run: waitForCookiesDialog,
},
{
name: `With flat and circular corners - Theme mode ${themeMode}`,
url: getCustomizationURL({
styling: {
depth: CustomizationDepth.Flat,
corners: CustomizationCorners.Circular,
},
}),
run: waitForCookiesDialog,
},
]),
},
{
+2
View File
@@ -3,6 +3,7 @@ import {
CustomizationBackground,
CustomizationCorners,
CustomizationDefaultFont,
CustomizationDepth,
type CustomizationHeaderItem,
CustomizationHeaderPreset,
CustomizationIconsStyle,
@@ -278,6 +279,7 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
dangerColor: { light: '#FB2C36', dark: '#FB2C36' },
successColor: { light: '#00C950', dark: '#00C950' },
corners: CustomizationCorners.Rounded,
depth: CustomizationDepth.Subtle,
font: CustomizationDefaultFont.Inter,
background: CustomizationBackground.Plain,
icons: CustomizationIconsStyle.Regular,
+9 -2
View File
@@ -16,7 +16,7 @@
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static/icons && rm -rf ./public/~gitbook/static/math"
},
"dependencies": {
"@gitbook/api": "^0.115.0",
"@gitbook/api": "^0.119.0",
"@gitbook/cache-do": "workspace:*",
"@gitbook/cache-tags": "workspace:*",
"@gitbook/colors": "workspace:*",
@@ -46,13 +46,20 @@
"katex": "^0.16.9",
"mathjax": "^3.2.2",
"mdast-util-to-markdown": "^2.1.2",
"mdast-util-from-markdown": "^2.0.2",
"mdast-util-frontmatter": "^2.0.1",
"mdast-util-gfm": "^3.1.0",
"micromark-extension-gfm": "^3.0.0",
"micromark-extension-frontmatter": "^2.0.0",
"unist-util-remove": "^4.0.0",
"unist-util-visit": "^5.0.0",
"memoizee": "^0.4.17",
"next": "14.2.26",
"next-themes": "^0.2.1",
"nuqs": "^2.2.3",
"object-hash": "^3.0.0",
"openapi-types": "^12.1.3",
"p-map": "^7.0.0",
"p-map": "^7.0.3",
"parse-cache-control": "^1.0.1",
"partial-json": "^0.1.7",
"react": "^19.0.0",
@@ -1,5 +1,6 @@
'use client';
import { tString, useLanguage } from '@/intl/client';
import * as storage from '@/lib/local-storage';
import type { ResolvedContentRef } from '@/lib/references';
import { tcls } from '@/lib/tailwind';
@@ -18,6 +19,8 @@ export function AnnouncementBanner(props: {
}) {
const { announcement, contentRef } = props;
const language = useLanguage();
const hasLink = announcement.link && contentRef?.href;
const closeable = announcement.style !== 'danger';
@@ -25,13 +28,13 @@ export function AnnouncementBanner(props: {
const style = BANNER_STYLES[announcement.style];
return (
<div className="announcement-banner theme-bold:bg-header-background pt-4 pb-2">
<div id="announcement-banner" className="theme-bold:bg-header-background pt-4 pb-2">
<div className="scroll-nojump">
<div className={tcls('relative', CONTAINER_STYLE)}>
<Tag
href={contentRef?.href ?? ''}
className={tcls(
'flex w-full items-start justify-center overflow-hidden rounded-md straight-corners:rounded-none px-4 py-3 text-neutral-strong text-sm theme-bold:ring-1 theme-gradient:ring-1 ring-inset transition-colors',
'flex w-full items-start justify-center overflow-hidden circular-corners:rounded-xl rounded-md straight-corners:rounded-none px-4 py-3 text-neutral-strong text-sm theme-bold:ring-1 theme-gradient:ring-1 ring-inset transition-colors',
style.container,
closeable && 'pr-12',
hasLink && style.hover
@@ -78,9 +81,10 @@ export function AnnouncementBanner(props: {
</Tag>
{closeable ? (
<button
className={`absolute top-0 right-4 mt-2 mr-2 rounded straight-corners:rounded-none p-1.5 transition-all hover:ring-1 sm:right-6 md:right-8 ${style.close}`}
className={`absolute top-0 right-4 mt-2 mr-2 rounded circular-corners:rounded-lg straight-corners:rounded-none p-1.5 transition-all hover:ring-1 sm:right-6 md:right-8 ${style.close}`}
type="button"
onClick={dismissAnnouncement}
title={tString(language, 'close')}
>
<Icon icon="close" className="size-4" />
</button>
@@ -0,0 +1,66 @@
'use client';
import { tcls } from '@/lib/tailwind';
import { Button } from '../primitives';
import { AskInput } from './AskInput';
import { AskMessages } from './AskMessages';
import { useAskController, useAskState } from './state';
export function AskDialog() {
const state = useAskState();
const controller = useAskController();
if (!state.opened) {
return null;
}
return (
<div
role="dialog"
className={`ask-dialog ${tcls(
'z-40',
'fixed',
'top-3',
'right-3',
'bottom-3',
'w-[480px]',
'flex',
'flex-col',
'bg-tint-base',
'rounded-lg',
'straight-corners:rounded-sm',
'circular-corners:rounded-2xl',
'ring-1',
'ring-tint',
'shadow-2xl',
'depth-flat:shadow-none',
'overflow-hidden',
'dark:ring-inset',
'dark:ring-tint'
)})`}
>
<div className="flex flex-row">
<div className="flex-1"></div>
<div className="p-2">
<Button
variant="blank"
icon="close"
iconOnly
onClick={() => {
controller.close();
}}
/>
</div>
</div>
<div className="flex-1">
<AskMessages session={state.session} />
</div>
<div className="flex flex-row">
<AskInput />
</div>
{/* <div>
<h1>{state.session.title}</h1>
</div> */}
</div>
);
}
@@ -0,0 +1,28 @@
import { tcls } from '@/lib/tailwind';
import React from 'react';
import { useAskController } from './state';
export function AskInput() {
const controller = useAskController();
const [value, setValue] = React.useState('');
return (
<textarea
className={tcls('resize-none', 'bg-tint-base', 'rounded-lg', 'm-2', 'p-2', 'flex-1')}
value={value}
placeholder="Ask a question..."
onChange={(event) => {
setValue(event.currentTarget.value);
}}
onKeyDown={(event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
controller.postMessage({
message: value,
});
setValue('');
}
}}
/>
);
}
@@ -0,0 +1,15 @@
import type { AskSession } from './state';
export function AskMessages(props: {
session: AskSession;
}) {
const { session } = props;
return (
<div className="flex flex-col gap-2">
{session.messages.map((message, index) => {
return <div key={index}>{message.content}</div>;
})}
</div>
);
}
@@ -0,0 +1,2 @@
export * from './AskDialog';
export * from './state';
@@ -0,0 +1,31 @@
import type { AIMessage } from '@gitbook/api';
import { DocumentView } from '../../DocumentView';
/**
* Render a message from the API backend.
*/
export function AIMessageView(props: {
message: AIMessage;
}) {
const { message } = props;
return (
<div className="flex flex-col gap-2">
{message.steps.map((step, index) => {
return (
<div key={index} className="flex flex-col gap-2">
<DocumentView
document={step.content}
context={{
mode: 'default',
contentContext: undefined,
wrapBlocksInSuspense: false,
}}
style={['space-y-5']}
/>
</div>
);
})}
</div>
);
}
@@ -0,0 +1,214 @@
'use server';
import {
type AIMessage,
AIMessageRole,
type AIMessageStep,
type AIStreamResponse,
} from '@gitbook/api';
import type { GitBookBaseContext } from '@v2/lib/context';
import type { GitBookDataFetcher } from '@v2/lib/data';
import { EventIterator } from 'event-iterator';
import type { MaybePromise } from 'p-map';
import * as partialJson from 'partial-json';
import type { DeepPartial } from 'ts-essentials';
import type { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { AIMessageView } from './AIMessageView';
/**
* Get the latest value from a stream and the response id.
*/
export async function generate<T>(
promise: MaybePromise<{
stream: EventIterator<T>;
response: Promise<{ responseId: string }>;
}>
) {
const input = await promise;
let value: T | undefined;
for await (const event of input.stream) {
value = event;
}
const { responseId } = await input.response;
return {
responseId,
value,
};
}
/**
* Stream the generation of an object using the AI.
*/
export async function streamGenerateObject<T>(
context: GitBookBaseContext,
{
schema,
...input
}: Omit<Parameters<GitBookDataFetcher['streamAIResponse']>[0], 'output'> & {
schema: z.ZodSchema<T>;
}
) {
const rawStream = context.dataFetcher.streamAIResponse({
...input,
output: {
type: 'object',
schema: zodToJsonSchema(schema),
},
});
let json = '';
return parseResponse<DeepPartial<T>>(rawStream, (event) => {
if (event.type === 'response_object') {
json += event.jsonChunk;
const parsed = partialJson.parse(json, partialJson.ALL);
return parsed;
}
});
}
/**
* Stream the generation of a document.
*/
export async function streamGenerateDocument(
context: GitBookBaseContext,
input: Omit<Parameters<GitBookDataFetcher['streamAIResponse']>[0], 'output'>
) {
const rawStream = context.dataFetcher.streamAIResponse({
...input,
output: {
type: 'document',
},
});
const message: AIMessage = {
id: '',
role: AIMessageRole.Assistant,
steps: [],
};
const updateProcessingMessageStep = (
stepIndex: number,
callback: (step: AIMessageStep) => void
) => {
if (stepIndex > message.steps.length) {
throw new Error(
`Step index out of bounds ${stepIndex} (${message.steps.length} steps)`
);
}
if (message.steps[stepIndex]) {
message.steps = [...message.steps];
message.steps[stepIndex] = { ...message.steps[stepIndex] };
callback(message.steps[stepIndex]);
} else {
message.steps = [
...message.steps,
{
content: {
object: 'document',
data: {},
nodes: [],
},
},
];
callback(message.steps[stepIndex]);
}
};
return parseResponse<React.ReactNode>(rawStream, (event) => {
switch (event.type) {
/**
* The agent is processing a tool call in a new message.
*/
case 'response_tool_call': {
updateProcessingMessageStep(event.stepIndex, (step) => {
step.toolCalls ??= [];
step.toolCalls.push(event.toolCall);
});
break;
}
/**
* The agent is writing the content of a new message.
*/
case 'response_reasoning':
case 'response_document': {
updateProcessingMessageStep(event.stepIndex, (step) => {
const container = event.type === 'response_reasoning' ? 'reasoning' : 'content';
step[container] ??= {
object: 'document',
data: {},
nodes: [],
};
step[container] = {
...step[container],
nodes: [...step[container].nodes],
};
if (event.operation === 'insert') {
step[container].nodes.push(...event.blocks);
} else {
step[container].nodes.splice(
-event.blocks.length,
event.blocks.length,
...event.blocks
);
}
});
break;
}
}
return <AIMessageView message={message} />;
});
}
/**
* Parse a stream from the API to extract the responseId.
*/
function parseResponse<T>(
responseStream: EventIterator<AIStreamResponse>,
parse: (response: AIStreamResponse) => T | undefined
): {
stream: EventIterator<T>;
response: Promise<{ responseId: string }>;
} {
let resolveResponse: (value: { responseId: string }) => void;
const response = new Promise<{ responseId: string }>((resolve) => {
resolveResponse = resolve;
});
const stream = new EventIterator<T>((queue) => {
(async () => {
let foundResponse = false;
for await (const event of responseStream) {
if (event.type === 'response_finish') {
foundResponse = true;
resolveResponse({ responseId: event.responseId });
} else {
const parsed = parse(event);
if (parsed !== undefined) {
queue.push(parsed);
}
}
}
if (!foundResponse) {
throw new Error('No response found');
}
})().then(
() => {
queue.stop();
},
(error) => {
queue.fail(error);
}
);
});
return { stream, response };
}
@@ -0,0 +1,44 @@
'use server';
import { getV1BaseContext } from '@/lib/v1';
import { isV2 } from '@/lib/v2';
import { AIMessageRole, AIModel } from '@gitbook/api';
import { getSiteURLDataFromMiddleware } from '@v2/lib/middleware';
import { getServerActionBaseContext } from '@v2/lib/server-actions';
import { streamGenerateDocument } from './api';
const PROMPT = `
You are a helpful assistant that can answer questions about the content of the site.
`;
export async function* streamAsk({
query,
previousResponseId,
}: {
query: string;
previousResponseId?: string;
}) {
const baseContext = isV2() ? await getServerActionBaseContext() : await getV1BaseContext();
const siteURLData = await getSiteURLDataFromMiddleware();
const { response, stream } = await streamGenerateDocument(baseContext, {
organizationId: siteURLData.organization,
siteId: siteURLData.site,
model: AIModel.Fast,
instructions: PROMPT,
previousResponseId,
input: [
{
role: AIMessageRole.User,
content: query,
},
],
});
for await (const output of stream) {
yield { output };
}
// Wait for the responseId to be available and yield one final time
const { responseId } = await response;
yield { responseId };
}
@@ -0,0 +1,2 @@
export * from './api';
export * from './ask';
@@ -0,0 +1,189 @@
'use client';
import { AIMessageRole } from '@gitbook/api';
import * as React from 'react';
import { streamAsk } from './server-actions';
export type AskMessage = {
role: AIMessageRole;
content: React.ReactNode;
};
export type AskSession = {
/**
* The title of the ask session.
* It can be auto-generated by the AI or an initial value.
*/
title: string;
/**
* ID of the latest AI response.
*/
responseId: string | null;
/**
* Messages in the session.
*/
messages: AskMessage[];
/**
* If true, the session is in progress.
*/
loading: boolean;
};
export type AskState = {
opened: boolean;
session: AskSession;
};
export type AskController = {
/** Open the dialog */
open: () => void;
/** Close the dialog */
close: () => void;
/** Post a message to the session */
postMessage: (input: {
/** If defined, the title of the session will be updated. */
title?: string;
/** The message to post to the session. */
message: string;
}) => void;
};
const AskStateContext = React.createContext<AskState | null>(null);
const AskControllerContext = React.createContext<AskController | null>(null);
export function AskStateProvider(props: React.PropsWithChildren) {
const { children } = props;
const [state, setState] = React.useState<AskState>({
opened: false,
session: {
title: '',
responseId: null,
messages: [],
loading: false,
},
});
const stateRef = React.useRef<AskState>(state);
stateRef.current = state;
const controller = React.useMemo(() => {
return {
open: () => {
setState((previous) => {
return {
...previous,
opened: true,
};
});
},
close: () => {
setState((previous) => {
return {
...previous,
opened: false,
};
});
},
postMessage: async (input: { title?: string; message: string }) => {
try {
const stream = await streamAsk({
query: input.message,
previousResponseId: stateRef.current.session.responseId ?? undefined,
});
setState((previous) => {
return {
...previous,
session: {
...previous.session,
messages: [
...previous.session.messages,
{
role: AIMessageRole.User,
content: input.message,
},
{
role: AIMessageRole.Assistant,
content: null,
},
],
},
};
});
for await (const data of stream) {
if (!data) continue;
if (data.responseId) {
setState((previous) => {
return {
...previous,
session: {
...previous.session,
responseId: data.responseId,
},
};
});
}
if (data.output) {
setState((previous) => {
return {
...previous,
session: {
...previous.session,
messages: [
...previous.session.messages.slice(0, -1),
{
role: AIMessageRole.Assistant,
content: data.output,
},
],
},
};
});
}
}
} catch (error) {
console.error('Error in summary stream:', error);
}
},
};
}, []);
return (
<AskStateContext.Provider value={state}>
<AskControllerContext.Provider value={controller}>
{children}
</AskControllerContext.Provider>
</AskStateContext.Provider>
);
}
/**
* Get the current state of the ask session.
*/
export function useAskState(): AskState {
const context = React.useContext(AskStateContext);
if (!context) {
throw new Error('useAskState must be used within a AskStateProvider');
}
return context;
}
/**
* Get the controller to interact with the ask session.
*/
export function useAskController(): AskController {
const context = React.useContext(AskControllerContext);
if (!context) {
throw new Error('useAskController must be used within a AskStateProvider');
}
return context;
}
@@ -47,9 +47,11 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
'bg-tint-base',
'rounded',
'straight-corners:rounded-none',
'circular-corners:rounded-2xl',
'ring-1',
'ring-tint-subtle',
'shadow-1xs',
'depth-flat:shadow-none',
'p-4',
'pr-8',
'bottom-4',
@@ -72,7 +74,7 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
<button
type="button"
onClick={() => setShow(false)}
aria-label={tString(language, 'cookies_close')}
aria-label={tString(language, 'close')}
className={tcls(
'absolute',
'top-3',
@@ -83,9 +85,10 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
'justify-center',
'items-center',
'rounded-sm',
'circular-corners:rounded-full',
'hover:bg-tint-hover'
)}
title={tString(language, 'cookies_close')}
title={tString(language, 'close')}
>
<Icon icon="xmark" className={tcls('size-4')} />
</button>
@@ -113,6 +113,9 @@ export function Block<T extends DocumentBlock>(props: BlockProps<T>) {
case 'code-line':
case 'tabs-item':
throw new Error(`Blocks (${block.type}) should be directly rendered by parent`);
case 'columns':
case 'column':
return null;
default:
return nullIfNever(block);
}
@@ -177,6 +180,9 @@ export function BlockSkeleton(props: { block: DocumentBlock; style: ClassValue }
case 'code-line':
case 'tabs-item':
throw new Error(`Blocks (${block.type}) should be directly rendered by parent`);
case 'columns':
case 'column':
return null;
default:
return nullIfNever(block);
}
@@ -42,7 +42,7 @@ export function Caption(
'after:pointer-events-none',
fit ? 'w-fit' : null,
withBorder
? 'rounded straight-corners:rounded-none after:border-tint-subtle after:border after:rounded straight-corners:after:rounded-none dark:after:mix-blend-plus-lighter after:pointer-events-none'
? 'rounded circular-corners:rounded-2xl straight-corners:rounded-none after:border-tint-subtle after:border after:rounded circular-corners:after:rounded-2xl straight-corners:after:rounded-none dark:after:mix-blend-plus-lighter after:pointer-events-none'
: null,
],
style,
@@ -23,6 +23,7 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
'rounded-md',
hasHeading ? 'rounded-l' : null,
'straight-corners:rounded-none',
'circular-corners:rounded-xl',
'overflow-hidden',
hasHeading ? ['border-l-2', hintStyle.containerWithHeader] : hintStyle.container,
@@ -1,9 +1,4 @@
import type {
DocumentBlockImage,
DocumentBlockImageDimension,
DocumentBlockImages,
JSONDocument,
} from '@gitbook/api';
import type { DocumentBlockImage, DocumentBlockImages, JSONDocument, Length } from '@gitbook/api';
import { Image, type ImageResponsiveSize } from '@/components/utils';
import { resolveContentRef } from '@/lib/references';
@@ -29,7 +24,7 @@ export function Images(props: BlockProps<DocumentBlockImages>) {
align === 'center' && 'justify-center',
align === 'right' && 'justify-end',
align === 'left' && 'justify-start',
isMultipleImages && ['grid', 'grid-flow-col', 'max-w-none']
isMultipleImages && ['grid', 'grid-flow-col']
)}
>
{block.nodes.map((node: any, _i: number) => (
@@ -119,7 +114,7 @@ async function ImageBlock(props: {
* When using relative values, the converted dimension will be relative to the parent element's size.
*/
function getImageDimension<DefaultValue>(
dimension: DocumentBlockImageDimension | undefined,
dimension: Length | undefined,
defaultValue: DefaultValue
): string | DefaultValue {
if (typeof dimension === 'number') {
@@ -271,7 +271,7 @@ body {
}
.scalar-activate-button {
@apply flex gap-2 items-center;
@apply bg-primary-solid text-contrast-primary-solid hover:bg-primary-solid-hover hover:text-contrast-primary-solid-hover contrast-more:ring-1 rounded-md straight-corners:rounded-none place-self-start;
@apply bg-primary-solid text-contrast-primary-solid hover:bg-primary-solid-hover hover:text-contrast-primary-solid-hover contrast-more:ring-1 rounded-md straight-corners:rounded-none circular-corners:rounded-full circular-corners:px-3 place-self-start;
@apply ring-1 ring-tint hover:ring-tint-hover;
@apply shadow-sm shadow-tint dark:shadow-tint-1 hover:shadow-md active:shadow-none;
@apply contrast-more:ring-tint-12 contrast-more:hover:ring-2 contrast-more:hover:ring-tint-12;
@@ -481,7 +481,7 @@
.openapi-panel,
.openapi-codesample,
.openapi-response-examples {
@apply border rounded-md straight-corners:rounded-none bg-tint-subtle border-tint-subtle shadow-sm;
@apply border rounded-md straight-corners:rounded-none circular-corners:rounded-xl bg-tint-subtle border-tint-subtle shadow-sm;
}
.openapi-panel pre,
@@ -571,7 +571,7 @@ body:has(.openapi-select-popover) {
}
.openapi-select-popover {
@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 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 circular-corners:rounded-xl straight-corners:rounded-none;
@apply shadow-md shadow-tint-12/1 dark:shadow-tint-1/1;
}
@@ -680,7 +680,7 @@ body:has(.openapi-select-popover) {
/* Disclosure group */
.openapi-disclosure-group {
@apply border-tint-subtle transition-all 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;
@apply border-tint-subtle transition-all border-b border-x overflow-auto last:rounded-b-md straight-corners:last:rounded-none circular-corners:last:rounded-b-xl first:rounded-t-md straight-corners:first:rounded-none circular-corners:first:rounded-t-xl first:border-t relative;
}
.openapi-disclosure-group:has(.openapi-disclosure-group-trigger:hover) {
@@ -786,7 +786,7 @@ body:has(.openapi-select-popover) {
}
.openapi-disclosure-trigger-label {
@apply absolute right-3 px-2 h-5 justify-end shrink-0 ring-tint-subtle truncate text-tint duration-300 transition-all rounded straight-corners:rounded-none flex flex-row gap-1 items-center text-xs;
@apply absolute right-3 px-2 h-5 justify-end shrink-0 ring-tint-subtle truncate text-tint duration-300 transition-all rounded straight-corners:rounded-none circular-corners:rounded-xl flex flex-row gap-1 items-center text-xs;
}
.openapi-disclosure-trigger-label span {
@@ -848,7 +848,7 @@ body:has(.openapi-select-popover) {
}
.openapi-tooltip {
@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];
@apply flex items-center gap-1 bg-tint-base border border-tint-subtle text-tint-strong rounded-md straight-corners:rounded-none circular-corners:rounded-lg font-medium px-1.5 py-0.5 shadow-sm text-[13px];
}
.openapi-tooltip svg {
@@ -34,13 +34,13 @@ export function StepperStep(props: BlockProps<DocumentBlockStepperStep>) {
<div className="relative select-none">
<div
className={tcls(
'can-override-bg can-override-text flex size-[calc(1.75rem+1px)] items-center justify-center rounded-full bg-primary-subtle tabular-nums',
'font-medium text-primary'
'can-override-bg can-override-text flex size-[calc(1.75rem+1px)] items-center justify-center rounded-full bg-primary-solid theme-muted:bg-primary-subtle tabular-nums',
'font-medium text-contrast-primary-solid theme-muted:text-primary'
)}
>
{index + 1}
</div>
<div className="can-override-bg absolute top-9 bottom-2 left-[0.875rem] w-px bg-primary-subtle" />
<div className="can-override-bg absolute top-9 bottom-2 left-[0.875rem] w-px bg-primary-7 theme-muted:bg-primary-subtle" />
</div>
<Blocks
{...contextProps}
@@ -4,7 +4,7 @@ import {
SiteInsightsLinkPosition,
} from '@gitbook/api';
import { Link } from '@/components/primitives';
import { LinkBox, LinkOverlay } from '@/components/primitives';
import { Image } from '@/components/utils';
import { resolveContentRef } from '@/lib/references';
import { type ClassValue, tcls } from '@/lib/tailwind';
@@ -44,15 +44,15 @@ export async function RecordCard(
<div
className={tcls(
'grid-area-1-1',
'z-0',
'relative',
'grid',
'bg-tint-base',
'w-[calc(100%+2px)]',
'h-[calc(100%+2px)]',
'inset-[-1px]',
'rounded-[7px]',
'rounded',
'straight-corners:rounded-none',
'circular-corners:rounded-xl',
'overflow-hidden',
'[&_.heading>div:first-child]:hidden',
'[&_.heading>div]:text-[.8em]',
@@ -148,10 +148,11 @@ export async function RecordCard(
'grid',
'shadow-1xs',
'shadow-tint-9/1',
'rounded-md',
'depth-flat:shadow-none',
'rounded',
'straight-corners:rounded-none',
'circular-corners:rounded-xl',
'dark:shadow-transparent',
'z-0',
'before:pointer-events-none',
'before:grid-area-1-1',
@@ -167,19 +168,22 @@ export async function RecordCard(
if (target && targetRef) {
return (
<Link
href={target.href}
className={tcls(style, 'hover:before:ring-tint-12/5')}
insights={{
type: 'link_click',
link: {
target: targetRef,
position: SiteInsightsLinkPosition.Content,
},
}}
>
// We don't use `Link` directly here because we could end up in a situation where
// a link is rendered inside a link, which is not allowed in HTML.
// It causes an hydration error in React.
<LinkBox href={target.href} className={tcls(style, 'hover:before:ring-tint-12/5')}>
<LinkOverlay
href={target.href}
insights={{
type: 'link_click',
link: {
target: targetRef,
position: SiteInsightsLinkPosition.Content,
},
}}
/>
{body}
</Link>
</LinkBox>
);
}
@@ -115,7 +115,8 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
return <Tag className={tcls(['w-full', verticalAlignment])}>{''}</Tag>;
}
const horizontalAlignment = `[&_*]:${getColumnAlignment(definition)} ${getColumnAlignment(definition)}`;
const horizontalAlignment = getColumnAlignment(definition);
const childrenHorizontalAlignment = `[&_*]:${horizontalAlignment}`;
return (
<Blocks
@@ -131,6 +132,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
'leading-normal',
verticalAlignment,
horizontalAlignment,
childrenHorizontalAlignment,
]}
context={context}
blockStyle={['w-full', 'max-w-[unset]']}
@@ -38,7 +38,8 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
className={tcls(
tableWidth,
styles.rowGroup,
'straight-corners:rounded-none'
'straight-corners:rounded-none',
'circular-corners:rounded-xl'
)}
>
<div role="row" className={tcls('flex', 'w-full')}>
@@ -25,6 +25,7 @@ export function Footer(props: { context: GitBookSiteContext }) {
return (
<footer
id="site-footer"
className={tcls(
'border-tint-subtle border-t',
// If the footer only contains a mode toggle, we only show it on smaller screens
@@ -159,7 +159,7 @@ function getHeaderLinkClassName(_props: { headerPreset: CustomizationHeaderPrese
'links-accent:py-0.5', // Prevent underline from being cut off at the bottom
'theme-bold:text-header-link',
'theme-bold:hover:text-header-link'
'theme-bold:hover:!text-header-link/7'
);
}
@@ -2,6 +2,7 @@ import type { SiteSpace } from '@gitbook/api';
import { tcls } from '@/lib/tailwind';
import { joinPath } from '@/lib/paths';
import type { GitBookSiteContext } from '@v2/lib/context';
import { DropdownChevron, DropdownMenu } from './DropdownMenu';
import { SpacesDropdownMenuItem } from './SpacesDropdownMenuItem';
@@ -61,7 +62,7 @@ export function SpacesDropdown(props: {
className
)}
>
<span className={tcls('line-clamp-1', 'grow')}>{siteSpace.title}</span>
<span className={tcls('truncate', 'grow')}>{siteSpace.title}</span>
<DropdownChevron />
</div>
}
@@ -74,7 +75,7 @@ export function SpacesDropdown(props: {
title: otherSiteSpace.title,
url: otherSiteSpace.urls.published
? linker.toLinkForContent(otherSiteSpace.urls.published)
: otherSiteSpace.space.urls.app,
: getFallbackSiteSpaceURL(otherSiteSpace, context),
}}
active={otherSiteSpace.id === siteSpace.id}
/>
@@ -82,3 +83,14 @@ export function SpacesDropdown(props: {
</DropdownMenu>
);
}
/**
* When the site is not published yet, `urls.published` is not available.
* To ensure navigation works in preview, we compute a relative URL from the siteSpace path.
*/
function getFallbackSiteSpaceURL(siteSpace: SiteSpace, context: GitBookSiteContext) {
const { linker, sections } = context;
return linker.toPathInSite(
sections?.current ? joinPath(sections.current.path, siteSpace.path) : siteSpace.path
);
}
@@ -30,6 +30,7 @@ export function AsideSectionHighlight({
'rounded-md',
'straight-corners:rounded-none',
'circular-corners:rounded-2xl',
'sidebar-list-line:rounded-l-none',
'sidebar-list-pill:bg-primary',
@@ -48,6 +48,7 @@ export function PageAside(props: {
'group/aside',
'hidden',
'xl:flex',
'ask-open:hidden',
// 'page-no-toc:lg:flex',
'flex-col',
'basis-56',
@@ -82,6 +82,7 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
'rounded-md',
'straight-corners:rounded-none',
'circular-corners:rounded-2xl',
'sidebar-list-line:rounded-l-none',
'hover:bg-tint-hover',
@@ -22,6 +22,7 @@ export async function PageCover(props: {
}) {
const { as, page, cover, context } = props;
const resolved = cover.ref ? await resolveContentRef(cover.ref, context) : null;
const resolvedDark = cover.refDark ? await resolveContentRef(cover.refDark, context) : null;
return (
<div
@@ -37,7 +38,7 @@ export async function PageCover(props: {
'lg:ml-0',
!page.layout.tableOfContents &&
context.customization.header.preset !== 'none'
? 'lg:-ml-64'
? 'xl:-ml-64'
: null,
]
: ['sm:mx-auto', 'max-w-3xl', 'sm:rounded-md', 'mb-8']
@@ -58,6 +59,12 @@ export async function PageCover(props: {
height: defaultPageCover.height,
},
},
dark: resolvedDark
? {
src: resolvedDark.href,
size: resolvedDark.file?.dimensions,
}
: null,
}}
resize={
// When using the default cover, we don't want to resize as it's a SVG
@@ -107,6 +107,7 @@ function NavigationCard(
'border',
'border-tint-subtle',
'rounded',
'circular-corners:rounded-2xl',
'straight-corners:rounded-none',
'hover:border-primary',
'text-pretty',
@@ -1,5 +1,4 @@
import {
CustomizationCorners,
CustomizationHeaderPreset,
CustomizationIconsStyle,
CustomizationSidebarBackgroundStyle,
@@ -75,16 +74,15 @@ export async function CustomizationRootLayout(props: {
lang={customization.internationalization.locale}
className={tcls(
customization.header.preset === CustomizationHeaderPreset.None
? 'site-header-none'
? null
: 'scroll-pt-[76px]', // Take the sticky header in consideration for the scrolling
customization.styling.corners === CustomizationCorners.Straight
? ' straight-corners'
: '',
customization.styling.corners && `${customization.styling.corners}-corners`,
'theme' in customization.styling && `theme-${customization.styling.theme}`,
tintColor ? ' tint' : 'no-tint',
sidebarStyles.background && `sidebar-${sidebarStyles.background}`,
sidebarStyles.list && `sidebar-list-${sidebarStyles.list}`,
'links' in customization.styling && `links-${customization.styling.links}`,
'depth' in customization.styling && `depth-${customization.styling.depth}`,
fontNotoColorEmoji.variable,
ibmPlexMono.variable,
fontData.type === 'default' ? fontData.variable : 'font-custom',
@@ -158,7 +156,9 @@ export async function CustomizationRootLayout(props: {
'theme-muted:bg-tint-subtle',
'theme-gradient:bg-gradient-primary',
'theme-gradient-tint:bg-gradient-tint'
'theme-gradient-tint:bg-gradient-tint',
'ask-open:mr-[500px]'
)}
>
<IconsProvider
@@ -148,6 +148,13 @@
width: 100%;
}
}
.elevate-link {
& a[href]:not(.link-overlay) {
position: relative;
z-index: 20;
}
}
}
html {
@@ -161,6 +168,6 @@ html.dark {
color-scheme: dark light;
}
html.announcement-hidden .announcement-banner {
html.announcement-hidden #announcement-banner {
@apply hidden;
}
@@ -176,6 +176,7 @@ function AnswerFollowupQuestions(props: { followupQuestions: string[] }) {
'py-2',
'rounded',
'straight-corners:rounded-none',
'circular-corners:rounded-full',
'text-tint',
'hover:bg-tint-hover',
'focus-within:bg-tint-hover'
@@ -6,6 +6,7 @@ import { useEffect, useState } from 'react';
import { tString, useLanguage } from '@/intl/client';
import { type ClassValue, tcls } from '@/lib/tailwind';
import { useAskController } from '../Ask';
import { useTrackEvent } from '../Insights';
import { useSearch } from './useSearch';
@@ -17,14 +18,19 @@ export function SearchButton(props: { children?: React.ReactNode; style?: ClassV
const language = useLanguage();
const [, setSearchState] = useSearch();
const askController = useAskController();
const trackEvent = useTrackEvent();
const onClick = () => {
setSearchState({
ask: false,
global: false,
query: '',
});
if (1) {
askController.open();
} else {
setSearchState({
ask: false,
global: false,
query: '',
});
}
trackEvent({
type: 'search_open',
@@ -45,21 +51,25 @@ export function SearchButton(props: { children?: React.ReactNode; style?: ClassV
'w-full',
'py-2',
'px-3',
'circular-corners:px-4',
'gap-2',
'bg-tint-base',
'ring-1',
'ring-tint-12/2',
'depth-flat:ring-tint-subtle',
'shadow-sm',
'shadow-tint-12/3',
'dark:shadow-none',
'depth-flat:shadow-none',
'text-tint',
'rounded-lg',
'straight-corners:rounded-sm',
'circular-corners:rounded-full',
'contrast-more:ring-tint-12',
'contrast-more:text-tint-strong',
@@ -68,10 +78,12 @@ export function SearchButton(props: { children?: React.ReactNode; style?: ClassV
'hover:bg-tint-subtle',
'hover:shadow-md',
'hover:scale-102',
'depth-flat:hover:scale-100',
'hover:ring-tint-hover',
'hover:text-tint-strong',
'focus:shadow-md',
'focus:scale-102',
'depth-flat:focus:scale-100',
'focus:ring-tint-hover',
'focus:text-tint-strong',
@@ -9,6 +9,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { tString, useLanguage } from '@/intl/client';
import { tcls } from '@/lib/tailwind';
import { useAskController } from '../Ask';
import { LoadingPane } from '../primitives/LoadingPane';
import { SearchAskAnswer } from './SearchAskAnswer';
import { SearchAskProvider, useSearchAskState } from './SearchAskContext';
@@ -146,6 +147,7 @@ function SearchModalBody(
const language = useLanguage();
const resultsRef = React.useRef<SearchResultsRef>(null);
const inputRef = React.useRef<HTMLInputElement>(null);
const askController = useAskController();
React.useEffect(() => {
inputRef.current?.focus();
@@ -186,7 +188,9 @@ function SearchModalBody(
};
const onSwitchToAsk = () => {
setSearchState((state) => (state ? { ...state, ask: true } : null));
alert('switch to ask');
askController.open();
// setSearchState((state) => (state ? { ...state, ask: true } : null));
};
// We trim the query to avoid invalidating the search when the user is typing between words.
@@ -225,9 +229,11 @@ function SearchModalBody(
'w-full',
'rounded-lg',
'straight-corners:rounded-sm',
'circular-corners:rounded-2xl',
'ring-1',
'ring-tint-hover',
'ring-tint',
'shadow-2xl',
'depth-flat:shadow-none',
'overflow-hidden',
'dark:ring-inset',
'dark:ring-tint'
@@ -108,6 +108,7 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt
'p-2',
'rounded',
'straight-corners:rounded-none',
'circular-corners:rounded-full',
active ? ['bg-primary-solid', 'text-contrast-primary-solid'] : ['opacity-6']
)}
>
@@ -72,6 +72,7 @@ export const SearchQuestionResultItem = React.forwardRef(function SearchQuestion
'rounded',
'self-center',
'straight-corners:rounded-none',
'circular-corners:rounded-full',
active ? ['bg-primary-solid', 'text-contrast-primary-solid'] : ['opacity-6']
)}
>
@@ -177,7 +177,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
block: 'nearest',
inline: 'nearest',
});
}, [cursor, refs]);
}, [cursor]);
const moveBy = React.useCallback(
(delta: number) => {
@@ -198,7 +198,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
}
refs.current[cursor]?.click();
}, [cursor, refs]);
}, [cursor]);
React.useImperativeHandle(
ref,

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