Compare commits

...

19 Commits

Author SHA1 Message Date
Peter White 91ec127e01 Search the API directly from the browser on published sites
The browser posts to the search API with the content token of the page and
builds results with the code the search route uses. The route stays as the
fallback for previews, expiring tokens and failed direct calls.

Drop the cache directive on the search fetcher: its key holds the token and
the typed prefix, so entries are written and not read back.

searchSpikeOverrides.ts is a measurement switch for the preview deploy and
must be removed before merge.

Part of RND-13965
2026-11-12 12:58:14 +00:00
Tomek 7d1cd9f9df Remove ChatGPT-specific plain text Markdown responses (#4766) 2026-11-12 10:38:07 +00:00
Samy Pessé 6029a31c07 Version Packages (#4755)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-11-12 10:34:04 +00:00
Nolann B. 4fb7e14436 Push the release PR with the PAT so its workflows run (#4738) 2026-11-12 10:18:08 +00:00
Steven H 31e88d16eb Relicense @gitbook/embed under Apache 2.0 (#4765)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 09:33:54 +00:00
Peter White 7ddd023d72 Fix search prewarming on public sites (#4762) 2026-11-12 10:24:29 +01:00
Peter White b53e73c0e7 Link suggested follow-up questions to the markdown ask route (#4757) 2026-11-12 08:57:20 +01:00
Taran Vohra 4a405475f3 Fix page outline not following the active section on long pages (#4754) 2026-11-11 19:29:25 +05:30
Samy Pessé aade3189ca Version Packages (#4711) 2026-11-10 18:25:28 +01:00
Brett Jephson 8689736e3a RND-13702: remove italic from annotation dfn (#4750) 2026-11-10 15:53:57 +00:00
Nolann B. 1b29a7051e Add a fetch option to parseOpenAPI (#4753) 2026-11-10 16:34:59 +01:00
Peter White 6c128729b2 Expand 3-digit hex colours correctly (#4748)
Co-authored-by: monem <119044801+pucedoteth@users.noreply.github.com>
2026-11-09 11:29:12 +00:00
Nolann B. 6344646974 Fix bash highlighting of the last letter before > (RND-13792) (#4747) 2026-11-09 10:47:47 +01:00
Nolann B. 192863526f Remove docs.cartographer3d.com from customer visual tests (#4749) 2026-11-09 10:28:06 +01:00
Greg Bergé 09a8310c9d Serve robots.txt without sign-in on authenticated access sites (RND-13789) (#4746)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:52:58 +01:00
Peter White a023a7131b Warn visitors not to share sensitive personal info in the Assistant (#4745) 2026-11-05 17:48:53 +00:00
conico974 198cafe978 Bump Next.js to 16.4.5 and OpenNext to 1.20.16 (#4743)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 11:22:58 +01:00
conico974 268fa83683 Fix formatting in README.md (#4741) 2026-11-03 19:29:19 +01:00
conico974 123454d2ac Dummy PR (#4739)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:58:05 +01:00
127 changed files with 1237 additions and 685 deletions
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Bump Next.js to 16.4.3, keeping the dev-mode OOM patch.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Preconnect each asset origin once, skip same-origin icon URLs, and open the fonts preconnect in CORS mode so font requests can reuse it.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Stop caching failed searches, so a rate-limited or failing search is retried on the next query instead of being served from cache for 15 minutes.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Declare the Tailwind layer order in each component stylesheet, so a route whose first stylesheet is one of them still puts `base` before `components` and preflight does not strip their padding and borders.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Forward the visitor's user agent on ask and Assistant calls so questions are classified by who asked.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Load the inline expression runtime only on pages whose code blocks or text use expressions, so other pages ship less JavaScript.
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/react-contentkit": patch
---
Load a webframe only once it is near the viewport. A page can hold hundreds of them inside collapsed expandables, and loading every one on mount held up the first paint.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Pack the class lists that every block, heading anchor and table cell repeats into shared CSS classes, and carry a table cell's width once as a custom property instead of spelling it out as both width and min-width, so a large page carries far less markup.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Split llms-full.txt into parts by size so large sites no longer fail with "content too large"
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Follow site and space redirects when the MCP getPage tool resolves a page URL.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Fix the MCP `getPage` tool returning "Page not found" for the Markdown URL of a page (`<page>.md`).
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Add a search input to filter models by name on OpenAPI models pages.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Let analytics tools like GA4 track outbound link clicks by using native link navigation.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Set `Partitioned` on visitor cookies set inside an iframe so authenticated embeds work cross-site.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Fix encoded or non-normalized links on proxied sites redirecting to the internal proxy path instead of the public URL.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Support a secondary brand colour for secondary buttons and `$secondary` text, background, icon and tag colours. Sites without one render as before.
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Remove the ChatGPT-specific plain text response for Markdown and llms.txt routes; these now always serve `text/markdown`.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Resolve the published content with the full URL in a single lookup and always send the visitor country.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Resolve dark-mode image URLs in published Markdown.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Keep lists, paragraphs and code blocks as HTML in Updates RSS feeds, instead of running all the text together
-5
View File
@@ -1,5 +0,0 @@
---
"@gitbook/openapi-parser": patch
---
Bump @scalar/openapi-parser to 0.29 so valid OpenAPI 3.2 documents no longer report false validation errors.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Only apply a site preview customization override to the section or variant it was made for.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Render soft line breaks in paragraphs that contain buttons.
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Fix visitors being logged out right after signing in on sites with a logout link in the header.
-5
View File
@@ -1,5 +0,0 @@
---
"gitbook": patch
---
Truncate long tab titles one by one and show the selected tab's full title, instead of hiding tabs behind a "…" menu
+2
View File
@@ -87,3 +87,5 @@ The site must be [published publicly](https://gitbook.com/docs/published-documen
## License
The renderer is distributed under the [GNU GPLv3 License](https://github.com/GitbookIO/gitbook/blob/main/LICENSE). If you distribute the code, you must keep the source code public. To use it in a private repository, you need a [commercial license](https://www.gitbook.com/pricing).
The [`@gitbook/embed`](https://github.com/GitbookIO/gitbook/blob/main/packages/embed) package is distributed under the [Apache License 2.0](https://github.com/GitbookIO/gitbook/blob/main/packages/embed/LICENSE). Contributions to it are made under the same license.
+2
View File
@@ -25,6 +25,8 @@ jobs:
with:
# This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits
fetch-depth: 0
# Otherwise git pushes with GITHUB_TOKEN instead of the PAT, and the release PR never triggers Preview
persist-credentials: false
- name: Setup Node
uses: actions/setup-node@v4
with:
+3 -1
View File
@@ -124,6 +124,8 @@ If you plan to distribute the code, you must make the source code public to comp
See `LICENSE` for more information.
The [`@gitbook/embed`](packages/embed) package is an exception: it is distributed under the [Apache License 2.0](packages/embed/LICENSE), so it can be used in proprietary applications.
## Badges
<p align="left">
@@ -161,4 +163,4 @@ GitBook wouldn't be possible without these projects:
## Legacy GitBook (Deprecated)
Our previous version of GitBook and it's CLI tool are now deprecated. You can still view the old repository and it's commits on this [branch](https://github.com/GitbookIO/gitbook/tree/legacy).
Our previous version of GitBook and it's CLI tool are now deprecated. You can still view the old repository and it's commits on this [branch](https://github.com/GitbookIO/gitbook/tree/legacy).
+51 -51
View File
@@ -7,7 +7,7 @@
"devDependencies": {
"@changesets/cli": "^2.31.1",
"oxfmt": "^0.62.0",
"oxlint": "^1.90.0",
"oxlint": "^1.93.0",
"turbo": "^2.11.10",
"vercel": "50.37.3",
},
@@ -40,7 +40,7 @@
},
"packages/colors": {
"name": "@gitbook/colors",
"version": "0.4.4",
"version": "0.4.5",
"devDependencies": {
"bun-types": "catalog:",
"tsdown": "catalog:",
@@ -49,10 +49,9 @@
},
"packages/embed": {
"name": "@gitbook/embed",
"version": "0.6.0",
"version": "0.7.0",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:",
"bidc": "catalog:",
},
"devDependencies": {
@@ -110,7 +109,7 @@
},
"packages/gitbook": {
"name": "gitbook",
"version": "0.28.0",
"version": "0.28.2",
"dependencies": {
"@base-ui/react": "catalog:",
"@cloudflare/workers-types": "^4.20251011.0",
@@ -129,8 +128,8 @@
"@gitbook/react-openapi": "workspace:*",
"@mermaid-js/mermaid-zenuml": "^0.2.2",
"@modelcontextprotocol/sdk": "1.17.5",
"@opennextjs/aws": "4.1.11",
"@opennextjs/cloudflare": "1.20.12",
"@opennextjs/aws": "4.1.15",
"@opennextjs/cloudflare": "1.20.16",
"@panzoom/panzoom": "^4.6.1",
"@sindresorhus/fnv1a": "^3.1.0",
"@tailwindcss/container-queries": "^0.1.1",
@@ -160,7 +159,7 @@
"micromark-extension-gfm": "^3.0.0",
"motion": "^12.23.24",
"negotiator": "^1.0.0",
"next": "^16.4.3",
"next": "^16.4.5",
"next-themes": "^0.4.6",
"nuqs": "^2.2.3",
"object-hash": "^3.0.0",
@@ -249,7 +248,7 @@
},
"packages/openapi-parser": {
"name": "@gitbook/openapi-parser",
"version": "3.0.13",
"version": "3.1.0",
"dependencies": {
"@scalar/json-magic": "^0.17.0",
"@scalar/openapi-parser": "^0.29.19",
@@ -266,7 +265,7 @@
},
"packages/react-contentkit": {
"name": "@gitbook/react-contentkit",
"version": "0.7.17",
"version": "0.7.18",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:*",
@@ -309,7 +308,7 @@
},
"packages/react-openapi": {
"name": "@gitbook/react-openapi",
"version": "2.0.0",
"version": "2.0.1",
"dependencies": {
"@base-ui/react": "catalog:",
"@gitbook/expr": "workspace:*",
@@ -342,8 +341,9 @@
},
"patchedDependencies": {
"decode-named-character-reference@1.0.2": "patches/decode-named-character-reference@1.0.2.patch",
"next@16.4.5": "patches/next@16.4.5.patch",
"@shikijs/langs@3.21.0": "patches/@shikijs%2Flangs@3.21.0.patch",
"mermaid@11.16.1": "patches/mermaid@11.16.1.patch",
"next@16.4.3": "patches/next@16.4.3.patch",
},
"overrides": {
"@codemirror/state": "6.4.1",
@@ -888,23 +888,23 @@
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.6", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-DXj75ewm11LIWUk198QSKUTxjyRjsBwk09MuMk5DGK+GDUtyPhhEHOGP/Xwwj3DjQXXkivoBirmOnKrLfc0+9g=="],
"@next/env": ["@next/env@16.4.3", "", {}, "sha512-a7tz1QCcWWkwC/rPJcPMJk/UNRu9CFhDd3OHYE5hddbJrs8BqRTalxI6GlKCWEGzRLHN+S/B7a4PAHCtm25aBQ=="],
"@next/env": ["@next/env@16.4.5", "", {}, "sha512-JwSPwpdkZL5VMl2jA/q+JI8MZdweAL3xmw5/tb9y/TLtmS0YXZnmYDzSihwk8xTj1OL+WM53gTqaj96yuLp6Xw=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.4.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4yq5EXBt+VDtAZwTyfijaFp5D2C5oz3Ikw5a2kiX2K9nw72vrZX2q5UuAlkPtUMO1tQz6JTKpaKWjS7OOefTDQ=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.4.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W5TniBzWKASbNNwHZ/S/f1MxQUXU/LdjklVMWA0Ujbfl7SZnUzFls45hO0i/sF7Bx1jJBWp7WWmoQajXtX/O8A=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.4.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-1Rvkn/LUhnEF8WxnRkOrqGhbwXui1bkWFxONKIbN+r7C+9KHDSZ+iUoj4CzymVMWUyZIyx9szcb9+kbKhD500A=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.4.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-uWq4qkHapBDS1oFltWS4YxTb9pLHAtLNcpbuK6SmRsebLYZqpBML/j/C0op+50JYGTpZgVL/DWNt/6oWlamrrw=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.4.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-dCKpXErGg3abUM6t5JTJV/ottVBTStJB5lRJTbgdUdQaQ+jNHiK56Hsc0YAEg/rinSh42hYG3HWiYmEMdilnxg=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-FdRcyOx5u5P51mlAqLB9EXdd8jZO5fsdHtk4BAQSkqOywCsbTNN/rmw+jO7pnmjNdAtu2AG46NzbCL2FPDrrgw=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.4.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-WwYbhWkhYBvBtib1K4W0kL8MW2snBD2A5b3SN4vPpqalZbZs5+bQ1yzAdepVoOqrFfEW3dmREy4Zs1MrWcrFbw=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.4.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-cyvmmgtRXR8aGVT86wi9Py53IlfyCx7P2fZxuAXAoTummDnhKtUyjrMeXfapqp4qgD+gT5RqyGD/VfA16jEK7Q=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.4.3", "", { "os": "linux", "cpu": "x64" }, "sha512-QymWvfUwDEC5/lpJOsxX5VAbyoR2D13+2Jsjz4be7gT4LPqEgdpkeLIR/HFfCLgKzoMhpJbp6I/JnogyNZHcaA=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-hPc+Fl+N22DAvqZHq559NmamXboOX5Lu8I5Wl0DezfrhTk1Zz21NQeYoZDmNI/NjTcrdsVxM72Fzn2lI+Q+vAw=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.4.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Vmjd9D7DpA4Vv3cUlr82QWgJD/BQZmns801vtC5VWHg6jCIA4a5vMKpQnfrM2pyCrp2eV0ENdUg4PlN3bAc2Yw=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.4.5", "", { "os": "linux", "cpu": "x64" }, "sha512-gbACYE8IP/jlprNTpiWXHb8N9CofOzpnEwH2SrIz1dUbiJf1FbDssDupCAPeP1Taxe8mgG20sGZamJI7KFOdZw=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.4.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-lG1pPdPLk7gTyGb00n5JIMLdBJgjb+UEhSdZvFbhUMg0DyC6z4B7RnpSKF9VCd4imkLObVbXOFA0kFCpw7yFow=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.4.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-+8gj++ZMo8H20Xh2ijjNqyFk5LPLw0AC4Fs4Tq/G94rjjQXhOjN4jgSWsWKPFsyY26hfIeemJIwKtMxOsp5GBA=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.4.3", "", { "os": "win32", "cpu": "x64" }, "sha512-NmQC9RkJdnPXRaMxtkCzobB3teVjqF33oSOnwDfi+XYGWWYpt+xlGK3mtktSKLbdjwaWbJu2OkkMiRFGUUncKg=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.4.5", "", { "os": "win32", "cpu": "x64" }, "sha512-qNZj66pYXnaWLiF9R9GUbfBdrCv7kC6TaZfIwB2KH5WPc/8pEM3nVOj8hpQhWJU8S94rlH5MPDcX15+RrgUhiw=="],
"@node-minify/core": ["@node-minify/core@9.0.2", "", { "dependencies": { "@node-minify/utils": "9.0.1", "glob": "10.3.3", "mkdirp": "3.0.1" } }, "sha512-FNhv29Wom6wKrrFKaeAfmZqz7TX5A1E6P+bpd0VIc+DYWMLUIhAViS8riaZg3A1oD0s06s+5BG2Fg7RqMKiKHw=="],
@@ -934,9 +934,9 @@
"@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
"@opennextjs/aws": ["@opennextjs/aws@4.1.11", "", { "dependencies": { "@ast-grep/napi": "^0.45.3", "@aws-sdk/client-cloudfront": "3.984.0", "@aws-sdk/client-dynamodb": "3.984.0", "@aws-sdk/client-lambda": "3.984.0", "@aws-sdk/client-s3": "3.984.0", "@aws-sdk/client-sqs": "3.984.0", "@node-minify/core": "^9.0.2", "@node-minify/terser": "^9.0.1", "@tsconfig/node18": "^18.2.7", "aws4fetch": "^1.0.20", "chalk": "^5.6.2", "cookie": "^1.1.1", "esbuild": "0.28.2", "express": "^5.2.1", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.1.0", "yaml": "^2.9.1" }, "peerDependencies": { "next": ">=15.5.28 <16 || >=16.4.1" }, "bin": { "open-next": "dist/index.js" } }, "sha512-cew0cUgGZF/Kr/C+Z1qvmAdKj4Va5cRho+aB6Ci/5ILd+QDdrR2LVaC/6Qm12KVF37vSLDK7lPDd/KdvvCoNEw=="],
"@opennextjs/aws": ["@opennextjs/aws@4.1.15", "", { "dependencies": { "@ast-grep/napi": "^0.45.3", "@aws-sdk/client-cloudfront": "3.984.0", "@aws-sdk/client-dynamodb": "3.984.0", "@aws-sdk/client-lambda": "3.984.0", "@aws-sdk/client-s3": "3.984.0", "@aws-sdk/client-sqs": "3.984.0", "@node-minify/core": "^9.0.2", "@node-minify/terser": "^9.0.1", "@tsconfig/node18": "^18.2.7", "aws4fetch": "^1.0.20", "chalk": "^5.6.2", "cookie": "^1.1.1", "esbuild": "0.28.2", "express": "^5.2.1", "path-to-regexp": "^6.3.0", "urlpattern-polyfill": "^10.1.0", "yaml": "^2.9.1" }, "peerDependencies": { "diff": "^9.0.0", "next": ">=15.5.29 <16 || >=16.4.5" }, "optionalPeers": ["diff"], "bin": { "open-next": "dist/index.js" } }, "sha512-ulv7rFEAeieD0d/zDMnw4IFPU+zY/atVUhn7ezgFqIurLB5oBYeHCUGlcGk3Pg7uMPeI03SNRjHW1+xL9EwMPw=="],
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.20.12", "", { "dependencies": { "@ast-grep/napi": "^0.45.3", "@dotenvx/dotenvx": "^2.33.0", "@opennextjs/aws": "4.1.11", "ci-info": "^4.2.0", "cloudflare": "^4.4.1", "comment-json": "^4.5.1", "enquirer": "^2.4.1", "glob": "^13.0.6", "ts-tqdm": "^0.8.6", "yargs": "^18.2.0" }, "peerDependencies": { "next": ">=15.5.28 <16 || >=16.4.1", "rclone.js": "^0.6.6", "wrangler": "^4.148.0" }, "optionalPeers": ["rclone.js"], "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-ZU1j7hMuOFaa0RHT/wrPXQKEEqzkkoc3fHaHy0Xrx4K0zZdSXGQbwbSHCnbjPRGXblCuWo+ur6UUkl0o9kVhrg=="],
"@opennextjs/cloudflare": ["@opennextjs/cloudflare@1.20.16", "", { "dependencies": { "@ast-grep/napi": "^0.45.3", "@dotenvx/dotenvx": "^2.33.0", "@opennextjs/aws": "4.1.15", "ci-info": "^4.2.0", "cloudflare": "^4.4.1", "comment-json": "^4.5.1", "enquirer": "^2.4.1", "glob": "^13.0.6", "ts-tqdm": "^0.8.6", "yargs": "^18.2.0" }, "peerDependencies": { "next": ">=15.5.29 <16 || >=16.4.5", "rclone.js": "^0.6.6", "wrangler": "^4.148.0" }, "optionalPeers": ["rclone.js"], "bin": { "opennextjs-cloudflare": "dist/cli/index.js" } }, "sha512-3c1Cwex5e2jXy4o4RZAusy8o9Uz4eowuPCjdVLYEZ/MnkAzN2jh9dOgsRjgu3wjigXuNe8rmPECmOTo+xKQHsw=="],
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
@@ -1020,43 +1020,43 @@
"@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.62.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dlI5SY7XYQCiCBafntWagCR6HcAJB/NpsLtdlPx8x08+Osz8Ok1HHz1GZuusegCe/VoJ6pAnF5a4pd5OZAq7qQ=="],
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.90.0", "", { "os": "android", "cpu": "arm" }, "sha512-sgfalb1HuxYjiAa4wESZ626z1FGusqNcXYHyB5oIOEprS2sbPg0UYEfo83uF1GNG70DIfZxHkXuE6KFrlSdn8Q=="],
"@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.93.0", "", { "os": "android", "cpu": "arm" }, "sha512-KKw70pbD5Pvq3G2lJ+WQ71XPMjCmg37rkSe2+mDqCRP3PoTSdDMuEi4UOnE8JBtyNM7bek61EC2s0Jt7S7u+Sg=="],
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.90.0", "", { "os": "android", "cpu": "arm64" }, "sha512-RcKhz1ETMq+fazE9TKS2+5NIRGwigm/nW8egqF0LI+A8Yw2iW5r0E/C3XAPRiwIm/vcPiutIRIa6IQCIKhZsTQ=="],
"@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.93.0", "", { "os": "android", "cpu": "arm64" }, "sha512-fvr1fTUZGelwRxKDt64pf28aL1WaWgviwz3X+E/FcRYvrlEahH0NECRz/c1SY+K+KWmZk5uAf6h1kQnyBkp/2A=="],
"@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.90.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PSrxMvP6mx+p1karZcUxLdy9lpaP+JWQPti4al9FgTaPhxE+pT/BjhGExloVVSWgZgIfOlLOjVyyq8BXlodhLQ=="],
"@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.93.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VdvSN9JRf4Es6ZzoAdvEeh8tgKdw8L4H82WmA8MMPVfPdZGCwE/nI27Z1HrRHYQhPxOh4kmnxQEioSUFrOvDaw=="],
"@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.90.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-OLbU9qsrV2Q8QxT2OEzOPdO8zCCpyI8qB+hDI8yr6RsYELtkf0FAZhJdfjM/RnLHKzt44HWwAPnk92vBtKofvw=="],
"@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.93.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-QR1V890Ml0YGRG0F/iV0K9igG+pOhe+VWd+aZnxY849rRHZf3F2gEoQks5+GVHf105X9Mh/6HdShTP5juekG2g=="],
"@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.90.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fBA4pWyVDARmNucQqpyFuPC49xYf1r+sX7dxPL5fNPQrSMLum39Sn56tZ9JPleWC7FzLPwSQ7plYYfirYBHF8A=="],
"@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.93.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jDfIbi30zZaKi+rTvig3sge+Xhi8pFFSgHxb0jzovc4qh8ETm/irKV9c7N/JsuwnYc5sbvf4IC8w1vQQ0IL+BA=="],
"@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.90.0", "", { "os": "linux", "cpu": "arm" }, "sha512-zhxH78eP2nR6/pGdhBc7MIfjhhcgATeu8fkmkFJbuzfk6Cj0FzBxyEhL85yDqrCybjx+G2YDzZGsxIO9kvO4Iw=="],
"@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.93.0", "", { "os": "linux", "cpu": "arm" }, "sha512-qtxZ714ND/koFgVH00QgxlzZiIbRPmguaPhnHRU5DFFtrtEdOrSnZD4U/H2AzXrXkZ59eQbMBnJwbum5XohrIQ=="],
"@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.90.0", "", { "os": "linux", "cpu": "arm" }, "sha512-MvYmpHyJqUJjEvkZbJHdwfdL45cGn8EVBkYmoXFiaYFW9suoD3dbJl5mBdpy0DVgdjlyddmldbRtk3cCgETadQ=="],
"@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.93.0", "", { "os": "linux", "cpu": "arm" }, "sha512-5pJ9NjMvH7WMUqXB6bTTOSemBm39w2cod27Hu3+Yom/GhbTeNXlYcpDx1o4CgBP6fhp0YHG66TNdVaUm6sHjsA=="],
"@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.90.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-L13Sy+Kg0nZhwTsToniqjUFPA2TuM6962qCZ5Wp5KeB517Tw7ApdLTTtlb+bF86fcZZa2EtvCVvAJxk4Dya/MQ=="],
"@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.93.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-pRf+7jtbt2fHE23SX7qBxRgnBZicUgsoCK1PVIjnOk+t3a6UBkpt/GrKI99WlxsKUxftz/5nv/kfKvScmYywkw=="],
"@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.90.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-wFDdZWGpzIPFdJFjSLWnKB0TGzFriTKUIrl04iMqMyM9BBIJTuerfCHYa0WpDSjr5x3lcW/CHLyacSt886WpGg=="],
"@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.93.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-zM8Fd/K8sqGvsBcJBZ3oVZ3ctGWbszA08IaGt5gFFF5RcunppAI/ZnAXbouABvnWeJ//S44KKlfPZnDDbeiphg=="],
"@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.90.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FRywfn+MoKn8xpBSzgNwNVliSKRI2QXkxi/iF7o6FEQs8Bh8z2RpVTaFAcO3pchXjvB2eGNFIoLLQABIQ+u7VA=="],
"@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.93.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-7qKCnvhZKDXJG2pdbLazd2hw4FxjJHbZOFrrbzyy5PdHfPLH9vnNUa1le01A8uItZuYKPBPncH+HCoEiFT4SgA=="],
"@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.90.0", "", { "os": "linux", "cpu": "none" }, "sha512-IS2PaUgpc/qeLADTyhUNmdx3x4ijs9pjJdPKBYHaT+mAE7k3FsjnHPTjhpsak1b++h6CIPhv6WX9Z+y7Pw0voQ=="],
"@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.93.0", "", { "os": "linux", "cpu": "none" }, "sha512-2pgulxC6G8V5wE750NJE/fRkf9aO6i6yoj10wyaR2EdiNtan4OemsOtsQ0ZTXGdY8Q3IoTmlND3GYvjJsvikYg=="],
"@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.90.0", "", { "os": "linux", "cpu": "none" }, "sha512-s7JFKRkGS8O7i2ZVODfuO++NC7sH6HDqpMn8lYxw91w0SXJOxnAVjQ8utvmDwLMMT0jaSkuOKuUYJDGDtu4sHQ=="],
"@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.93.0", "", { "os": "linux", "cpu": "none" }, "sha512-yWsn6Gg2PnGG5jlyDz5+pHGiMTMiy4adaa4WRMzDgzB8efaShk1JMmTHIBSVLQMh0tYXRs4CN5uJCO79r4Mi4g=="],
"@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.90.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-b0GxrOQr5VONCkd7KLSqmP6b6nqZuTpuS8QPQHzltmqdXP5bY0Jl0CZ0QmlCTrS3ExaLYbioFYOARzNbYvcApQ=="],
"@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.93.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-WCn5plwp/ezS2RDPQKp0/EXEVRr9/dDexUdRKmsMFqzNM5xBSZ1wbsi3PuMk1VfkMRnI5BuazCsziCWMXRDOlw=="],
"@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.90.0", "", { "os": "linux", "cpu": "x64" }, "sha512-iaJeShJtgUp8rZbt9YUdTAGOpmX5KogCLba17Jp1tWdJ3F4M3D2g2TKUyI1FMTHxKH9NNHuQcuC2kZRg6IV7yQ=="],
"@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.93.0", "", { "os": "linux", "cpu": "x64" }, "sha512-F2CGjb9LAkzH+IYTZSzd1klQ1vk5z6Utjax64WeLByCkYXbVDbsun+Oy2hLfW2MDSKCvW+jwy/9ld0EPgdeu+A=="],
"@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.90.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/aZoXnL5WBLCXuzfHqvEFXcm4iwYmr/cYByHDTXzLFzJdE5DC7bm6phpWqHv6BeqpVgY9IpBAxGDv2WEkl5JCg=="],
"@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.93.0", "", { "os": "linux", "cpu": "x64" }, "sha512-plf5DE2objcG8Tf7mXRuRa8tT8rzDkpjqXQtKoGsKHWkpgsialbON1+cCtoSEUTVbQUjXvuAm8vV9+3rR0yCBw=="],
"@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.90.0", "", { "os": "none", "cpu": "arm64" }, "sha512-SMhk7ozwoxYDKfYqGZCQEfULOKDneF9uhaoIf2neoDLWvNtjY9yVeL7YHmE5L9WdKvMpWNwwZxhenxBu6AgfOg=="],
"@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.93.0", "", { "os": "none", "cpu": "arm64" }, "sha512-Kp2yJrKvoTe9FfKdO/xwLNUbs2bYTMKx0+aoQD1s0h/Tx0kSgtHSdGmrk8tTKdluosjFbo88qYRIaNIX43AfFw=="],
"@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.90.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-KBvR3A9fO7fCi2vCP8ifWvlia7tGEQV9NaYh+IGwa/7Groy48Mt/UaYqMK9/zOtAWl20BUCf6RPqzJ32fRw33Q=="],
"@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.93.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-vigjcHCFsR6KjnWTEVfaCxcMYapEuaK9FZa1MpewwQ1S+Xud3v0okfkj1RySsD6RF1NRHIjSlQSalTnKIaj3nA=="],
"@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.90.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-+x42rDHQGbTCuT3EzpWAzhP3NoVPE/cJsG4gnrakOSg0olV+GCdMpPU23Nvrl3zZMnXxFZdTtDv4D3SuaA5ZeQ=="],
"@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.93.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-0Ot3BEe/AAr2/uQhe55r8CmPlperYk2mQmxdutOru7oU0kir6IiEVHb1TG3GORRlfra97lPHVDLhgmihiKzLrg=="],
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.90.0", "", { "os": "win32", "cpu": "x64" }, "sha512-30JiaFEBUB9peEOcvVM8ggo88SoN/IdSr9DW505JdBji/R6dM66gKEiG65hw6qcaF2f4mO0cEJ+RwRZprmun9g=="],
"@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.93.0", "", { "os": "win32", "cpu": "x64" }, "sha512-ZzzIIVs/Qkm8trUWYKF0OYub5ALlv50/3gsEZbKAvWgz+yNJX6uRmQ8ySPXU2Q0/Y9m4JCOjhpmC8XUYbPHX5Q=="],
"@panzoom/panzoom": ["@panzoom/panzoom@4.6.1", "", {}, "sha512-ogf/KhHHjj+DYAvHfaf3TXMQ8OE36pJtKpabLlx1OmpjcgtpCvkUoCiNONA8kuVRPGJdLiqMf0n8LRFXj1OyuA=="],
@@ -1362,17 +1362,17 @@
"@tsconfig/strictest": ["@tsconfig/strictest@2.0.6", "", {}, "sha512-tPOhmDhIUcDjvpDDYyiUdssP84Eqm7n5KxJe5J3/g+s6xoDIPAf+SIn06dhw7VkhxIvLOnhDDrX7tsqMHNEhDg=="],
"@turbo/darwin-64": ["@turbo/darwin-64@2.11.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-By4idRap5+nLnMuRzLB4qkOPRlvNdyY1ne4VJYDyUoUB11H94k7uif9mbe3JT8JSNXs1D68uMcBj6hJ/jMQRvA=="],
"@turbo/darwin-64": ["@turbo/darwin-64@2.11.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-HMMeUIEAsmyIbSYn1ySvF/4gGMa0joiTMq4dQDuloIAnOpbtFT3JEiVsYnASdTWDXvplbRbZlqjU1EBeSR9G1A=="],
"@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.11.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-pyEYlVcuexOPa92p0Wlwl9YT1Is5D60lxN3ItWm3g30YLvFaUq86TF4BBWg5ZAc0uMTnrOzCvuCsOBTdzUWnRQ=="],
"@turbo/darwin-arm64": ["@turbo/darwin-arm64@2.11.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+8iySNgqYWY6PZYUQzYu6zn0C04z2OEq1vQ6oMGsJ9sxN6wOpg1gzi1AJ5VKDHviWvMUjhqng51hTEQZOuiqYQ=="],
"@turbo/linux-64": ["@turbo/linux-64@2.11.10", "", { "os": [ "linux", "android", ], "cpu": "x64" }, "sha512-1tYeR/IDZ5HEdsb0jNq2L2xam5nMQ+g80tlV2BZmv4OFw+pMcUM0mMmUcfc5wXPPNHVNPuDMjh9Y8G8HgXPLZA=="],
"@turbo/linux-64": ["@turbo/linux-64@2.11.11", "", { "os": [ "linux", "android", ], "cpu": "x64" }, "sha512-uImTKFYCFKsoFxjaXeJXlMPv4uU3OIIDjyzoIwYVqOtu9LnCZ/5/fzu/WP6+G9YfwGAPRplTHkKFfcI5t97RGw=="],
"@turbo/linux-arm64": ["@turbo/linux-arm64@2.11.10", "", { "os": [ "linux", "android", ], "cpu": "arm64" }, "sha512-HuekQxvVGhUd46g1Im/DKdOtglHr4/gguZAvzEpPUgtxNkztSa8lWhQEGQ+EmPQ0n7w19qjThFU+OfEEd0uvlw=="],
"@turbo/linux-arm64": ["@turbo/linux-arm64@2.11.11", "", { "os": [ "linux", "android", ], "cpu": "arm64" }, "sha512-Y7xffSVfyyYRGzNy6PLdrFL4xXN6iTuR3W0SyJvH/yd3d4defA6dansZwJpPDVnIpD8a2Qn256DwY/Qin4Ip4g=="],
"@turbo/windows-64": ["@turbo/windows-64@2.11.10", "", { "os": "win32", "cpu": "x64" }, "sha512-UJOP3BkbreY3/JlMgJR6ZiimxYIBn+c+B4tnBIxD0E9c3Estmk+XoVNVBNBottZLNRXe3/ScgOU4+/k8tIWysg=="],
"@turbo/windows-64": ["@turbo/windows-64@2.11.11", "", { "os": "win32", "cpu": "x64" }, "sha512-e2WqNtkPGo3QDwNNwwBo89Dx3PtSJ9Wp9QuRNzn80rrRnSHB+1mfBMZTpdHjA8/G/sG9rl76TdWdpgVsimjRdg=="],
"@turbo/windows-arm64": ["@turbo/windows-arm64@2.11.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-xOkEjF3ws/b/YNlc+bvJHMYBn+vL+QbEISZFVi0qS+ImmhA1X3/gmNmBrjjIG3dg6aeN+n6gQitzSkXQ286qJQ=="],
"@turbo/windows-arm64": ["@turbo/windows-arm64@2.11.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-LURivfmgv34Osm8NR1gZGx4vdlux9KAgRM+FoooXNG0838ff0+akWZ76iPSke0NS2rsf7Z4Gw+YSaygzNTZuRA=="],
"@tusbar/cache-control": ["@tusbar/cache-control@1.0.2", "", {}, "sha512-PXfjYTYBVvMPYCLDWj+xIOA9ITFbbhWCHzLcqUCJ5TPGm4JO4cxpGb7x3K8Q1K1ADgNgfBxLsDcTMVRydtZB9A=="],
@@ -2636,7 +2636,7 @@
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
"next": ["next@16.4.3", "", { "dependencies": { "@next/env": "16.4.3", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.5.23", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.4.3", "@next/swc-darwin-x64": "16.4.3", "@next/swc-linux-arm64-gnu": "16.4.3", "@next/swc-linux-arm64-musl": "16.4.3", "@next/swc-linux-x64-gnu": "16.4.3", "@next/swc-linux-x64-musl": "16.4.3", "@next/swc-win32-arm64-msvc": "16.4.3", "@next/swc-win32-x64-msvc": "16.4.3", "sharp": "^0.35.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-OqxKTGi9u80FydydsMNYNjnuje9Au+kvHlt2GZ//ftdpDHm1dxKqAaENoOOTX5wKzn+9vaKUNIfZeyZUuLhCnA=="],
"next": ["next@16.4.5", "", { "dependencies": { "@next/env": "16.4.5", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.5.23", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.4.5", "@next/swc-darwin-x64": "16.4.5", "@next/swc-linux-arm64-gnu": "16.4.5", "@next/swc-linux-arm64-musl": "16.4.5", "@next/swc-linux-x64-gnu": "16.4.5", "@next/swc-linux-x64-musl": "16.4.5", "@next/swc-win32-arm64-msvc": "16.4.5", "@next/swc-win32-x64-msvc": "16.4.5", "sharp": "^0.35.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-rmdfsWosjTUO+6R0S3KokoIk6Kj0sWDRTe86tZHdqaWH/oXq/udX3/4xb+YQi7aWbyEFoglDU54kaifLCdXbBw=="],
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
@@ -2696,7 +2696,7 @@
"oxfmt": ["oxfmt@0.62.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.62.0", "@oxfmt/binding-android-arm64": "0.62.0", "@oxfmt/binding-darwin-arm64": "0.62.0", "@oxfmt/binding-darwin-x64": "0.62.0", "@oxfmt/binding-freebsd-x64": "0.62.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.62.0", "@oxfmt/binding-linux-arm-musleabihf": "0.62.0", "@oxfmt/binding-linux-arm64-gnu": "0.62.0", "@oxfmt/binding-linux-arm64-musl": "0.62.0", "@oxfmt/binding-linux-ppc64-gnu": "0.62.0", "@oxfmt/binding-linux-riscv64-gnu": "0.62.0", "@oxfmt/binding-linux-riscv64-musl": "0.62.0", "@oxfmt/binding-linux-s390x-gnu": "0.62.0", "@oxfmt/binding-linux-x64-gnu": "0.62.0", "@oxfmt/binding-linux-x64-musl": "0.62.0", "@oxfmt/binding-openharmony-arm64": "0.62.0", "@oxfmt/binding-win32-arm64-msvc": "0.62.0", "@oxfmt/binding-win32-ia32-msvc": "0.62.0", "@oxfmt/binding-win32-x64-msvc": "0.62.0" }, "peerDependencies": { "svelte": "^5.0.0", "vite-plus": "*" }, "optionalPeers": ["svelte", "vite-plus"], "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-vxgGHTmnDU9j4CX7dDBLzxgmHxfda/yPcgJkGCMUSCwRmz+euo/V08xXLNgXTeqAB9Fhf3Pe2nO1RNKLCVgphQ=="],
"oxlint": ["oxlint@1.90.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.90.0", "@oxlint/binding-android-arm64": "1.90.0", "@oxlint/binding-darwin-arm64": "1.90.0", "@oxlint/binding-darwin-x64": "1.90.0", "@oxlint/binding-freebsd-x64": "1.90.0", "@oxlint/binding-linux-arm-gnueabihf": "1.90.0", "@oxlint/binding-linux-arm-musleabihf": "1.90.0", "@oxlint/binding-linux-arm64-gnu": "1.90.0", "@oxlint/binding-linux-arm64-musl": "1.90.0", "@oxlint/binding-linux-ppc64-gnu": "1.90.0", "@oxlint/binding-linux-riscv64-gnu": "1.90.0", "@oxlint/binding-linux-riscv64-musl": "1.90.0", "@oxlint/binding-linux-s390x-gnu": "1.90.0", "@oxlint/binding-linux-x64-gnu": "1.90.0", "@oxlint/binding-linux-x64-musl": "1.90.0", "@oxlint/binding-openharmony-arm64": "1.90.0", "@oxlint/binding-win32-arm64-msvc": "1.90.0", "@oxlint/binding-win32-ia32-msvc": "1.90.0", "@oxlint/binding-win32-x64-msvc": "1.90.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2005", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-RkTN9EmgIUpLuhNoLhO3OliMGJlvb3Dk7XVjBZnfoMCHrVpuz5ESd97klfiyYeV7XfA2lNLnDCPMkUI+Qw5How=="],
"oxlint": ["oxlint@1.93.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.93.0", "@oxlint/binding-android-arm64": "1.93.0", "@oxlint/binding-darwin-arm64": "1.93.0", "@oxlint/binding-darwin-x64": "1.93.0", "@oxlint/binding-freebsd-x64": "1.93.0", "@oxlint/binding-linux-arm-gnueabihf": "1.93.0", "@oxlint/binding-linux-arm-musleabihf": "1.93.0", "@oxlint/binding-linux-arm64-gnu": "1.93.0", "@oxlint/binding-linux-arm64-musl": "1.93.0", "@oxlint/binding-linux-ppc64-gnu": "1.93.0", "@oxlint/binding-linux-riscv64-gnu": "1.93.0", "@oxlint/binding-linux-riscv64-musl": "1.93.0", "@oxlint/binding-linux-s390x-gnu": "1.93.0", "@oxlint/binding-linux-x64-gnu": "1.93.0", "@oxlint/binding-linux-x64-musl": "1.93.0", "@oxlint/binding-openharmony-arm64": "1.93.0", "@oxlint/binding-win32-arm64-msvc": "1.93.0", "@oxlint/binding-win32-ia32-msvc": "1.93.0", "@oxlint/binding-win32-x64-msvc": "1.93.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2005", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-19JMsQFX+YvEbcUTFQxjTwhvs0Ibg0D6CFneSLgpwnACWcY/6LjhpGpkPodh0T5aIT1pjMTLlYaeBFzaVgugTA=="],
"p-filter": ["p-filter@2.1.0", "", { "dependencies": { "p-map": "^2.0.0" } }, "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw=="],
@@ -3130,7 +3130,7 @@
"tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
"turbo": ["turbo@2.11.10", "", { "optionalDependencies": { "@turbo/darwin-64": "2.11.10", "@turbo/darwin-arm64": "2.11.10", "@turbo/linux-64": "2.11.10", "@turbo/linux-arm64": "2.11.10", "@turbo/windows-64": "2.11.10", "@turbo/windows-arm64": "2.11.10" }, "bin": { "turbo": "bin/turbo" } }, "sha512-3GJYwig8ygEI89oaOZdduJX8/aw6O9eRPYHoIt6T3FZl9hkKjiyIspufJoVgqXihPoJu8M5CSV0DlZvzfohBCw=="],
"turbo": ["turbo@2.11.11", "", { "optionalDependencies": { "@turbo/darwin-64": "2.11.11", "@turbo/darwin-arm64": "2.11.11", "@turbo/linux-64": "2.11.11", "@turbo/linux-arm64": "2.11.11", "@turbo/windows-64": "2.11.11", "@turbo/windows-arm64": "2.11.11" }, "bin": { "turbo": "bin/turbo" } }, "sha512-AI4UJbUE/2cVZdoXJ4PODc8qvrjhQQX9Fa93eKGOI6Ims/w7+kUIb9T1jhlgSnZcf+gtUruci/ohSwqtNNC5Yw=="],
"type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
+5 -4
View File
@@ -7,8 +7,8 @@
"devDependencies": {
"@changesets/cli": "^2.31.1",
"oxfmt": "^0.62.0",
"oxlint": "^1.90.0",
"turbo": "^2.11.10",
"oxlint": "^1.93.0",
"turbo": "^2.11.11",
"vercel": "50.37.3"
},
"packageManager": "bun@1.3.7",
@@ -67,7 +67,8 @@
"patchedDependencies": {
"decode-named-character-reference@1.0.2": "patches/decode-named-character-reference@1.0.2.patch",
"@vercel/next@4.4.2": "patches/@vercel%2Fnext@4.4.2.patch",
"next@16.4.3": "patches/next@16.4.3.patch",
"mermaid@11.16.1": "patches/mermaid@11.16.1.patch"
"mermaid@11.16.1": "patches/mermaid@11.16.1.patch",
"next@16.4.5": "patches/next@16.4.5.patch",
"@shikijs/langs@3.21.0": "patches/@shikijs%2Flangs@3.21.0.patch"
}
}
+6
View File
@@ -1,5 +1,11 @@
# @gitbook/colors
## 0.4.5
### Patch Changes
- 6c12872: Expand 3-digit hex colors correctly in `hexToRgbArray`.
## 0.4.4
### Patch Changes
+1 -1
View File
@@ -8,7 +8,7 @@
}
},
"sideEffects": false,
"version": "0.4.4",
"version": "0.4.5",
"devDependencies": {
"bun-types": "catalog:",
"tsdown": "catalog:",
+13 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'bun:test';
import { colorScale } from './transformations';
import { colorScale, hexToRgbArray } from './transformations';
describe('colorScale exact base', () => {
it('takes a very light tint as the exact background on step 1 (clean/bold)', () => {
@@ -67,3 +67,15 @@ describe('colorScale exact base', () => {
expect(scale[0]).not.toBe('#F5F3EF');
});
});
describe('hexToRgbArray', () => {
it('parses a 6-digit hex color', () => {
expect(hexToRgbArray('#1a2b3c')).toEqual([26, 43, 60]);
});
it('expands a 3-digit hex color', () => {
expect(hexToRgbArray('#abc')).toEqual([170, 187, 204]);
expect(hexToRgbArray('abc')).toEqual([170, 187, 204]);
expect(hexToRgbArray('#fff')).toEqual([255, 255, 255]);
});
});
+3 -1
View File
@@ -311,7 +311,9 @@ export function hexToRgbArray(hex: string): RGBColor {
const originalHex = hex;
let value = hex.replace('#', '');
if (hex.length === 3) value = value + value;
if (value.length === 3) {
value = [...value].map((digit) => digit + digit).join('');
}
const r = value.substring(0, 2);
const g = value.substring(2, 4);
+6
View File
@@ -1,5 +1,11 @@
# @gitbook/embed
## 0.7.0
### Minor Changes
- 31e88d1: Relicense under Apache 2.0 and drop the dependency on `@gitbook/icons`.
## 0.6.0
### Minor Changes
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+4
View File
@@ -542,3 +542,7 @@ button: {
icon: 'assistant'
}
```
## License
Distributed under the [Apache License 2.0](./LICENSE).
+4 -3
View File
@@ -13,10 +13,10 @@
}
},
"sideEffects": false,
"version": "0.6.0",
"version": "0.7.0",
"license": "Apache-2.0",
"dependencies": {
"@gitbook/api": "catalog:",
"@gitbook/icons": "workspace:",
"bidc": "catalog:"
},
"peerDependencies": {
@@ -42,7 +42,8 @@
"files": [
"dist",
"README.md",
"CHANGELOG.md"
"CHANGELOG.md",
"LICENSE"
],
"publishConfig": {
"access": "public",
+6 -1
View File
@@ -1,5 +1,10 @@
import type { AIToolCallResult, AIToolDefinition } from '@gitbook/api';
import type { IconName } from '@gitbook/icons';
/**
* Name of a Font Awesome icon (e.g. `bolt`), or a GitBook custom icon.
*/
// Kept as a plain string so the package doesn't depend on @gitbook/icons and its licence.
type IconName = string;
/**
* Confirmation action to be displayed to the user before executing a tool.
+50
View File
@@ -1,5 +1,55 @@
# gitbook
## 0.28.2
### Patch Changes
- b53e73c: Link suggested follow-up questions in markdown AI answers to the `.md` ask route so agents get markdown back.
- 4a40547: Fix the page outline not scrolling to keep the active section visible on long pages.
- 7ddd023: Fix search prewarming on public sites.
- Updated dependencies [31e88d1]
- @gitbook/embed@0.7.0
## 0.28.1
### Patch Changes
- a023a71: Warn visitors not to share sensitive personal info in the Assistant context popover.
- 6344646: Fix bash code blocks colouring the last letter of a word before `>` differently, as in `<name>`.
- 7e096d5: Bump Next.js to 16.4.3, keeping the dev-mode OOM patch.
- 198cafe: Bump Next.js to 16.4.5 and OpenNext (Cloudflare 1.20.16, AWS 4.1.15), keeping the dev-mode OOM patch.
- f61578f: Preconnect each asset origin once, skip same-origin icon URLs, and open the fonts preconnect in CORS mode so font requests can reuse it.
- 26ec43e: Stop caching failed searches, so a rate-limited or failing search is retried on the next query instead of being served from cache for 15 minutes.
- 8689736: Remove italic from dfn styling
- 04f39f1: Declare the Tailwind layer order in each component stylesheet, so a route whose first stylesheet is one of them still puts `base` before `components` and preflight does not strip their padding and borders.
- a512777: Forward the visitor's user agent on ask and Assistant calls so questions are classified by who asked.
- 508f1bc: Load the inline expression runtime only on pages whose code blocks or text use expressions, so other pages ship less JavaScript.
- 04f39f1: Pack the class lists that every block, heading anchor and table cell repeats into shared CSS classes, and carry a table cell's width once as a custom property instead of spelling it out as both width and min-width, so a large page carries far less markup.
- bd431f1: Split llms-full.txt into parts by size so large sites no longer fail with "content too large"
- a5d58bc: Follow site and space redirects when the MCP getPage tool resolves a page URL.
- e6b8502: Fix the MCP `getPage` tool returning "Page not found" for the Markdown URL of a page (`<page>.md`).
- e745c88: Add a search input to filter models by name on OpenAPI models pages.
- 96c5683: Let analytics tools like GA4 track outbound link clicks by using native link navigation.
- 347a1cc: Set `Partitioned` on visitor cookies set inside an iframe so authenticated embeds work cross-site.
- 3837c48: Fix encoded or non-normalized links on proxied sites redirecting to the internal proxy path instead of the public URL.
- ea01850: Support a secondary brand colour for secondary buttons and `$secondary` text, background, icon and tag colours. Sites without one render as before.
- c00a7b3: Resolve the published content with the full URL in a single lookup and always send the visitor country.
- 063e685: Resolve dark-mode image URLs in published Markdown.
- 09a8310: Serve a disallow-all robots.txt instead of redirecting to sign-in on authenticated access sites.
- b7b8ae6: Keep lists, paragraphs and code blocks as HTML in Updates RSS feeds, instead of running all the text together
- 4a72ead: Only apply a site preview customization override to the section or variant it was made for.
- 38f60f2: Render soft line breaks in paragraphs that contain buttons.
- 1383a79: Fix visitors being logged out right after signing in on sites with a logout link in the header.
- be658e1: Truncate long tab titles one by one and show the selected tab's full title, instead of hiding tabs behind a "…" menu
- Updated dependencies [6c12872]
- Updated dependencies [04f39f1]
- Updated dependencies [1b29a70]
- Updated dependencies [4dfe630]
- @gitbook/colors@0.4.5
- @gitbook/react-contentkit@0.7.18
- @gitbook/openapi-parser@3.1.0
- @gitbook/react-openapi@2.0.1
## 0.28.0
### Minor Changes
-10
View File
@@ -283,11 +283,6 @@ const testCases: TestsCase[] = [
contentBaseURL: 'https://vimeo.com',
tests: [{ name: 'Home', url: '/legal' }],
},
{
name: 'help.aikido.dev',
contentBaseURL: 'https://help.aikido.dev',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'doc.demarche.numerique.gouv.fr',
contentBaseURL: 'https://doc.demarche.numerique.gouv.fr',
@@ -589,11 +584,6 @@ const testCases: TestsCase[] = [
contentBaseURL: 'https://help.blotato.com',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.cartographer3d.com',
contentBaseURL: 'https://docs.cartographer3d.com',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.acestudio.ai',
contentBaseURL: 'https://docs.acestudio.ai',
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "gitbook",
"version": "0.28.0",
"version": "0.28.2",
"private": true,
"dependencies": {
"@base-ui/react": "catalog:",
@@ -20,8 +20,8 @@
"@gitbook/react-openapi": "workspace:*",
"@mermaid-js/mermaid-zenuml": "^0.2.2",
"@modelcontextprotocol/sdk": "1.17.5",
"@opennextjs/aws": "4.1.11",
"@opennextjs/cloudflare": "1.20.12",
"@opennextjs/aws": "4.1.15",
"@opennextjs/cloudflare": "1.20.16",
"@panzoom/panzoom": "^4.6.1",
"@sindresorhus/fnv1a": "^3.1.0",
"@tailwindcss/container-queries": "^0.1.1",
@@ -51,7 +51,7 @@
"micromark-extension-gfm": "^3.0.0",
"motion": "^12.23.24",
"negotiator": "^1.0.0",
"next": "^16.4.3",
"next": "^16.4.5",
"next-themes": "^0.4.6",
"nuqs": "^2.2.3",
"object-hash": "^3.0.0",
@@ -1,14 +0,0 @@
import { getSiteURLDataFromMiddleware } from '@/lib/middleware';
import { getServerActionBaseContext } from '@/lib/server-actions';
export async function POST() {
const [context, siteURLData] = await Promise.all([
getServerActionBaseContext(),
getSiteURLDataFromMiddleware(),
]);
const apiClient = await context.dataFetcher.api();
await apiClient.orgs.prewarmSiteSearch(siteURLData.organization, siteURLData.site);
return new Response(null, { status: 204 });
}
@@ -1,13 +1,10 @@
import { type NextRequest, NextResponse } from 'next/server';
import { orderSearchResultGroups } from './orderSearchResults';
import type {
OrderedComputedResult,
SearchSiteContentRequest,
} from '@/components/Search/search-types';
import type { SearchSiteContentRequest } from '@/components/Search/search-types';
import { throwIfDataError } from '@/lib/data';
import { getSiteURLDataFromMiddleware } from '@/lib/middleware';
import { transformSitePageResult } from '@/lib/search';
import { computeSearchResults } from '@/lib/search-results';
import { getServerActionBaseContext } from '@/lib/server-actions';
import { findSiteSpaceBy, getLinkerForSiteSpace } from '@/lib/sites';
@@ -56,49 +53,24 @@ export async function POST(request: NextRequest) {
? getLinkerForSiteSpace(context.linker, currentSiteSpace.siteSpace, revision.pages)
: context.linker;
const results = orderSearchResultGroups<OrderedComputedResult>(
searchResults.map((resultItem) => {
if (resultItem.type === 'record') {
const result: OrderedComputedResult = {
type: 'record',
id: resultItem.id,
title: resultItem.title,
description: resultItem.description,
href: resultItem.url,
score: resultItem.score,
};
const results = computeSearchResults(searchResults, (spaceItem, pageItem) => {
const isCurrentRevisionSpace = Boolean(revision && spaceItem.id === siteURLData.space);
const found =
isCurrentRevisionSpace && currentSiteSpace
? currentSiteSpace
: findSiteSpaceBy(structure, (siteSpace) => siteSpace.space.id === spaceItem.id);
return { type: 'context' as const, results: [result] };
}
const isCurrentRevisionSpace = Boolean(revision && resultItem.id === siteURLData.space);
const found =
isCurrentRevisionSpace && currentSiteSpace
? currentSiteSpace
: findSiteSpaceBy(
structure,
(siteSpace) => siteSpace.space.id === resultItem.id
);
return {
type: 'pages' as const,
results: resultItem.pages.flatMap((pageItem) => {
const result = transformSitePageResult({
asEmbeddable: Boolean(asEmbeddable),
linker: isCurrentRevisionSpace ? revisionLinker : context.linker,
pageItem,
spaceItem: resultItem,
siteSpace: found?.siteSpace,
siteSection: found?.siteSection ?? undefined,
siteSectionGroup: found?.siteSectionGroup ?? undefined,
revisionPages: isCurrentRevisionSpace ? revision?.pages : undefined,
});
return result ? [{ rank: pageItem.rank, result }] : [];
}),
};
})
);
return transformSitePageResult({
asEmbeddable: Boolean(asEmbeddable),
linker: isCurrentRevisionSpace ? revisionLinker : context.linker,
pageItem,
spaceItem,
siteSpace: found?.siteSpace,
siteSection: found?.siteSection ?? undefined,
siteSectionGroup: found?.siteSectionGroup ?? undefined,
revisionPages: isCurrentRevisionSpace ? revision?.pages : undefined,
});
});
return NextResponse.json(results);
}
@@ -146,6 +146,10 @@ export function AIChatInput(props: {
<Icon icon="message-question" className="size-3.5 opacity-7" />
{t(language, 'ai_chat_context_previous_messages')}
</li>
<li className="flex items-center gap-2">
<Icon icon="lock" className="size-3.5 opacity-7" />
{t(language, 'ai_chat_context_sensitive_info')}
</li>
</ul>
<p>{t(language, 'ai_chat_context_disclaimer')}</p>
</div>
@@ -19,7 +19,7 @@ export function AnnotationPopover(props: { children: React.ReactNode; body: Reac
>
<dfn
data-annotation=""
className="cursor-help underline decoration-dotted decoration-1 underline-offset-2"
className="not-italic cursor-help underline decoration-dotted decoration-1 underline-offset-2"
// oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- we want to be able to focus the definition to open the tooltip
tabIndex={0}
>
@@ -709,6 +709,19 @@ function joinLineContent(line: HighlightLine): string {
return visit(line.tokens);
}
it('keeps the last letter of a bash word before `>` in the same token', async () => {
const tokenContents = async (code: string) => {
const lines = await highlightWithInlines(singleLineBlock('bash', code));
return lines[0]!.tokens.map((t) => (t.type === 'shiki' ? t.token.content : ''));
};
const placeholder = await tokenContents('cli create --name <resource name>');
expect(placeholder.find((c) => c.includes('nam'))).toContain('name');
const fdRedirect = await tokenContents('cmd 2>&1');
expect(fdRedirect.find((c) => c.includes('2'))).toContain('2>&1');
});
function singleLineBlock(syntax: string | undefined, text: string): DocumentBlockCode {
return {
object: 'block',
@@ -4,6 +4,7 @@ import { useRouter } from 'next/navigation';
import React, { useEffect, useRef } from 'react';
import { createStore, useStore } from 'zustand';
import type { GitBookIntegrationTool } from '@gitbook/browser-types';
import type { GitBookEmbeddableConfiguration, ParentToFrameMessage } from '@gitbook/embed';
import { integrationsAssistantTools } from '../Integrations';
@@ -107,7 +108,8 @@ export function EmbeddableIframeAPI(props: { baseURL: string }) {
const { settings } = message;
embeddableConfiguration.setState(settings);
integrationsAssistantTools.setState({
tools: settings.tools,
// The embed types icons as plain strings to avoid depending on @gitbook/icons.
tools: settings.tools as GitBookIntegrationTool[],
});
const defaultPage =
@@ -49,7 +49,8 @@ export function ScrollSectionsList({ sections }: { sections: DocumentSection[] }
React.useEffect(() => {
if (activeId && activeItemRef.current && scrollContainerRef.current) {
scrollContainerRef.current?.scrollTo({
// The list's wrapper is the element that scrolls, not the list itself.
scrollContainerRef.current.parentElement?.scrollTo({
top: activeItemRef.current.offsetTop - ACTIVE_ITEM_OFFSET,
behavior: 'smooth',
});
@@ -2,6 +2,8 @@ import type { SiteExternalLink, SiteSection, SiteSectionGroup, SiteSpace } from
import { hasMultipleSiteSections } from '../SiteSections';
import type { GitBookSiteContext } from '@/lib/context';
import { getSearchSpaceLookup } from '@/lib/search';
import type { DirectSearchProps } from '@/lib/search-results';
import { getLocalizedTitle } from '@/lib/sites';
export type SearchSection = Pick<SiteSection, 'id' | 'icon' | 'object'> & {
@@ -27,6 +29,7 @@ export interface SearchBaseProps {
withSiteVariants: boolean;
indexURL: string;
searchURL: string;
directSearch?: DirectSearchProps;
}
export function getSearchBaseProps(context: GitBookSiteContext): SearchBaseProps {
@@ -38,6 +41,9 @@ export function getSearchBaseProps(context: GitBookSiteContext): SearchBaseProps
return {
searchURL: context.linker.toPathInSpace('~gitbook/search'),
directSearch: context.directSearch
? { ...context.directSearch, spaces: getSearchSpaceLookup(context.structure) }
: undefined,
section,
sections,
siteSpace,
@@ -0,0 +1,37 @@
// Spike only: lets a measurement harness switch the search arms from the browser without a rebuild,
// via `localStorage.gb_search_spike = '{"direct": false, "debounceMs": 200}'`.
// Delete this module (and its two call sites in useSearchResults.ts) when the spike ends.
const STORAGE_KEY = 'gb_search_spike';
type SearchSpikeOverrides = {
/** `false` forces the search route, `true` uses the API directly whenever the page allows it. */
direct?: boolean;
/** Replaces the search debounce. */
debounceMs?: number;
};
export function getSearchSpikeOverrides(): SearchSpikeOverrides {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) {
return {};
}
const parsed: unknown = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object') {
return {};
}
const { direct, debounceMs } = parsed as Record<string, unknown>;
return {
direct: typeof direct === 'boolean' ? direct : undefined,
debounceMs:
typeof debounceMs === 'number' && Number.isFinite(debounceMs) && debounceMs >= 0
? debounceMs
: undefined,
};
} catch {
return {};
}
}
@@ -131,6 +131,7 @@ export function useSearchController(
withSections,
siteSpaces,
searchURL,
directSearch,
indexURL,
} = props;
@@ -259,6 +260,7 @@ export function useSearchController(
scope: state?.scope ?? 'default',
suggestions: config.suggestions,
searchURL,
directSearch,
indexURL,
lang: siteSpace.space.language,
withSections,
@@ -1,7 +1,10 @@
import { readStreamableValue } from 'ai/rsc';
import { jwtDecode } from 'jwt-decode';
import React from 'react';
import { assert } from 'ts-essentials';
import type { SearchRecordResult, SearchSpaceResult } from '@gitbook/api';
import { useTrackEvent } from '../Insights';
import {
type RecommendedQuestionResult,
@@ -13,10 +16,19 @@ import { useRecentSearchQueries } from './recent-queries';
import { type MergedPageResult, fuseSearchResults } from './reciprocalRankFusion';
import { computeRemoteSearchScope } from './remote-scope';
import type { OrderedComputedResult, SearchSiteContentScope } from './search-types';
import { getSearchSpikeOverrides } from './searchSpikeOverrides';
import { streamRecommendedQuestions } from './server-actions';
import { type LocalPageResult, useLocalSearchResults } from './useLocalSearchResults';
import type { SearchScope } from './useSearch';
import { useAI } from '@/components/AI';
import { getEmbeddableLinker } from '@/lib/embeddable-linker';
import { createSiteLinker } from '@/lib/links';
import { removeTrailingSlash } from '@/lib/paths';
import {
type DirectSearchProps,
computePublishedSearchResults,
toSearchSiteContentBody,
} from '@/lib/search-results';
export type ResultType =
| OrderedComputedResult
@@ -29,6 +41,9 @@ export type { LocalPageResult, MergedPageResult };
// Small helper extracted for unit testing of scope → local filter mapping
// computeFilterSiteSpaceIds is imported from './filter' for testability
/** Delay between the last keystroke and the remote search request. */
const SEARCH_DEBOUNCE_MS = 200;
/**
* We cache the recommended questions globally to avoid calling the API multiple times
* when re-opening the search modal. The cache is per space, so that we can
@@ -50,6 +65,8 @@ export function useSearchResults(props: {
suggestions?: string[];
/** URL for the search API route (e.g. from linker.toPathInSpace('~gitbook/search')). */
searchURL: string;
/** Target to query the search API directly from the browser, when the page allows it. */
directSearch?: DirectSearchProps;
/** URL for the local index JSON (e.g. from linker.toPathInSite('~gitbook/site-index')). */
indexURL: string;
/** BCP-47 language code of the current site space, used to filter local search results. */
@@ -68,6 +85,7 @@ export function useSearchResults(props: {
scope,
suggestions,
searchURL,
directSearch,
indexURL,
lang,
withSections,
@@ -208,10 +226,15 @@ export function useSearchResults(props: {
let cancelled = false;
const abortController = new AbortController();
const timeout = setTimeout(async () => {
const fetchSearch = (
scope: Parameters<typeof fetchSearchResults>[1]
): Promise<OrderedComputedResult[]> =>
fetchSearchResults(searchURL, scope, query, abortController.signal, asEmbeddable);
const fetchSearch = (scope: SearchSiteContentScope): Promise<OrderedComputedResult[]> =>
fetchSearchResults({
searchURL,
directSearch,
scope,
query,
signal: abortController.signal,
asEmbeddable,
});
try {
const resultsPromise = fetchSearch(
@@ -261,7 +284,7 @@ export function useSearchResults(props: {
error: true,
});
}
}, 200);
}, getSearchSpikeOverrides().debounceMs ?? SEARCH_DEBOUNCE_MS);
abortRef.current = () => {
cancelled = true;
@@ -286,6 +309,7 @@ export function useSearchResults(props: {
disabled,
suggestions,
searchURL,
directSearch,
asEmbeddable,
]);
@@ -338,14 +362,107 @@ export function useSearchResults(props: {
};
}
/**
* Fetch search results from the search API, or from the search route when the page can't query it.
*/
async function fetchSearchResults(input: {
searchURL: string;
directSearch: DirectSearchProps | undefined;
scope: SearchSiteContentScope;
query: string;
signal: AbortSignal;
asEmbeddable: boolean | undefined;
}): Promise<OrderedComputedResult[]> {
const { searchURL, directSearch, scope, query, signal, asEmbeddable } = input;
if (directSearch && canSearchDirectly(directSearch)) {
try {
return await fetchDirectSearchResults({
directSearch,
scope,
query,
signal,
asEmbeddable: Boolean(asEmbeddable),
});
} catch (error) {
if (signal.aborted) {
throw error;
}
}
}
return fetchRouteSearchResults(searchURL, scope, query, signal, asEmbeddable);
}
function canSearchDirectly(directSearch: DirectSearchProps): boolean {
if (getSearchSpikeOverrides().direct === false) {
return false;
}
try {
const { exp } = jwtDecode<{ exp?: number }>(directSearch.apiToken);
// Same margin as the static routes, which stop serving a token this close to expiry.
return typeof exp === 'number' && exp > Date.now() / 1000 + 120;
} catch {
return false;
}
}
async function fetchDirectSearchResults(input: {
directSearch: DirectSearchProps;
scope: SearchSiteContentScope;
query: string;
signal: AbortSignal;
asEmbeddable: boolean;
}): Promise<OrderedComputedResult[]> {
const { directSearch, scope, query, signal, asEmbeddable } = input;
const { apiURL, apiToken, organizationId, siteId, spaces } = directSearch;
if (query.length <= 1) {
return [];
}
const response = await fetch(
`${removeTrailingSlash(apiURL)}/v1/orgs/${organizationId}/sites/${siteId}/search`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(toSearchSiteContentBody(query, scope)),
signal,
}
);
if (!response.ok) {
throw new Error(`Direct search request failed: ${response.status}`);
}
const data = (await response.json()) as {
items?: (SearchSpaceResult | SearchRecordResult)[];
};
if (!Array.isArray(data?.items)) {
throw new Error('Direct search response has no items');
}
const linker = createSiteLinker(directSearch.linker);
return computePublishedSearchResults({
asEmbeddable,
linker: asEmbeddable ? getEmbeddableLinker(linker) : linker,
searchResults: data.items,
spaces,
});
}
/**
* Fetch search results from the search API route.
*/
async function fetchSearchResults(
async function fetchRouteSearchResults(
searchURL: string,
scope: SearchSiteContentScope,
query: string,
signal?: AbortSignal,
signal: AbortSignal,
asEmbeddable?: boolean
): Promise<OrderedComputedResult[]> {
const response = await fetch(searchURL, {
@@ -65,11 +65,16 @@ export async function SiteLayout(props: {
const scripts = withTracking ? context.scripts : [];
// React dedupes preconnects by href, so reduce to the origin; relative URLs are same-origin.
[GITBOOK_API_PUBLIC_URL, GITBOOK_ICONS_URL, GITBOOK_ASSETS_URL].forEach((url) => {
[GITBOOK_ICONS_URL, GITBOOK_ASSETS_URL].forEach((url) => {
if (url && URL.canParse(url)) {
ReactDOM.preconnect(new URL(url).origin);
}
});
// Search queries the API with a CORS fetch without credentials, which only reuses an
// anonymous connection.
if (URL.canParse(GITBOOK_API_PUBLIC_URL)) {
ReactDOM.preconnect(new URL(GITBOOK_API_PUBLIC_URL).origin, { crossOrigin: 'anonymous' });
}
// Start the search-index download from the HTML itself. `crossOrigin` must match the
// client `fetch()` (cors + same-origin credentials) or the preload is ignored and the
@@ -137,6 +137,7 @@ export const ar: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'الصفحة التي أنت عليها الآن',
ai_chat_context_info_provided_by_the_site: 'معلومات عنك يوفرها الموقع',
ai_chat_context_previous_messages: 'الرسائل في محادثتك',
ai_chat_context_sensitive_info: 'لا تشارك معلومات شخصية حساسة',
ai_chat_context_disclaimer: 'قد تحتوي إجابات الذكاء الاصطناعي على أخطاء.',
ai_chat_input_placeholder: 'اسأل أو ابحث أو اشرح...',
ai_chat_queued_message: 'سيتم إرسال هذه الرسالة بعد أن ينتهي ${1}',
@@ -141,6 +141,7 @@ export const bg: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Страницата, на която сте в момента',
ai_chat_context_info_provided_by_the_site: 'Информация за вас, предоставена от сайта',
ai_chat_context_previous_messages: 'Съобщения във вашия разговор',
ai_chat_context_sensitive_info: 'Не споделяйте чувствителна лична информация',
ai_chat_context_disclaimer: 'AI отговорите може да съдържат грешки.',
ai_chat_input_placeholder: 'Попитайте, потърсете или обяснете...',
ai_chat_queued_message: 'Това съобщение ще бъде изпратено, след като ${1} приключи',
@@ -139,6 +139,7 @@ export const cs: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Stránka, na které se právě nacházíte',
ai_chat_context_info_provided_by_the_site: 'Informace o vás poskytnuté webem',
ai_chat_context_previous_messages: 'Zprávy ve vaší konverzaci',
ai_chat_context_sensitive_info: 'Nesdílejte citlivé osobní údaje',
ai_chat_context_disclaimer: 'Odpovědi AI mohou obsahovat chyby.',
ai_chat_input_placeholder: 'Zeptejte se, hledejte nebo vysvětlete...',
ai_chat_queued_message: 'Tato zpráva se odešle, až ${1} dokončí odpověď',
@@ -138,6 +138,7 @@ export const da: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Siden du er på nu',
ai_chat_context_info_provided_by_the_site: 'Oplysninger om dig fra webstedet',
ai_chat_context_previous_messages: 'Beskeder i din samtale',
ai_chat_context_sensitive_info: 'Del ikke følsomme personoplysninger',
ai_chat_context_disclaimer: 'AI-svar kan indeholde fejl.',
ai_chat_input_placeholder: 'Spørg, søg eller forklar...',
ai_chat_queued_message: 'Denne besked sendes, når ${1} er færdig',
@@ -145,6 +145,7 @@ export const de: TranslationLanguage = {
ai_chat_context_info_provided_by_the_site:
'Von der Website bereitgestellte Informationen über Sie',
ai_chat_context_previous_messages: 'Nachrichten in Ihrem Gespräch',
ai_chat_context_sensitive_info: 'Teilen Sie keine sensiblen persönlichen Daten',
ai_chat_context_disclaimer: 'KI-Antworten können Fehler enthalten.',
ai_chat_input_placeholder: 'Fragen, suchen oder erklären...',
ai_chat_queued_message: 'Diese Nachricht wird gesendet, sobald ${1} fertig ist',
@@ -143,6 +143,7 @@ export const el: TranslationLanguage = {
ai_chat_context_info_provided_by_the_site:
'Πληροφορίες για εσάς που παρέχονται από τον ιστότοπο',
ai_chat_context_previous_messages: 'Μηνύματα στη συνομιλία σας',
ai_chat_context_sensitive_info: 'Μην κοινοποιείτε ευαίσθητες προσωπικές πληροφορίες',
ai_chat_context_disclaimer: 'Οι απαντήσεις AI μπορεί να περιέχουν λάθη.',
ai_chat_input_placeholder: 'Ρωτήστε, αναζητήστε ή εξηγήστε...',
ai_chat_queued_message: 'Αυτό το μήνυμα θα σταλεί μόλις ολοκληρώσει ${1}',
@@ -137,6 +137,7 @@ export const en = {
ai_chat_context_pages_youve_read: "The page you're on now",
ai_chat_context_info_provided_by_the_site: 'Info about you provided by the site',
ai_chat_context_previous_messages: 'Messages in your conversation',
ai_chat_context_sensitive_info: "Don't share sensitive personal info",
ai_chat_context_disclaimer: 'AI responses may contain mistakes.',
ai_chat_input_placeholder: 'Ask, search, or explain...',
ai_chat_queued_message: 'This message will be sent after ${1} finishes',
@@ -143,6 +143,7 @@ export const es: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'La página en la que estás ahora',
ai_chat_context_info_provided_by_the_site: 'Información sobre ti proporcionada por el sitio',
ai_chat_context_previous_messages: 'Mensajes de tu conversación',
ai_chat_context_sensitive_info: 'No compartas información personal confidencial',
ai_chat_context_disclaimer: 'Las respuestas de IA pueden contener errores.',
ai_chat_input_placeholder: 'Pregunta, busca o explica...',
ai_chat_queued_message: 'Este mensaje se enviará cuando ${1} termine',
@@ -139,6 +139,7 @@ export const et: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Leht, kus te praegu asute',
ai_chat_context_info_provided_by_the_site: 'Saidi pakutud teave teie kohta',
ai_chat_context_previous_messages: 'Teie vestluse sõnumid',
ai_chat_context_sensitive_info: 'Ärge jagage tundlikku isikuteavet',
ai_chat_context_disclaimer: 'AI vastused võivad sisaldada vigu.',
ai_chat_input_placeholder: 'Küsige, otsige või selgitage...',
ai_chat_queued_message: 'See sõnum saadetakse, kui ${1} on lõpetanud',
@@ -140,6 +140,7 @@ export const fi: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Sivu, jolla olet nyt',
ai_chat_context_info_provided_by_the_site: 'Sivuston tarjoamat tiedot sinusta',
ai_chat_context_previous_messages: 'Viestit keskustelussasi',
ai_chat_context_sensitive_info: 'Älä jaa arkaluonteisia henkilötietoja',
ai_chat_context_disclaimer: 'AI-vastaukset voivat sisältää virheitä.',
ai_chat_input_placeholder: 'Kysy, hae tai selitä...',
ai_chat_queued_message: 'Tämä viesti lähetetään, kun ${1} on valmis',
@@ -139,6 +139,7 @@ export const fr: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'La page sur laquelle vous êtes actuellement',
ai_chat_context_info_provided_by_the_site: 'Informations vous concernant fournies par le site',
ai_chat_context_previous_messages: 'Messages de votre conversation',
ai_chat_context_sensitive_info: 'Ne partagez pas d’informations personnelles sensibles',
ai_chat_context_disclaimer: 'Les réponses générées peuvent contenir des erreurs.',
ai_chat_input_placeholder: 'Demander, rechercher ou expliquer...',
ai_chat_queued_message: 'Ce message sera envoyé une fois que ${1} aura terminé',
@@ -136,6 +136,7 @@ export const he: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'הדף שאתה נמצא בו כעת',
ai_chat_context_info_provided_by_the_site: 'מידע עליך שסופק על ידי האתר',
ai_chat_context_previous_messages: 'הודעות בשיחה שלך',
ai_chat_context_sensitive_info: 'אל תשתפו מידע אישי רגיש',
ai_chat_context_disclaimer: 'תשובות AI עשויות להכיל טעויות.',
ai_chat_input_placeholder: 'שאל, חפש או הסבר...',
ai_chat_queued_message: 'הודעה זו תישלח לאחר ש-${1} יסיים',
@@ -138,6 +138,7 @@ export const hi: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'वह पृष्ठ जिस पर आप अभी हैं',
ai_chat_context_info_provided_by_the_site: 'साइट द्वारा प्रदान की गई आपके बारे में जानकारी',
ai_chat_context_previous_messages: 'आपकी बातचीत के संदेश',
ai_chat_context_sensitive_info: 'संवेदनशील व्यक्तिगत जानकारी साझा न करें',
ai_chat_context_disclaimer: 'AI उत्तरों में गलतियां हो सकती हैं।',
ai_chat_input_placeholder: 'पूछें, खोजें या समझाएं...',
ai_chat_queued_message: 'यह संदेश ${1} के समाप्त होने के बाद भेजा जाएगा',
@@ -139,6 +139,7 @@ export const hr: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Stranica na kojoj se trenutno nalazite',
ai_chat_context_info_provided_by_the_site: 'Informacije o vama koje pruža web-mjesto',
ai_chat_context_previous_messages: 'Poruke u vašem razgovoru',
ai_chat_context_sensitive_info: 'Ne dijelite osjetljive osobne podatke',
ai_chat_context_disclaimer: 'AI odgovori mogu sadržavati pogreške.',
ai_chat_input_placeholder: 'Pitajte, pretražite ili objasnite...',
ai_chat_queued_message: 'Ova poruka bit će poslana kada ${1} završi',
@@ -141,6 +141,7 @@ export const hu: TranslationLanguage = {
ai_chat_context_info_provided_by_the_site:
'Az Önről szóló, a webhely által megadott információk',
ai_chat_context_previous_messages: 'Üzenetek a beszélgetésében',
ai_chat_context_sensitive_info: 'Ne osszon meg érzékeny személyes adatokat',
ai_chat_context_disclaimer: 'Az AI-válaszok hibákat tartalmazhatnak.',
ai_chat_input_placeholder: 'Kérdezzen, keressen vagy magyarázzon...',
ai_chat_queued_message: 'Ez az üzenet akkor lesz elküldve, amikor ${1} befejezi',
@@ -138,6 +138,7 @@ export const id: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Halaman tempat Anda berada sekarang',
ai_chat_context_info_provided_by_the_site: 'Info tentang Anda yang disediakan oleh situs',
ai_chat_context_previous_messages: 'Pesan dalam percakapan Anda',
ai_chat_context_sensitive_info: 'Jangan bagikan informasi pribadi yang sensitif',
ai_chat_context_disclaimer: 'Jawaban AI dapat berisi kesalahan.',
ai_chat_input_placeholder: 'Tanya, cari, atau jelaskan...',
ai_chat_queued_message: 'Pesan ini akan dikirim setelah ${1} selesai',
@@ -142,6 +142,7 @@ export const it: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'La pagina su cui ti trovi ora',
ai_chat_context_info_provided_by_the_site: 'Informazioni su di te fornite dal sito',
ai_chat_context_previous_messages: 'Messaggi nella tua conversazione',
ai_chat_context_sensitive_info: 'Non condividere informazioni personali sensibili',
ai_chat_context_disclaimer: "Le risposte dell'IA possono contenere errori.",
ai_chat_input_placeholder: 'Chiedi, cerca o spiega...',
ai_chat_queued_message: 'Questo messaggio verrà inviato quando ${1} avrà finito',
@@ -139,6 +139,7 @@ export const ja: TranslationLanguage = {
ai_chat_context_pages_youve_read: '現在表示中のページ',
ai_chat_context_info_provided_by_the_site: 'サイトから提供されるあなたに関する情報',
ai_chat_context_previous_messages: '会話内のメッセージ',
ai_chat_context_sensitive_info: '機密性の高い個人情報は共有しないでください',
ai_chat_context_disclaimer: 'AIの回答には誤りが含まれる場合があります。',
ai_chat_input_placeholder: '質問、検索、または説明...',
ai_chat_queued_message: '${1}の応答が完了したら、このメッセージが送信されます',
@@ -139,6 +139,7 @@ export const ko: TranslationLanguage = {
ai_chat_context_pages_youve_read: '현재 보고 있는 페이지',
ai_chat_context_info_provided_by_the_site: '사이트에서 제공하는 회원님에 대한 정보',
ai_chat_context_previous_messages: '대화의 메시지',
ai_chat_context_sensitive_info: '민감한 개인 정보를 공유하지 마세요',
ai_chat_context_disclaimer: 'AI 응답에는 오류가 있을 수 있습니다.',
ai_chat_input_placeholder: '질문, 검색 또는 설명...',
ai_chat_queued_message: '${1}의 응답이 끝나면 이 메시지가 전송됩니다',
@@ -139,6 +139,7 @@ export const lt: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Puslapis, kuriame šiuo metu esate',
ai_chat_context_info_provided_by_the_site: 'Svetainės pateikta informacija apie jus',
ai_chat_context_previous_messages: 'Pranešimai jūsų pokalbyje',
ai_chat_context_sensitive_info: 'Nebendrinkite jautrios asmeninės informacijos',
ai_chat_context_disclaimer: 'AI atsakymuose gali būti klaidų.',
ai_chat_input_placeholder: 'Klauskite, ieškokite arba paaiškinkite...',
ai_chat_queued_message: 'Ši žinutė bus išsiųsta, kai ${1} baigs',
@@ -138,6 +138,7 @@ export const lv: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Lapa, kurā pašlaik atrodaties',
ai_chat_context_info_provided_by_the_site: 'Vietnes sniegtā informācija par jums',
ai_chat_context_previous_messages: 'Ziņojumi jūsų sarunā',
ai_chat_context_sensitive_info: 'Nekopīgojiet sensitīvu personisko informāciju',
ai_chat_context_disclaimer: 'AI atbildēs var būt kļūdas.',
ai_chat_input_placeholder: 'Jautājiet, meklējiet vai skaidrojiet...',
ai_chat_queued_message: 'Šī ziņa tiks nosūtīta, kad ${1} būs pabeidzis',
@@ -138,6 +138,7 @@ export const ms: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Halaman yang anda berada sekarang',
ai_chat_context_info_provided_by_the_site: 'Maklumat tentang anda yang disediakan oleh laman',
ai_chat_context_previous_messages: 'Mesej dalam perbualan anda',
ai_chat_context_sensitive_info: 'Jangan kongsi maklumat peribadi yang sensitif',
ai_chat_context_disclaimer: 'Jawapan AI mungkin mengandungi kesilapan.',
ai_chat_input_placeholder: 'Tanya, cari atau jelaskan...',
ai_chat_queued_message: 'Mesej ini akan dihantar setelah ${1} selesai',
@@ -141,6 +141,7 @@ export const nl: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'De pagina waar je nu op bent',
ai_chat_context_info_provided_by_the_site: 'Informatie over jou verstrekt door de site',
ai_chat_context_previous_messages: 'Berichten in je gesprek',
ai_chat_context_sensitive_info: 'Deel geen gevoelige persoonlijke gegevens',
ai_chat_context_disclaimer: 'AI-antwoorden kunnen fouten bevatten.',
ai_chat_input_placeholder: 'Vraag, zoek of leg uit...',
ai_chat_queued_message: 'Dit bericht wordt verzonden zodra ${1} klaar is',
@@ -141,6 +141,7 @@ export const no: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Siden du er på nå',
ai_chat_context_info_provided_by_the_site: 'Informasjon om deg gitt av nettstedet',
ai_chat_context_previous_messages: 'Meldinger i samtalen din',
ai_chat_context_sensitive_info: 'Ikke del sensitive personopplysninger',
ai_chat_context_disclaimer: 'AI-svar kan inneholde feil.',
ai_chat_input_placeholder: 'Spør, søk eller forklar...',
ai_chat_queued_message: 'Denne meldingen sendes når ${1} er ferdig',
@@ -140,6 +140,7 @@ export const pl: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Strona, na której jesteś teraz',
ai_chat_context_info_provided_by_the_site: 'Informacje o Tobie podane przez stronę',
ai_chat_context_previous_messages: 'Wiadomości w Twojej rozmowie',
ai_chat_context_sensitive_info: 'Nie udostępniaj poufnych danych osobowych',
ai_chat_context_disclaimer: 'Odpowiedzi AI mogą zawierać błędy.',
ai_chat_input_placeholder: 'Zapytaj, wyszukaj lub wyjaśnij...',
ai_chat_queued_message: 'Ta wiadomość zostanie wysłana, gdy ${1} zakończy',
@@ -143,6 +143,7 @@ export const pt_br: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'A página em que você está agora',
ai_chat_context_info_provided_by_the_site: 'Informações sobre você fornecidas pelo site',
ai_chat_context_previous_messages: 'Mensagens na sua conversa',
ai_chat_context_sensitive_info: 'Não compartilhe informações pessoais sensíveis',
ai_chat_context_disclaimer: 'Respostas de IA podem conter erros.',
ai_chat_input_placeholder: 'Pergunte, pesquise ou explique...',
ai_chat_queued_message: 'Esta mensagem será enviada quando ${1} terminar',
@@ -140,6 +140,7 @@ export const pt: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'A página em que se encontra agora',
ai_chat_context_info_provided_by_the_site: 'Informações sobre si fornecidas pelo site',
ai_chat_context_previous_messages: 'Mensagens na sua conversa',
ai_chat_context_sensitive_info: 'Não partilhe informações pessoais sensíveis',
ai_chat_context_disclaimer: 'As respostas de IA podem conter erros.',
ai_chat_input_placeholder: 'Pergunte, pesquise ou explique...',
ai_chat_queued_message: 'Esta mensagem será enviada quando ${1} terminar',
@@ -142,6 +142,7 @@ export const ro: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Pagina pe care te afli acum',
ai_chat_context_info_provided_by_the_site: 'Informații despre tine furnizate de site',
ai_chat_context_previous_messages: 'Mesaje din conversația ta',
ai_chat_context_sensitive_info: 'Nu partajați informații personale sensibile',
ai_chat_context_disclaimer: 'Răspunsurile AI pot conține greșeli.',
ai_chat_input_placeholder: 'Întreabă, caută sau explică...',
ai_chat_queued_message: 'Acest mesaj va fi trimis după ce ${1} termină',
@@ -142,6 +142,7 @@ export const ru: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Страница, на которой вы сейчас находитесь',
ai_chat_context_info_provided_by_the_site: 'Информация о вас, предоставленная сайтом',
ai_chat_context_previous_messages: 'Сообщения в вашем разговоре',
ai_chat_context_sensitive_info: 'Не передавайте конфиденциальную личную информацию',
ai_chat_context_disclaimer: 'Ответы ИИ могут содержать ошибки.',
ai_chat_input_placeholder: 'Спросите, найдите или объясните...',
ai_chat_queued_message: 'Это сообщение будет отправлено, когда ${1} закончит',
@@ -141,6 +141,7 @@ export const sk: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Stránka, na ktorej sa práve nachádzate',
ai_chat_context_info_provided_by_the_site: 'Informácie o vás poskytnuté stránkou',
ai_chat_context_previous_messages: 'Správy vo vašej konverzácii',
ai_chat_context_sensitive_info: 'Nezdieľajte citlivé osobné údaje',
ai_chat_context_disclaimer: 'Odpovede AI môžu obsahovať chyby.',
ai_chat_input_placeholder: 'Pýtajte sa, hľadajte alebo vysvetľujte...',
ai_chat_queued_message: 'Táto správa sa odošle, keď ${1} skončí',
@@ -140,6 +140,7 @@ export const sl: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Stran, na kateri se trenutno nahajate',
ai_chat_context_info_provided_by_the_site: 'Informacije o vas, ki jih zagotavlja spletno mesto',
ai_chat_context_previous_messages: 'Sporočila v vašem pogovoru',
ai_chat_context_sensitive_info: 'Ne delite občutljivih osebnih podatkov',
ai_chat_context_disclaimer: 'Odgovori AI lahko vsebujejo napake.',
ai_chat_input_placeholder: 'Vprašajte, iščite ali razložite...',
ai_chat_queued_message: 'To sporočilo bo poslano, ko ${1} zaključi',
@@ -140,6 +140,7 @@ export const sv: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Sidan du är på nu',
ai_chat_context_info_provided_by_the_site: 'Information om dig från webbplatsen',
ai_chat_context_previous_messages: 'Meddelanden i din konversation',
ai_chat_context_sensitive_info: 'Dela inte känsliga personuppgifter',
ai_chat_context_disclaimer: 'AI-svar kan innehålla fel.',
ai_chat_input_placeholder: 'Fråga, sök eller förklara...',
ai_chat_queued_message: 'Det här meddelandet skickas när ${1} är klar',
@@ -135,6 +135,7 @@ export const th: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'หน้าที่คุณอยู่ตอนนี้',
ai_chat_context_info_provided_by_the_site: 'ข้อมูลเกี่ยวกับคุณที่เว็บไซต์ให้ไว้',
ai_chat_context_previous_messages: 'ข้อความในการสนทนาของคุณ',
ai_chat_context_sensitive_info: 'โปรดอย่าแชร์ข้อมูลส่วนบุคคลที่ละเอียดอ่อน',
ai_chat_context_disclaimer: 'คำตอบจาก AI อาจมีข้อผิดพลาด',
ai_chat_input_placeholder: 'ถาม ค้นหา หรืออธิบาย...',
ai_chat_queued_message: 'ข้อความนี้จะถูกส่งหลังจากที่ ${1} เสร็จสิ้น',
@@ -138,6 +138,7 @@ export const tr: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Şu anda bulunduğunuz sayfa',
ai_chat_context_info_provided_by_the_site: 'Site tarafından sağlanan sizinle ilgili bilgiler',
ai_chat_context_previous_messages: 'Konuşmanızdaki mesajlar',
ai_chat_context_sensitive_info: 'Hassas kişisel bilgileri paylaşmayın',
ai_chat_context_disclaimer: 'AI yanıtları hata içerebilir.',
ai_chat_input_placeholder: 'Sor, ara veya açıkla...',
ai_chat_queued_message: 'Bu mesaj, ${1} yanıtını tamamladıktan sonra gönderilecek',
@@ -138,6 +138,7 @@ export const uk: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Сторінка, на якій ви зараз перебуваєте',
ai_chat_context_info_provided_by_the_site: 'Інформація про вас, надана сайтом',
ai_chat_context_previous_messages: 'Повідомлення у вашій розмові',
ai_chat_context_sensitive_info: 'Не діліться конфіденційною особистою інформацією',
ai_chat_context_disclaimer: 'Відповіді AI можуть містити помилки.',
ai_chat_input_placeholder: 'Запитайте, знайдіть або поясніть...',
ai_chat_queued_message: 'Це повідомлення буде надіслано, коли ${1} завершить',
@@ -138,6 +138,7 @@ export const vi: TranslationLanguage = {
ai_chat_context_pages_youve_read: 'Trang bạn đang ở hiện tại',
ai_chat_context_info_provided_by_the_site: 'Thông tin về bạn do trang web cung cấp',
ai_chat_context_previous_messages: 'Tin nhắn trong cuộc trò chuyện của bạn',
ai_chat_context_sensitive_info: 'Không chia sẻ thông tin cá nhân nhạy cảm',
ai_chat_context_disclaimer: 'Câu trả lời AI có thể chứa lỗi.',
ai_chat_input_placeholder: 'Hỏi, tìm kiếm hoặc giải thích...',
ai_chat_queued_message: 'Tin nhắn này sẽ được gửi sau khi ${1} hoàn tất',
@@ -134,6 +134,7 @@ export const yue: TranslationLanguage = {
ai_chat_context_pages_youve_read: '你而家喺嘅頁面',
ai_chat_context_info_provided_by_the_site: '網站提供嘅關於你嘅資訊',
ai_chat_context_previous_messages: '你對話入面嘅訊息',
ai_chat_context_sensitive_info: '唔好分享敏感個人資料',
ai_chat_context_disclaimer: 'AI 回答可能有錯。',
ai_chat_input_placeholder: '發問、搜尋或說明...',
ai_chat_queued_message: '呢個訊息會喺 ${1} 完成後發送',
@@ -134,6 +134,7 @@ export const zh_tw: TranslationLanguage = {
ai_chat_context_pages_youve_read: '您目前所在的頁面',
ai_chat_context_info_provided_by_the_site: '網站提供的關於您的資訊',
ai_chat_context_previous_messages: '您對話中的訊息',
ai_chat_context_sensitive_info: '請勿分享敏感個人資料',
ai_chat_context_disclaimer: 'AI 回答可能包含錯誤。',
ai_chat_input_placeholder: '提問、搜尋或說明...',
ai_chat_queued_message: '這則訊息將在 ${1} 完成後傳送',
@@ -135,6 +135,7 @@ export const zh: TranslationLanguage = {
ai_chat_context_pages_youve_read: '您当前所在的页面',
ai_chat_context_info_provided_by_the_site: '网站提供的关于您的信息',
ai_chat_context_previous_messages: '您对话中的消息',
ai_chat_context_sensitive_info: '请勿分享敏感个人信息',
ai_chat_context_disclaimer: '人工智能的回答可能包含错误。',
ai_chat_input_placeholder: '询问、搜索或解释...',
ai_chat_queued_message: '此消息将在 ${1} 完成后发送',
-12
View File
@@ -1,12 +0,0 @@
/** Request markers for ChatGPT's server-side fetchers. */
export function isChatGPTRequest(request: Pick<Request, 'headers'>): boolean {
const normalizedUserAgent = request.headers.get('user-agent')?.toLowerCase() ?? '';
const normalizedSignatureAgent = request.headers.get('signature-agent')?.toLowerCase() ?? '';
return (
normalizedUserAgent.includes('chatgpt-user') ||
normalizedUserAgent.includes('chatgpt agent') ||
normalizedUserAgent.includes('oai-searchbot') ||
normalizedSignatureAgent.includes('chatgpt.com')
);
}
+36 -33
View File
@@ -20,9 +20,14 @@ import type {
TranslationLanguage,
} from '@gitbook/api';
import { GITBOOK_URL } from './env';
import { GITBOOK_API_PUBLIC_URL, GITBOOK_URL } from './env';
import { type ImageResizer, createImageResizer } from './images';
import { type GitBookLinker, createLinker, linkerForPublishedURL } from './links';
import {
type GitBookLinker,
type SiteLinkerInput,
createSiteLinker,
linkerForPublishedURL,
} from './links';
import type { AskVisitorAgent } from '@/lib/ask-visitor';
import {
type GitBookDataFetcher,
@@ -31,6 +36,7 @@ import {
throwIfDataError,
} from '@/lib/data';
import { getLogger } from '@/lib/logger';
import type { DirectSearchTarget } from '@/lib/search-results';
import {
findSiteSpaceBy,
getFallbackSiteSpacePath,
@@ -103,9 +109,6 @@ export type SiteURLData = Pick<
*/
isAiAgent?: boolean;
/** Whether the request comes from ChatGPT. Only set for Markdown and LLM routes. */
isChatGPT?: boolean;
/**
* Headers of the visitor asking through `?ask=`, forwarded to the API to classify the asker.
* Only set for the ask route, which is static and can't read the request headers.
@@ -136,6 +139,11 @@ export type GitBookBaseContext = {
* Translation language of the space.
*/
locale?: TranslationLanguage;
/**
* Target for the browser to query the site search API directly, when the request allows it.
*/
directSearch?: DirectSearchTarget;
};
/**
@@ -219,9 +227,6 @@ export type GitBookSiteContext = GitBookSpaceContext & {
/** Whether the request comes from a detected AI agent. Only set for markdown routes. */
isAiAgent?: boolean;
/** Whether the request comes from ChatGPT. Only set for Markdown and LLM routes. */
isChatGPT?: boolean;
};
/**
@@ -246,28 +251,14 @@ export function getBaseContext(input: {
apiToken: siteURLData.apiToken ?? null,
});
const gitbookURL = GITBOOK_URL ? new URL(GITBOOK_URL) : undefined;
const linker =
urlMode === 'url-host'
? createLinker({
host: siteURL.host,
siteBasePath: siteURLData.siteBasePath,
spaceBasePath: siteURLData.basePath,
})
: createLinker({
protocol: gitbookURL?.protocol,
host: gitbookURL?.host,
siteBasePath: `/url/${siteURL.host}${siteURLData.siteBasePath}`,
spaceBasePath: `/url/${siteURL.host}${siteURLData.basePath}`,
});
if (urlMode === 'url') {
// Create link in the same format for links to other sites/sections.
linker.toLinkForContent = (rawURL: string) => {
const urlObject = new URL(rawURL);
return `/url/${urlObject.host}${urlObject.pathname}${urlObject.search}${urlObject.hash}`;
};
}
const linkerInput: SiteLinkerInput = {
urlMode,
siteHost: siteURL.host,
siteBasePath: siteURLData.siteBasePath,
spaceBasePath: siteURLData.basePath,
gitbookURL: GITBOOK_URL || undefined,
};
const linker = createSiteLinker(linkerInput);
const imageResizer = createImageResizer({
imagesContextId: siteURLData.imagesContextId,
@@ -276,10 +267,25 @@ export function getBaseContext(input: {
linker: linker,
});
const directSearch: DirectSearchTarget | undefined =
siteURLData.apiToken &&
!siteURLData.revision &&
!siteURLData.changeRequest &&
!siteURLData.preview
? {
apiURL: GITBOOK_API_PUBLIC_URL,
apiToken: siteURLData.apiToken,
organizationId: siteURLData.organization,
siteId: siteURLData.site,
linker: linkerInput,
}
: undefined;
return {
dataFetcher,
linker,
imageResizer,
directSearch,
};
}
@@ -306,7 +312,6 @@ export async function fetchSiteContextByURLLookup(
preview: data.preview ?? false,
displayAgentInstructions: data.displayAgentInstructions,
isAiAgent: data.isAiAgent,
isChatGPT: data.isChatGPT,
});
}
@@ -331,7 +336,6 @@ export async function fetchSiteContextByIds(
preview: boolean;
displayAgentInstructions?: boolean;
isAiAgent?: boolean;
isChatGPT?: boolean;
}
): Promise<GitBookSiteContext> {
const { dataFetcher } = baseContext;
@@ -462,7 +466,6 @@ export async function fetchSiteContextByIds(
preview: ids.preview,
displayAgentInstructions: ids.displayAgentInstructions,
isAiAgent: ids.isAiAgent,
isChatGPT: ids.isChatGPT,
};
}
-87
View File
@@ -1,87 +0,0 @@
import { afterAll, afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
const cacheLife = mock((_profile: unknown) => {});
const cacheTag = mock((..._tags: string[]) => {});
const realNextCache = await import('next/cache');
mock.module('next/cache', () => ({ ...realNextCache, cacheLife, cacheTag }));
const { createDataFetcher } = await import('./api');
const originalFetch = globalThis.fetch;
afterAll(() => mock.restore());
describe('searchSiteContent', () => {
beforeEach(() => {
cacheLife.mockClear();
cacheTag.mockClear();
});
afterEach(() => {
globalThis.fetch = originalFetch;
});
function mockFetch(impl: () => Promise<Response>) {
globalThis.fetch = mock(impl);
}
function search() {
return createDataFetcher().searchSiteContent({
organizationId: 'org-1',
siteId: 'site-1',
query: 'test',
scope: { mode: 'all' },
});
}
it('caches successful results for hours with the site and response tags', async () => {
mockFetch(async () =>
Response.json(
{ items: [] },
{ headers: { 'x-gitbook-cache-tag': 'space:space-1,space:space-2' } }
)
);
const result = await search();
expect(result).toEqual({ data: [] });
expect(cacheLife.mock.calls).toEqual([['hours']]);
expect(cacheTag.mock.calls.flat()).toEqual([
'site:site-1',
'space:space-1',
'space:space-2',
]);
});
it('does not cache a transient API error', async () => {
mockFetch(async () =>
Response.json({ error: { code: 429, message: 'Namespace saturated' } }, { status: 429 })
);
const result = await search();
expect(result.error?.code).toBe(429);
expect(cacheLife.mock.calls).toEqual([[{ stale: 0, revalidate: 0, expire: 0 }]]);
});
it('does not cache a network failure', async () => {
mockFetch(() => Promise.reject(new TypeError('fetch failed')));
const result = await search();
expect(result.error?.code).toBe(500);
expect(cacheLife.mock.calls).toEqual([[{ stale: 0, revalidate: 0, expire: 0 }]]);
});
it('keeps the default cache life for a stable 404', async () => {
mockFetch(async () =>
Response.json({ error: { code: 404, message: 'Site not found' } }, { status: 404 })
);
const result = await search();
expect(result.error?.code).toBe(404);
expect(cacheLife).not.toHaveBeenCalled();
});
});
+4 -36
View File
@@ -6,14 +6,14 @@ import {
GitBookAPI,
type HttpResponse,
type RenderIntegrationUI,
type SiteSearchScope,
} from '@gitbook/api';
import { getCacheTag, getComputedContentSourceCacheTags } from '@gitbook/cache-tags';
import { cache } from '../cache';
import { DataFetcherError, isTransientDataFetcherError, wrapDataFetcherError } from './errors';
import { DataFetcherError, wrapDataFetcherError } from './errors';
import type { GitBookDataFetcher } from './types';
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_USER_AGENT } from '@/lib/env';
import { toSearchSiteContentBody } from '@/lib/search-results';
import { trace } from '@/lib/tracing';
interface DataFetcherInput {
@@ -785,21 +785,12 @@ const getEmbedByUrl = cache(
}
);
// We don't use remote cache for this one because of the number of potential cache entry causing 429 on vercel runtime cache when revalidating
const searchSiteContent = cache(
async (
input: DataFetcherInput,
params: Parameters<GitBookDataFetcher['searchSiteContent']>[0]
) => {
'use cache';
cacheTag(
getCacheTag({
tag: 'site',
site: params.siteId,
})
);
const result = await wrapDataFetcherError(async () => {
return wrapDataFetcherError(async () => {
return trace(
`searchSiteContent(${params.organizationId}, ${params.siteId}, ${params.query})`,
async () => {
@@ -808,39 +799,16 @@ const searchSiteContent = cache(
const res = await api.orgs.searchSiteContent(
organizationId,
siteId,
{
query,
...(scope.mode === 'current' && scope.restrictTo
? {
// `restrictTo` only exists in the newer `scope` request shape,
// and the published @gitbook/api types don't include it yet.
scope: {
mode: 'default',
currentSiteSpace: scope.siteSpaceId,
restrictTo: scope.restrictTo,
} as SiteSearchScope,
}
: scope),
},
toSearchSiteContentBody(query, scope),
{},
{
...noCacheFetchOptions,
}
);
cacheTag(...getCacheTagsFromResponse(res));
cacheLife('hours');
return res.data.items;
}
);
});
// Don't serve transient errors for the default 15 minutes. Only safe because search runs in
// request handlers: a page cache calling it would inherit this short life during API incidents.
if (result.error && isTransientDataFetcherError(result.error)) {
cacheLife({ stale: 0, revalidate: 0, expire: 0 });
}
return result;
}
);
+1 -1
View File
@@ -10,7 +10,7 @@ import { DataFetcherError, getExposableError } from './errors';
const INTERNAL_PATHS = [
// Simple `~gitbook` endpoints. `image`, `__evt` and `visitor` are served by the middleware
// before the lookup, but are listed so this stays a complete registry.
/^~gitbook\/(pdf|search|icon|site-index|image|__evt|visitor)$/,
/^~gitbook\/(pdf|search|search-prewarm|icon|site-index|image|__evt|visitor)$/,
/^~gitbook\/ogimage\/[^/]+$/,
/^~gitbook\/auth\/(login|logout)$/,
/^~gitbook\/oauth2\/v1\/[^/]+\/authorize$/,
+44
View File
@@ -197,6 +197,50 @@ export function createLinker(
return linker;
}
/**
* Serializable inputs of the linker of a site request.
*/
export type SiteLinkerInput = {
urlMode: 'url' | 'url-host';
siteHost: string;
siteBasePath: string;
spaceBasePath: string;
/** Main host GitBook runs on, used in `url` mode. */
gitbookURL: string | undefined;
};
/**
* Create the linker of a site request, for the URL mode it is served in.
*/
export function createSiteLinker(input: SiteLinkerInput): GitBookLinker {
const { urlMode, siteHost, siteBasePath, spaceBasePath } = input;
const gitbookURL = input.gitbookURL ? new URL(input.gitbookURL) : undefined;
const linker =
urlMode === 'url-host'
? createLinker({
host: siteHost,
siteBasePath,
spaceBasePath,
})
: createLinker({
protocol: gitbookURL?.protocol,
host: gitbookURL?.host,
siteBasePath: `/url/${siteHost}${siteBasePath}`,
spaceBasePath: `/url/${siteHost}${spaceBasePath}`,
});
if (urlMode === 'url') {
// Create link in the same format for links to other sites/sections.
linker.toLinkForContent = (rawURL: string) => {
const urlObject = new URL(rawURL);
return `/url/${urlObject.host}${urlObject.pathname}${urlObject.search}${urlObject.hash}`;
};
}
return linker;
}
/**
* Create a new linker that intercepts links that belongs to the published site and rewrite them
* relative to the URL being served.
@@ -1,37 +0,0 @@
import { describe, expect, it } from 'bun:test';
import { isChatGPTRequest } from './chatgpt';
import { getMarkdownContentType } from './markdown-content-type';
describe('ChatGPT Markdown compatibility', () => {
const requestWith = (headers: Record<string, string>) => ({ headers: new Headers(headers) });
it('detects ChatGPT requests without matching other agents', () => {
expect(isChatGPTRequest(requestWith({ 'user-agent': 'ChatGPT-User/1.0' }))).toBe(true);
expect(isChatGPTRequest(requestWith({ 'user-agent': 'ChatGPT Agent' }))).toBe(true);
expect(
isChatGPTRequest(
requestWith({
'user-agent':
'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; OAI-SearchBot/1.4; +https://openai.com/searchbot',
})
)
).toBe(true);
expect(
isChatGPTRequest(
requestWith({
'user-agent': 'SomeClient/1.0',
'signature-agent': '"https://chatgpt.com"',
})
)
).toBe(true);
expect(isChatGPTRequest(requestWith({ 'user-agent': 'GPTBot/1.2' }))).toBe(false);
expect(isChatGPTRequest(requestWith({ 'user-agent': 'ClaudeBot/1.0' }))).toBe(false);
});
it('uses plain text only for ChatGPT responses', () => {
expect(getMarkdownContentType(true)).toBe('text/plain; charset=utf-8');
expect(getMarkdownContentType(false)).toBe('text/markdown; charset=utf-8');
expect(getMarkdownContentType()).toBe('text/markdown; charset=utf-8');
});
});

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