Compare commits

...

552 Commits

Author SHA1 Message Date
Samy Pessé 885264f132 Version Packages (#3506)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-05 11:03:50 +02:00
Greg Bergé f1a6dec951 Less strict OpenAPI parser (#3529)
Co-authored-by: Nolann Biron <biron.nolann@gmail.com>
2025-08-05 08:45:45 +00:00
Claire Chabas 44f41510fa Fix close buttons tooltips (#3525) 2025-08-04 15:22:59 +02:00
spastorelli f220229e5e Fix stepper title misalignment (#3524) 2025-08-04 09:34:22 +02:00
Greg Bergé 61b4cb8d7c Upgrade Argos (#3522) 2025-08-01 16:07:35 +02:00
Greg Bergé 185cdb4883 Fix scrolling to anchor positionning (#3521) 2025-08-01 15:44:27 +02:00
Zeno Kapitein 2cdba53451 Upgrade to Tailwind v4 (#3206) 2025-08-01 13:03:44 +02:00
Greg Bergé 950f6c36cf greg/rnd 6997 fix long openapi responses label (#3518) 2025-07-31 12:08:57 +00:00
Zeno Kapitein ea37977b1e Fix inconsistent state between q= and ask= and keyboard navigation (#3519) 2025-07-31 13:20:27 +02:00
Greg Bergé 81da82aed7 Increase tests max duration (#3517) 2025-07-30 18:33:56 +00:00
Greg Bergé 39a3518651 Show a HoverCard when an internal link is broken (#3516) 2025-07-30 18:09:57 +02:00
spastorelli d903273759 Strip visitor.* param when normalizing URL (#3512) 2025-07-30 17:22:09 +02:00
Taran Vohra d130532f69 Suggest questions in the current space context (#3514) 2025-07-30 20:36:40 +05:30
Zeno Kapitein 94fcd21f34 Improve ?ask= init handling (#3513) 2025-07-30 13:17:15 +02:00
conico974 1d2db4ae2b Fix cookie handling for AI assistant server actions (#3509)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-30 12:29:51 +02:00
Steven H d270b4af9b Don't track events if a special middleware header is set. (#3511) 2025-07-30 09:26:01 +01:00
Zeno Kapitein ccfbd3c1a8 Fix AI Search Query (#3510) 2025-07-30 07:44:37 +00:00
Claire Chabas 5c4923557f Temporarily remove highlighting for PowerShell and C++ in Safari to avoid page crash (#3508) 2025-07-29 20:39:26 +02:00
Zeno Kapitein b1f608b865 Implement basic URL scheme for Assistant & AI Search (#3503) 2025-07-29 16:13:44 +02:00
conico974 c7993756dd Fix regional caching of tag cache (#3504)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-29 13:27:56 +02:00
Nolann B. d02465823f Prevent page breaks inside blocks when printing (#3507) 2025-07-28 18:08:08 +02:00
Greg Bergé 6b9f6b5b79 Improve french translations (#3501) 2025-07-28 14:05:04 +02:00
Taran Vohra df7de8fb0c Fix an issue where links were not rendering properly in Ask AI answers (#3505) 2025-07-28 16:43:18 +05:30
Samy Pessé 72f5ce3985 Version Packages (#3480)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-24 13:20:46 +02:00
conico974 04c80262e2 Make sure to use the correct version for adaptive content (#3487)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-24 11:40:12 +02:00
Valentino Hudhra 56d1b8fe2d Fix overflowing recommended follow up questions in assistant (#3502) 2025-07-24 11:03:04 +02:00
Zeno Kapitein 054a1634ab Improve AI Actions dropdown (#3499) 2025-07-24 10:40:13 +02:00
conico974 6ff63f2db6 Don't throw on >500 API error (#3500)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-24 09:41:47 +02:00
Greg Bergé b5ad0ce1e5 Fix OpenAPI v2 parsing (#3498) 2025-07-23 17:21:05 +02:00
Claire Chabas 2ba7e54c81 Fix headings styles in hint block (#3497) 2025-07-23 15:46:44 +02:00
Zeno Kapitein 0a858e74ad Fix React warning (#3496) 2025-07-23 10:56:12 +02:00
Zeno Kapitein b0c534fc2c Tweaks to AI Chat and AI Actions Dropdown (#3495) 2025-07-22 19:18:35 +02:00
Steven H 250c194a02 Add nosnippet to announcement banner. (#3490) 2025-07-22 15:03:35 +01:00
Zeno Kapitein 5b10cde936 Fix background corners showing through Powered through GitBook badge (#3493) 2025-07-22 15:15:38 +02:00
Zeno Kapitein 4aeb81bfa5 Fix search input styling & text cutoff (#3491) 2025-07-22 14:58:20 +02:00
conico974 955cebf944 Add cache tags for fetchFileSystem (#3489)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-22 13:41:31 +02:00
Greg Bergé 7c951ef437 Support new colors in text formatting (#3492) 2025-07-22 13:40:03 +02:00
Brett Jephson 68f0dbcc35 RND-7464: confirm in content kit button (#3488) 2025-07-22 10:51:25 +01:00
Zeno Kapitein 39c4f76aea Add fullwidth page option (#3344) 2025-07-21 11:20:17 +02:00
Zeno Kapitein 2fe58e8cbb Page actions customisation (#3485)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-07-18 21:10:38 +02:00
Zeno Kapitein 765141a4d8 Make "last updated" section optional (#3486) 2025-07-18 18:46:11 +00:00
Zeno Kapitein eb1bd3aa01 AI response feedback buttons (#3482) 2025-07-18 15:40:04 +00:00
Zeno Kapitein b7284b0223 Small tweaks to assistant styling (#3481) 2025-07-17 14:31:53 +00:00
conico974 5bbd254742 Support webp and avif formats for og image (#3478)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-17 11:36:07 +02:00
Nolann B. 611e28626a Remove padding on first Heading in Columns (#3479) 2025-07-16 12:51:37 +02:00
Samy Pessé 2a3bb0eb22 Version Packages (#3453)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-15 17:40:24 +02:00
conico974 6816f0fa5a Add browserslist and improve CSS masks for compatibility (#3471)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-15 17:15:56 +02:00
Greg Bergé 17386776ec Add missing changeset to release @gitbook/openapi-parser (#3477) 2025-07-15 12:01:40 +00:00
Nolann B. 9ee9082614 Improvement for CopyMarkdown action (#3465) 2025-07-15 13:41:25 +02:00
Nolann B. 334cfdd522 Fix responsive class for SearchInput button (#3476) 2025-07-15 12:50:57 +02:00
Greg Bergé 8c961a5dbd Fix OpenAPI spec validation (#3473) 2025-07-15 08:56:08 +02:00
conico974 4708956e77 Ensure schema.security is always an array (#3472)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-14 22:01:06 +02:00
Zeno Kapitein 813dd03dbb Fix AIChatInput focus & styling (#3470)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-07-14 11:48:34 +01:00
conico974 0522dbc61e Handle markdown errors gracefully (#3468)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-11 19:11:09 +02:00
Zeno Kapitein acb9f53156 Inline search modal (#3416) 2025-07-11 16:29:03 +01:00
conico974 6016846156 Add cache life inference from API response headers (#3467)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-11 17:10:00 +02:00
Greg Bergé cf1fae5837 Fix using CMD or CTRL to open in a new tab (#3466) 2025-07-11 14:25:37 +00:00
Nolann B. 7212345466 Fix AI Actions dropdown and LLM integration (#3464) 2025-07-11 12:14:06 +02:00
conico974 e38caf78ff Implement consistent logging across the application (#3368)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-11 10:36:03 +02:00
Greg Bergé 0a8dd67541 Better aria for breadcrumbs (#3461) 2025-07-11 09:45:14 +02:00
Nolann B. ed684c19a9 Move AI Actions markdown fetching to client-side (#3459) 2025-07-10 16:05:53 +02:00
Zeno Kapitein 52ab3681e3 Reposition AI Actions dropdown (#3460) 2025-07-09 14:38:23 +00:00
Zeno Kapitein 9cc5a787e9 Generalise keyboard shortcuts, add Cmd+J to AI Chat (#3457) 2025-07-09 12:13:36 +00:00
Nolann B. 0003030bf5 Implement AI actions dropdown (#3431) 2025-07-08 23:33:36 +02:00
Zeno Kapitein 9ad8f45d38 Rebrand assistant (#3458) 2025-07-08 21:31:14 +02:00
conico974 a3855257c8 Gracefully handle OpenAPI fetch errors (#3455)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-08 17:35:23 +02:00
conico974 9169c2f053 Fix site redirect resolution with length check for source paths (#3454)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-08 17:15:57 +02:00
Greg Bergé abe028bd38 Fix Link behaviour reloading the page (#3456) 2025-07-08 14:30:12 +00:00
conico974 12c2451f4f Bump OpenNext to latest versions (#3451)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-08 15:06:23 +02:00
Samy Pessé 8daede5427 Ensure all content links are resolved relatively to preview (#3450) 2025-07-08 13:05:31 +02:00
Samy Pessé 4c89aa60eb Adapt cache header parsing to support s-maxage (#3452) 2025-07-08 12:55:00 +02:00
Greg Bergé d0a3f64ae4 Always open header logo link in the current tab (#3445) 2025-07-08 09:02:49 +00:00
Samy Pessé 433d66482d Version Packages (#3446)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-08 11:01:57 +02:00
Greg Bergé bd553bcf9e Expose isJson and isYaml in @gitbook/openapi-parser (#3447) 2025-07-08 10:41:46 +02:00
Nolann B. f5894bcf74 Fix incorrect revision ID in Markdown fetch logic (#3448) 2025-07-08 09:41:12 +02:00
Zeno Kapitein 1b59e7ce36 Styling improvements to AI Chat Followup questions (#3444) 2025-07-07 19:02:01 +02:00
Samy Pessé 32d6f5845d Version Packages (#3247)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-07 12:49:53 +00:00
Greg Bergé 4f5cbfe732 Upgrade @scalar/openapi-parser (#3442) 2025-07-07 14:33:40 +02:00
Steven H afba8fcb34 Rating columns in tables now use a solid icons instead of unfilled. (#3434) 2025-07-07 10:05:45 +01:00
conico974 377a4899e4 Adjust cache life for error responses (#3423)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-07 09:38:48 +02:00
Greg Bergé 938bdeb34b Fix visual tests (#3440) 2025-07-06 09:02:31 +02:00
Greg Bergé d216eaa3f6 Upgrade Argos & disable bad plugin (#3438) 2025-07-05 21:37:20 +02:00
conico974 5ca5da0f21 Fix handling of bad requests to prevent unnecessary 500 errors (#3432)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-04 17:06:37 +02:00
Greg Bergé ebe6eb3b39 Support custom monospace fonts (#3430) 2025-07-04 13:13:36 +00:00
Zeno Kapitein 3bfe347096 Show section tabs when there is a single section group present (#3387) 2025-07-04 14:55:40 +02:00
Samy Pessé 7b38f89078 Update API client and condition for AI assistant (#3429) 2025-07-03 20:34:26 +02:00
Greg Bergé 09b689b0ea Fix table display in print (#3428) 2025-07-03 17:44:55 +02:00
Nolann B. 2350baa75f Support for OpenAPI Array request body (#3420) 2025-07-03 17:18:26 +02:00
Nolann B. bc1eca815e Error handling for AI chat (#3411) 2025-07-03 14:26:40 +02:00
conico974 78c10340e7 Increase max line length for code highlighting (#3415)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-03 13:05:42 +02:00
Valentino Hudhra c16890a3a6 Align README with CONTRIBUTING guides regarding bun version (#3427) 2025-07-03 12:40:08 +02:00
Addison 3b4fe2827a Update community ad on sponsored/community sites (#3421) 2025-07-03 11:03:16 +02:00
Samy Pessé 7e807aabb5 Don't prevent sending message in AI chat when loading followup suggestions (#3426) 2025-07-03 10:16:42 +02:00
Samy Pessé 973c74ee69 Adapt AI assistant to upcoming API changes (#3422) 2025-07-02 20:58:23 +02:00
Zeno Kapitein efed0b0617 Turn on Docs Assistant for another site (#3425) 2025-07-02 18:02:12 +02:00
Zeno Kapitein ca3b9aca5d Improve AI Chat context popup (#3424) 2025-07-02 18:01:24 +02:00
Taran Vohra e2afc07ab2 Fix resolution of page by resolving site redirects before space redirects (#3414) 2025-07-02 17:30:18 +05:30
Brett Jephson e8fb84d362 Remove flip hash from columns (#3413) 2025-07-02 09:41:53 +01:00
Samy Pessé 57f951a7d9 Remove AI page link summaries (#3418) 2025-07-02 08:40:55 +02:00
conico974 28008667ed Make Emoji component synchronous (#3417)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-07-01 17:10:16 +02:00
Samy Pessé f3affc3034 Add support for MCP tools (#3410) 2025-07-01 09:58:59 +02:00
conico974 59da30f3c6 Add support for cover image repositioning (#3404)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-30 19:07:48 +02:00
Greg Bergé 2db721112a Fix OpenAPI error (#3408) 2025-06-30 15:56:22 +00:00
Samy Pessé b60039b7d5 Fix links to other spaces in a section (#3409)
Co-authored-by: Steven H <steven@gitbook.io>
2025-06-30 17:47:23 +02:00
Steven H 8fe9c9afea Fix an issue where same-site references across spaces created invalid links. (#3407) 2025-06-30 16:03:27 +01:00
Zeno Kapitein 216ba7a556 Add analytics on AI chat (#3405) 2025-06-30 08:41:12 +00:00
Samy Pessé a2ff57081b Fix changesets 2025-06-29 10:41:30 +02:00
Zeno Kapitein 40dbd9a14b AI Chat followup (#3403) 2025-06-28 17:55:56 +02:00
Nolann B. e5bac69c5c Fix markdown page generation for groups (#3402) 2025-06-27 23:32:35 +02:00
Zeno Kapitein 8d6598393f AI Chat UI (#3397)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-06-27 17:43:14 +02:00
Greg Bergé 59b86eb9dc Correctly handle OpenAPI errors (#3401) 2025-06-27 11:15:21 +02:00
Steven H 52c9f6da2f Fix an issue with link resolution in cross-space reusable content. (#3392)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-06-27 11:14:38 +02:00
Nolann B. dd65987c94 Include page group children under the .md route (#3400) 2025-06-27 09:53:18 +02:00
conico974 ace61901a4 Fix ScrollSectionsList with suspended component (#3390)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-26 15:37:51 +02:00
conico974 e6c3c7635d refactor toc components to client (#3394)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-06-26 11:35:02 +02:00
conico974 5134d9e7f8 Avoid unnecessary proxy URL lookups (#3377)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-25 17:21:09 +02:00
conico974 6821fb2937 Downgrade esbuild to fix wrangler dev issues (#3396)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-25 16:50:09 +02:00
Taran Vohra cfe4812045 Replace all usage of getPublishedContentByUrl (#3395) 2025-06-25 18:55:02 +05:30
Samy Pessé 14843ac343 Fix changesets (#3393) 2025-06-25 14:42:49 +02:00
Samy Pessé 8fb6d7465f Remove v1 and replace by v2 (#3391)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-25 13:28:04 +02:00
Samy Pessé 0ef647586f Hooks and APIs for AI v2 (#3385) 2025-06-25 09:48:28 +02:00
Nolann B. b4039627d9 Handle nullish OpenAPI mediaTypeObject (#3389) 2025-06-24 17:25:38 +00:00
Addison 4a295e6db4 Update contributing guide (#3381) 2025-06-24 17:55:24 +02:00
Greg Bergé 5726999a5a Fix viewing a page from a revision (#3388) 2025-06-24 15:31:06 +00:00
conico974 58d7f3c140 Fix revision ID handling for computed content (#3386)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-24 15:00:33 +02:00
Samy Pessé 07eab986d5 Optimize fetching of revisions (#3382) 2025-06-23 19:15:17 +02:00
Samy Pessé 711cf38f9b Use getRevision in getRevisionFile (#3379) 2025-06-23 18:43:41 +02:00
Zeno Kapitein 4f7c0eea9f Clicking an active TOC item toggles its descendants (#3378) 2025-06-23 17:27:34 +02:00
conico974 f3448de2da Improve efficiency of the queue (#3373)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-23 15:09:29 +02:00
Viktor Renkema ec92075ef8 Set tw classes correctly for horizontal alignment in tables (#3375) 2025-06-23 14:35:32 +02:00
Nolann B. 1e013cd87c Optional label in OpenAPI x-codeSamples (#3376) 2025-06-23 13:54:15 +02:00
Samy Pessé f033734308 Support for site customization option to change how external links open (#3372) 2025-06-22 15:49:15 +02:00
Nolann B. 50c05164f7 Bump @scalar/api-client-react (#3371) 2025-06-22 12:03:10 +02:00
conico974 7fb9004b43 Properly redirect to the root page (#3369)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-20 19:15:21 +02:00
Greg Bergé 4721403986 Hide scrollbar on sections (#3367) 2025-06-20 12:31:18 +00:00
Samy Pessé df848ef64e Icon in buttons and text alignment (#3365) 2025-06-20 12:08:28 +02:00
conico974 0b40ccd286 Remove buildId from composable cache key generation (#3366)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-20 12:06:44 +02:00
Greg Bergé caaa692f0b Allow to zoom image on mobile if relevant (#3364) 2025-06-20 10:25:58 +02:00
Greg Bergé 42d43e09c4 Always show scrollbars (#3363) 2025-06-20 08:41:21 +02:00
Samy Pessé 7ccbf55ba7 Set myself as codeowner for data cache (#3355) 2025-06-20 08:41:02 +02:00
conico974 81f5bfc2f9 Filter out malicious request (#3357)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-19 17:00:37 +02:00
Greg Bergé a7a713bca2 Scroll to active TOC when clicking a link (#3362) 2025-06-19 16:50:13 +02:00
conico974 d6613c787f Make skeletons client component (#3361)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-19 16:46:31 +02:00
Greg Bergé 427f748e1c Add metadata for adding site to Apple devices home (#3360) 2025-06-19 16:35:07 +02:00
Greg Bergé 4f5fec7213 Fix CodeBlock rendering (#3359) 2025-06-19 15:55:19 +02:00
Greg Bergé 8c0a53ab40 Fix page group not expanded by default (#3358) 2025-06-19 15:08:59 +02:00
conico974 2dfd5aab20 Update SpacesDropdown to manage active state based on current space (#3356)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-19 13:39:59 +02:00
conico974 8d3c6562a2 Implement a StylesProvider (#3352)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-18 19:02:32 +02:00
Zeno Kapitein dae019c115 Consistently show variant selector in section bar if site has sections (#3349) 2025-06-18 17:48:49 +02:00
Steven H 73e0cbb2d6 Fix an issue where PDF export URLs were not bringing their query params. (#3351) 2025-06-18 13:26:20 +01:00
Steven H 392f59450c Improve performance of InlineLinkTooltip (#3339) 2025-06-18 12:15:45 +01:00
conico974 b4918f60ce fix variant space change when pathname equals space-path (#3343)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-17 22:31:32 +02:00
Samy Pessé 8f7c304d58 Fix crash when rendering ogimage with invalid icon (#3348) 2025-06-17 20:25:21 +02:00
Samy Pessé a3a944d7dd Fix crash during rendering of ogimage for VA sites with default icon (#3347) 2025-06-17 19:38:47 +02:00
Samy Pessé 88a35ed057 Fix crash when integration is triggering invalid requests (#3346) 2025-06-17 19:23:52 +02:00
Samy Pessé 67998b6f15 Fix ogimage generation failing with some JPEG images (#3345) 2025-06-17 19:10:15 +02:00
Samy Pessé ff6d1150a5 Use getRevisionPageDocument on v2 (#3342) 2025-06-17 16:12:58 +02:00
Samy Pessé 500c8cb649 Don't crash ogimage generation on RTL text (#3341) 2025-06-17 13:04:50 +02:00
Samy Pessé 6859f7d239 Fix rendering of ogimage when logo or icon are AVIF images (#3336)
Co-authored-by: Steven H <steven@gitbook.io>
2025-06-17 00:31:07 +02:00
Samy Pessé dfa8a37be1 Don't cache unexpected API errors for more than a few minutes (#3334) 2025-06-17 00:16:52 +02:00
Samy Pessé 11a6511b7a Fix crash when integration script fails to render block (#3332) 2025-06-16 20:06:33 +02:00
Samy Pessé af98402655 Add support for inline icons (#3329) 2025-06-16 19:15:22 +02:00
Samy Pessé b7a0db3339 Fix rendering of ogimage with SVG logos (#3328) 2025-06-16 16:59:54 +02:00
Greg Bergé 382a19885b Hide empty section groups (#3327) 2025-06-16 13:52:42 +00:00
Utku Ufuk d99da6a3ae Ignore case while pattern-matching to highlight search results (#3326) 2025-06-16 12:33:00 +00:00
Samy Pessé 72cd0e59e6 Replace withoutConcurrency by a smarter React.cache (#3325) 2025-06-16 13:37:45 +02:00
Utku Ufuk 42d88da73c Fix UX issue about highlighting the search term in search result sections (#3323) 2025-06-16 13:31:15 +03:00
Taran Vohra 2863fe0dc1 Use resolvePublishedContentByUrl instead of the deprecated resolution endpoint (#3310) 2025-06-16 09:59:55 +05:30
conico974 87218062be Use Promise.all whenever possible (#3319)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-14 11:10:26 +02:00
Samy Pessé 8a3910e208 Experiment with hiding sections without a matching site space (#3318) 2025-06-13 15:55:04 +02:00
conico974 6294bbb53c Fix error propagating (#3317)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-13 15:46:37 +02:00
Zeno Kapitein a28a997851 Add margin to adjacent buttons (#3316) 2025-06-13 15:42:00 +02:00
spastorelli 28a9ee701e Add paypal.gitbook.com to ADAPTIVE_CONTENT_HOSTS list (#3315) 2025-06-13 10:10:10 +00:00
Samy Pessé 50e8b2ec52 Don't use next/server after (#3314) 2025-06-13 11:30:18 +02:00
Samy Pessé 315717fa2d Disable the background fetch of getRevisionPageDocument (#3313) 2025-06-13 11:17:33 +02:00
Samy Pessé da67711c90 Fix crash on Vercel (#3311) 2025-06-12 23:37:38 +02:00
Samy Pessé fecdbe8dde Support dynamic expressions (#3295) 2025-06-12 12:25:28 +02:00
Nolann B. 9316ccd1f4 Update Models page styling (#3307) 2025-06-12 10:57:37 +02:00
Samy Pessé c3b620e975 Preserve current variant when navigating between sections (#3306) 2025-06-12 09:30:08 +02:00
Nolann B. 7a00880e5b Improve support for OAuth2 security type (#3304) 2025-06-11 15:34:52 +02:00
Zeno Kapitein b6b597564d Reverse order of feedback smileys (#3300) 2025-06-11 14:44:07 +02:00
spastorelli d410381957 Add docs.testgitbook.com to ADAPTIVE_CONTENT_HOSTS list (#3303) 2025-06-11 14:25:05 +02:00
conico974 902c3c6c1a apply customization for dynamic site context in v2 (#3302)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-11 14:05:30 +02:00
Zeno Kapitein 4fb2a4a6d7 Rework full-width layout, add support for full-width page option (#3293)
Co-authored-by: conico974 <nicodorseuil@yahoo.fr>
2025-06-11 14:03:45 +02:00
conico974 2d64c78787 return null for 404 cached page in the incremental cache (#3296)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-11 13:47:34 +02:00
Samy Pessé fbfcca5dae Fix rendering of font in ogimage (#3299) 2025-06-11 13:41:26 +02:00
Greg Bergé 664ae8bff6 Make heading from stepper visible in outline (#3301) 2025-06-11 13:40:59 +02:00
Samy Pessé fa3d2aae08 Fix inline expression crashing pages (#3297) 2025-06-11 12:27:24 +02:00
Zeno Kapitein e7a591d9f6 Fix border being added to cards (#3294) 2025-06-10 17:25:29 +00:00
conico974 2860beacdc revert to use withoutConcurrentExecution (#3292)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-10 19:05:07 +02:00
Zeno Kapitein 231167d3e1 Make icons for page groups more contrasting (#3286) 2025-06-10 17:48:06 +02:00
Brett Jephson c0ee60ec13 RND-6843: columns block (#3257)
Co-authored-by: Steven Hall <steven@gitbook.io>
2025-06-09 11:37:56 +01:00
conico974 f58b904ba6 Encode customization header (#3289)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-09 11:37:29 +02:00
conico974 d5992f1a5c Bump OpenNext to latest (#3288)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-09 10:40:44 +02:00
Samy Pessé fa12f9eeb6 Page cover dark-mode specific (#3287) 2025-06-08 14:57:50 +02:00
Samy Pessé 33726c88a0 Generate a llms-full version of the docs (#3285) 2025-06-08 12:13:14 +02:00
Zeno Kapitein 6aa3ff9c5e Fix various styling issues (#3283) 2025-06-08 00:38:41 +01:00
Zeno Kapitein 7d3fe23195 Add circular corners and depth styles (#3280)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-06-07 13:48:16 +02:00
Samy Pessé 521052d84f Remove withoutConcurrentExecution and withCacheKey (#3281) 2025-06-05 20:43:15 +01:00
Samy Pessé c730845e17 Fix missing title for button to close announcement banner (#3279) 2025-06-04 16:33:01 +01:00
Addison 7b5a3616c4 Add UTM tags to the preview site badge (#3277) 2025-06-03 16:07:55 +01:00
Zeno Kapitein c9373efdb5 Fix bold header links hover color (#3255) 2025-06-03 15:18:04 +02:00
Brett Jephson 015615de8a fix: defaultWidth/fullWidth options in images (#3274) 2025-06-03 12:18:01 +01:00
conico974 0f867e533a bump OpenNext to latest (#3276)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-02 17:40:04 +02:00
conico974 7d5a6d2255 Fix hydration error on V2 (#3272)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-06-02 14:23:15 +01:00
conico974 a1da4f023d Move DO to a 3rd worker (#3270)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-30 16:19:11 +02:00
conico974 666e8426e1 External middleware in cloudflare (#3254)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-30 14:56:00 +02:00
conico974 df2fa42bda Chores: create server worker (#3265)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-30 14:31:21 +02:00
conico974 b3a7ad6d2c Fix href being empty in TOC (#3269)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-28 17:07:33 +02:00
conico974 2932077bf9 Remove trailing slash from linker (#3268)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-27 21:13:55 +02:00
Nolann B. 957afd967f Add authorization header for OAuth2 (#3266) 2025-05-26 20:54:07 +02:00
Nolann B. 4c9a9d0416 Handle nested deprecated properties in generateSchemaExample (#3267) 2025-05-26 20:28:26 +02:00
Nolann B. 40df91a363 Deduplicate path parameters from OpenAPI spec (#3264) 2025-05-26 18:55:11 +02:00
conico974 af66ff71ed Add a force-revalidate api route (#3263)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-25 16:55:45 +02:00
Samy Pessé 6f8ecd6111 Use more strict context ID for images during preview (#3261) 2025-05-23 13:16:43 +02:00
conico974 fa3eb07617 Cache fonts and static image in memory for OG Image (#3258)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-23 10:20:07 +02:00
Samy Pessé dc4268db64 Compute correct urls for variants/sections when previewing unpublished site (#3259) 2025-05-23 10:01:15 +02:00
Nolann B. a0c06a72cc Indent JSON python code sample (#3260) 2025-05-23 09:13:20 +02:00
conico974 19c20c0be3 Fix OGImage icon fetch on cloudflare (#3253)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-21 15:39:52 +02:00
Viktor Renkema fae34e56ac Calculate class more explicitly & separately to fix horizontal table cell alignment issue (#3252) 2025-05-21 14:20:39 +02:00
conico974 ba0094a862 fix ISR in preview in cloudflare (#3250)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-21 09:52:05 +02:00
conico974 b259009c9c Add temporary logging for Cloudflare debugging (#3249)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-20 19:33:33 +02:00
conico974 d88dd5ed8a Update OpenNext (#3248)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-20 19:04:07 +02:00
Zeno Kapitein 57bb146075 Dynamic TOC height (#3233) 2025-05-20 14:13:57 +02:00
Zeno Kapitein 4b67fe5317 V2: Add urlObject.hash to linker.toLinkForContent (#3246) 2025-05-20 10:25:35 +02:00
Samy Pessé 938125e772 Version Packages (#3180)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-19 19:10:57 +02:00
conico974 1c8d9febb3 Custom Incremental Cache (#3239)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-19 17:00:09 +02:00
conico974 223edae17d Add env variables for the DOQueue (#3243)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-19 13:47:33 +02:00
Nolann B. 12a455dab5 Fix OpenAPI layout issues (#3242) 2025-05-18 14:50:05 +02:00
Nolann B. d00dc8ca72 Pass spec validation errors through OpenAPIParseError (#3240) 2025-05-18 13:20:17 +02:00
Samy Pessé 2c85a302fc Update opennext and leverage "use cache" on CF (#3204)
Co-authored-by: Nicolas Dorseuil <nicolas@gitbook.io>
2025-05-16 13:58:35 +01:00
Addison 3460b78b45 Update docs.gitbook.com to gitbook.com/docs (#3187)
Co-authored-by: Tal Gluck <talagluck@gmail.com>
Co-authored-by: Taran Vohra <taranvohra@outlook.com>
2025-05-16 17:59:53 +05:30
Zeno Kapitein 95a1f652f9 Better print layouts: wrap code blocks & force table column auto-sizing (#3236) 2025-05-15 13:24:10 +02:00
spastorelli cc37e2ae15 Persist visitor params values in public visitor session cookie (#3231) 2025-05-14 14:16:13 +02:00
Claire Chabas 08382ce62f Improve tabs link support (#3211) 2025-05-14 11:10:09 +02:00
Nolann B. 80cb52a237 Handle OpenAPI alternatives from schema.items (#3235) 2025-05-14 08:05:08 +02:00
Nolann B. c6637b09e2 Use default value if string/number/boolean in generateSchemaExample (#3234) 2025-05-13 22:07:41 +02:00
Samy Pessé 778624af00 Only resize images with supported extensions (#3229) 2025-05-13 10:52:58 +02:00
Samy Pessé e15757d01f Use next@15.3.2 instead of canary (#3227) 2025-05-09 15:17:49 +02:00
Zeno Kapitein aed79fd6c3 Remove header logo rounding (#3225) 2025-05-09 14:45:38 +02:00
Greg Bergé 0c973a3292 Always link main logo to the root of the site (#3226) 2025-05-09 10:31:52 +00:00
spastorelli 56c923c0ed Adapt site resolution/lookup to pass visitor unsigned claims in addition to token (#3202)
Co-authored-by: taranvohra <taranvohra@outlook.com>
2025-05-09 15:34:20 +05:30
Samy Pessé 7d7806df30 Passthrough SVG images in image resizing (#3224) 2025-05-08 18:57:50 +02:00
Nolann B. cb5598dc19 Handle invalid OpenAPI Responses (#3223) 2025-05-08 11:54:46 +02:00
Greg Bergé 47f01edcb8 Fix E2E icons (#3222) 2025-05-08 13:04:04 +05:30
Greg Bergé e6ddc0f07f Fix sitemap URLs (#3220) 2025-05-08 08:54:28 +02:00
Zeno Kapitein 373f18f451 Prevent section group popovers from opening on click (#3213) 2025-05-07 13:57:58 +00:00
Taran Vohra 3f292065cd Update site redirect regex for validation (#3216) 2025-05-07 12:42:28 +05:30
Greg Bergé 5e975ab95b Fix code highlighting for HTTP (#3214) 2025-05-06 13:41:15 +00:00
Greg Bergé a3ec264764 Fix Python code sample "null vs None" (#3215) 2025-05-06 15:40:56 +02:00
Samy Pessé 5d504ffa4c Fix resolution of links in reusable content (#3212) 2025-05-06 10:30:55 +02:00
Samy Pessé 04999662db Use absolute URLs in sitemap.xml when pointing to sitemap-pages.xml (#3210) 2025-05-05 09:03:28 +02:00
Zeno Kapitein f7a34706c7 Allow high-contrast input colors to be used as-is (#3209) 2025-05-02 19:41:05 +02:00
Taran Vohra f328a41982 Revert getPublishedContentByUrl timeout back to 10s (#3208) 2025-05-02 11:16:31 +00:00
Nolann B. 20ebecb114 Missing top-level required OpenAPI alternatives (#3207) 2025-05-02 11:48:15 +02:00
Taran Vohra 5a69692f54 Temporarily bump getPublishedContentByUrl timeout to 30s on v1 (#3199) 2025-04-30 19:14:09 +05:30
Nolann B. 42ca7e1580 Fix openapi CR preview (#3197) 2025-04-30 11:44:39 +02:00
Nolann B. c4ebb3fecd Fix openapi-select hover in responses (#3196) 2025-04-30 11:27:03 +02:00
Zeno Kapitein 2a805cc082 Change OpenAPI schema-optional from info to tint color (#3194) 2025-04-29 19:30:11 +02:00
Zeno Kapitein 89a5816ee4 Fix OpenAPI disclosure label ("Show properties") misalignment on mobile (#3193) 2025-04-29 19:29:56 +02:00
Zeno Kapitein c3f6b8c003 Reduce chroma of text layers (#3188) 2025-04-29 19:29:42 +02:00
Zeno Kapitein 0e201d5899 Add border to filled sidebar on gradient theme (#3185) 2025-04-29 19:29:32 +02:00
Zeno Kapitein ae5f1abc58 Change Dropdowns to use Radix's DropdownMenu (#3189) 2025-04-29 18:30:47 +02:00
Nolann B. 580101d04e Schemas disclosure label causing client error (#3192) 2025-04-29 16:16:58 +02:00
Johan Preynat 81b4a4db53 Allow /~gitbook/image and /~gitbook/icon despite querystring parameters for SEO in robots.txt (#3191) 2025-04-28 12:07:34 +02:00
Steven H 8339e91e2b Fix images and other content refs in reusable content across spaces. (#3190) 2025-04-25 15:28:09 +01:00
Steven H 3119066728 Support for reusable content across spaces. (#3173) 2025-04-24 16:54:11 +01:00
Zeno Kapitein 326e28e9b0 Restyle openapi blocks (#3169) 2025-04-24 16:53:29 +02:00
Johan Preynat 90f0127ada Use react@19 on v2 and v1 (#3184) 2025-04-24 16:53:23 +02:00
Viktor Renkema 0353a8186c Have all nodes within a text-cells adhere to alignments (#3175) 2025-04-24 11:43:20 +02:00
Steven H dd043df00f Revert investigation work around URL caches. (#3183) 2025-04-23 16:53:33 +01:00
Steven H 97b7c79bc6 Increase logging around caching behaviour causing page crashes. (#3182) 2025-04-23 15:04:00 +01:00
Steven H 634e0b4302 Improve error messages around undefined site sections. (#3179) 2025-04-23 11:47:28 +00:00
Greg Bergé 2a23f1f003 Improve performances of TOC (#3176) 2025-04-23 09:31:22 +02:00
Samy Pessé 88ffdcc303 Version Packages (#3172)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-22 11:20:21 +02:00
Nolann B. b6b09d42af Fix OpenAPI responses select placement and icon (#3174) 2025-04-22 10:59:42 +02:00
Nolann B. ebc39e9db9 Missing select icon (#3171) 2025-04-18 16:26:59 +02:00
Samy Pessé 0df3849ea2 Version Packages (#3157)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-18 13:40:34 +02:00
Greg Bergé 8ed1bda2f2 Translate OpenAPI blocks (#3166) 2025-04-18 13:29:55 +02:00
Nolann B. d67699a1f1 Add OpenAPI Webhook block (#3160) 2025-04-18 08:35:44 +02:00
Greg Bergé 3363a18856 Merge simple alternatives (#3165) 2025-04-16 09:14:12 +00:00
Amrit Kahlon ad1dc0b914 Bump scalar version (#3161)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-04-15 18:00:07 -07:00
Samy Pessé e4c7307c99 Fix CORS error when loading fonts in 2c (#3163) 2025-04-14 17:05:31 -07:00
Samy Pessé f9c942c8ff Fix CORS error when loading icons in 2c (#3162) 2025-04-14 16:52:39 -07:00
Greg Bergé eeb977fc03 Fix Python JSON code example (#3159) 2025-04-14 19:50:40 +00:00
Greg Bergé 4b8a62122a Show sections tabs only if there is at least two sections (#3158) 2025-04-14 14:09:34 +02:00
Addison e44e947fc1 Update cookie banner to use American English (#3154) 2025-04-14 18:43:01 +09:00
Nolann B. 7588cfe1c5 Improve OpenAPIResponses examples and schemas (#3123) 2025-04-14 11:16:35 +02:00
Greg Bergé a9a643f222 Improve icon stabilization (#3147) 2025-04-13 05:41:47 +00:00
Samy Pessé 77d6cb75be Improve error message when image resizing failed (#3148) 2025-04-12 17:44:23 -07:00
Samy Pessé 1942d221df Bump the cache of getPublishedContentByUrl in v1 (#3146) 2025-04-12 07:18:26 -07:00
Greg Bergé 041601ed65 Upgrade Argos (#3145) 2025-04-12 15:48:51 +02:00
Steven H 5567281a85 Bump getPublishedContentByUrl cache. (#3144) 2025-04-11 17:13:01 +01:00
Samy Pessé 4c3753046b Cloudflare: fix image resizing (#3143) 2025-04-11 16:48:43 +02:00
Samy Pessé 89f8e16184 Cloudflare: resize images in v2 using direct fetch (#3141) 2025-04-11 15:18:44 +02:00
Samy Pessé a3e1125ced Hotfix for v1 crashes (#3139) 2025-04-10 22:00:41 +02:00
Samy Pessé 4dae8acfed Cloudflare: fix image resizing (#3138) 2025-04-10 19:30:15 +02:00
Samy Pessé bc081c4344 Fix cache revalidation for computed revisions (#3134) 2025-04-10 16:36:02 +02:00
Samy Pessé e4275b235d Version Packages (#3137)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 13:31:27 +02:00
Samy Pessé 77397cac82 Fix version of @gitbook/api referenced in cache-tags (#3136) 2025-04-10 13:30:28 +02:00
Samy Pessé b6a08bc679 Version Packages (#3084)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-10 13:18:22 +02:00
Samy Pessé 116575c811 Changeset for cache-tags (#3135) 2025-04-10 13:16:01 +02:00
Samy Pessé 4ba7bd5ce1 CF Preview URL: Extract worker URL and run visual tests (#3132) 2025-04-09 18:25:37 +02:00
Samy Pessé 223ab5377f Test cloudflare preview URL (#3130) 2025-04-09 17:24:38 +02:00
Samy Pessé ae43ca146c Fix preview URLs on Cloudflare env (#3129) 2025-04-09 16:00:21 +02:00
Taran Vohra 42342893d6 Fix incoming URL for requests that were proxied (#3127) 2025-04-09 19:10:14 +05:30
Nolann B. eedefdd5c9 Handle optional security headers (#3128) 2025-04-09 15:07:33 +02:00
Samy Pessé f5e152dd74 Fix hovering links in docs.gitbook.com (#3125) 2025-04-09 11:43:12 +02:00
Zeno Kapitein 95ea22d9c5 Cache AI Page Link summary (#3106)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-04-09 09:59:35 +02:00
Taran Vohra f23023ddfa Use the proxy request identifier for storing the VA cookie in proxy sites (#3091) 2025-04-09 12:49:22 +05:30
Samy Pessé da74077954 Remove proxy package (#3124) 2025-04-08 23:58:19 +02:00
Nolann B. 29aaba5949 Override Scalar's overscroll-behavior (#3120) 2025-04-08 19:16:46 +02:00
Zeno Kapitein daf41fc728 Footer design tweaks (#3109) 2025-04-08 18:44:57 +02:00
Zeno Kapitein b08eb1af59 Fix list of variants overflow (#3114) 2025-04-08 18:44:44 +02:00
Zeno Kapitein 8322cd3e35 Fix sections being displayed when there is only 1 (#3113) 2025-04-08 18:44:08 +02:00
Samy Pessé 444039c142 Fix opening of links when v1 is embedded in an iframe (#3122) 2025-04-08 18:43:06 +02:00
Samy Pessé 3b0eff7f71 Fix getRevisionPageByPath in v2 not encoding the path correctly (#3121) 2025-04-08 16:29:08 +00:00
Samy Pessé c765463e38 Fix ogimage generation crashing when site is using a custom WOFF2 font (#3119) 2025-04-08 16:44:28 +02:00
Samy Pessé e8ca25b67e Fix caching in Cloudflare version using R2 as a backend (#3118) 2025-04-08 13:37:07 +02:00
Brett Jephson 528eee3fc6 RND-548: add subscript and superscript support (#3110) 2025-04-08 11:32:12 +01:00
Steven H 168a4fa998 Add support for buttons to GitBook. (#3117) 2025-04-08 09:18:29 +01:00
Samy Pessé 3ea75adba4 Update turbo to 2.5.0 and fix cache config (#3116) 2025-04-08 09:56:05 +02:00
Nolann B. cbd768a095 Improve OpenAPI codesample (#3090) 2025-04-08 09:35:22 +02:00
Samy Pessé 2d01653a78 Fix ogimage on Cloudflare and resizing of ads image (#3107) 2025-04-08 01:29:34 +02:00
Nolann B. aa3357a57d Fix OpenAPISchemas description padding (#3115) 2025-04-07 21:54:33 +02:00
Samy Pessé de53946896 Sanitize the back URL used when rendering PDF (#3111) 2025-04-07 17:03:09 +02:00
Samy Pessé d51204148e Revert debugging of ogimage errors (#3105) 2025-04-06 13:33:48 +01:00
Samy Pessé 319761a39e Fix revalidation not working because of memoization (#3104) 2025-04-06 01:27:22 +02:00
Samy Pessé 7fbf01a348 Update opennext to 1.0.0-beta.0 (#3102) 2025-04-05 23:51:12 +02:00
Steven Hall c22490bd80 Prefer @vercel/og for icon generation. 2025-04-05 09:22:13 +01:00
Steven Hall 860fd6d02e Use system-ui font in icons. 2025-04-05 09:12:20 +01:00
Steven Hall c43c524804 Fix a typing issue using ArrayBuffer.detached 2025-04-05 09:06:17 +01:00
Steven H 66bb938d85 Use Inter font when generating icons to prevent ArrayBuffer re-use. (#3101) 2025-04-05 09:01:41 +01:00
Steven H 0e6d928496 Use Inter font when generating icons to prevent ArrayBuffer re-use. (#3100) 2025-04-05 08:47:56 +01:00
Steven H f07982d9f8 Investigate issue where icons and ogimage would occasionally fail. (#3099) 2025-04-05 08:15:02 +01:00
Steven H b92ecfad58 Implement retry logic for the DO cache to prevent when revalidating content. (#3098) 2025-04-04 23:44:32 +01:00
Zeno Kapitein 416bde70bc Link tooltips + AI link summaries (#3088)
Co-authored-by: Samy Pessé <samypesse@gmail.com>
2025-04-04 20:56:42 +02:00
Steven H 580f7ade15 Better debugging of GitBook v1s revalidate endpoint. (#3097) 2025-04-04 17:36:24 +01:00
Zeno Kapitein cdffd7c47b Desaturate text colours by decreasing chroma for the last steps of the color scale (#3096) 2025-04-04 18:00:01 +02:00
Greg Bergé 5a959cf041 Fix a regression on OpenAPI example bg (#3095) 2025-04-04 15:05:53 +00:00
Zeno Kapitein 31466546a2 Change overlay close icon to close (#3089) 2025-04-04 16:42:47 +02:00
Steven H 90ead98eaa Better error handling in revalidate v1 (#3094) 2025-04-04 14:59:14 +01:00
Samy Pessé 65e9cb9d5c Update next to "canary" for v2 (#3093) 2025-04-04 14:39:43 +02:00
Greg Bergé e59076a074 Improve OpenAPI schemas block ungrouped style (#3092) 2025-04-04 14:28:44 +02:00
Nolann B. 653920dec9 Add tests for generateSchemaExample (#3086) 2025-04-03 19:42:40 +02:00
Greg Bergé 23cedd2d8b Hide deprecated properties in examples (#3085) 2025-04-03 19:40:52 +02:00
Nolann B. 70c4182fd8 Improve OpenAPI schema style (#3079) 2025-04-03 16:10:20 +00:00
Samy Pessé b62b101065 Do not use insights cookie if disabled (#3087) 2025-04-03 17:30:43 +02:00
Zeno Kapitein b9c929beeb Update page breadcrumb color (for links-accent crumbs) (#3082) 2025-04-03 16:34:14 +02:00
Nolann B. 2b6c593289 Remove stable from x-stability (#3083) 2025-04-03 11:33:39 +02:00
Samy Pessé c0b339e406 Version Packages (#3075)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-02 13:36:04 +02:00
Nolann B. da485f5144 Fix read-only in generateSchemaExample (#3069) 2025-04-02 13:03:51 +02:00
Nolann B. 139a8050a5 Fix OpenAPI enum display (#3077) 2025-04-02 10:15:19 +02:00
Nolann B. da7b369ffd Fix missing headers in OpenAPIResponses (#3074) 2025-04-01 21:57:36 +02:00
Samy Pessé 432da64919 Version Packages (#3073)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-01 18:13:52 +02:00
Nolann B. 7d0b422200 Handle grouped OpenAPISchemas (#3071) 2025-04-01 17:24:43 +02:00
Zeno Kapitein fb90eb0acc Limit tinted background on bold theme to sites with filled sidebar (#3072) 2025-04-01 16:43:08 +02:00
Samy Pessé b647538e41 Version Packages (#3033)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-01 16:35:09 +02:00
Samy Pessé e50797a5c1 Adapt new computed content and add tag for translation (#3070) 2025-04-01 15:10:55 +02:00
Nolann B. 5b1e01c786 Support for x-stability property (#3064) 2025-03-31 13:49:37 +02:00
Nolann B. 813b2af06b Support for x-enumDescriptions and x-gitbook-enum (#3066) 2025-03-31 12:18:57 +02:00
Nolann B. 6f71da88d8 Fix padding in OpenAPI schemas (#3067) 2025-03-31 12:03:53 +02:00
Samy Pessé fa91eb7eaf Fix dark mode PDF (#3065) 2025-03-28 18:00:33 +01:00
Zeno Kapitein c756761591 Add breadcrumbs to search results (#2772) 2025-03-28 13:29:05 +01:00
Samy Pessé 434af90366 Fix image resizing when using proxy by passing signing identifier in site URL data (#3063)
Co-authored-by: Taran Vohra <taranvohra@outlook.com>
2025-03-28 12:28:12 +01:00
Valentino Hudhra 0f41e194cb Fix dismissed annoucement banner ignored when switching sites (#3057) 2025-03-28 10:56:18 +01:00
Nolann B. bd353489e9 Fix missing alternative schemas (#3058) 2025-03-28 06:53:28 +01:00
Samy Pessé 77fd393f15 Fix click on search items when embedded in an iframe and track announcement links (#3061) 2025-03-27 20:13:14 +01:00
Taran Vohra 8701b5e078 Use the site canonical URL when rewriting in the middleware (#3060) 2025-03-27 19:39:39 +01:00
Nolann B. 61db166f92 Add OpenAPI write-only indicator (#3059) 2025-03-27 18:21:38 +01:00
Zeno Kapitein 0d9b9cea0f Fix announcement banner margin (#3056) 2025-03-27 15:59:47 +00:00
Nolann B. 7bb37c7f0a Move filterSelectedOpenAPISchemas to @gitbook/openapi-parser (#3054) 2025-03-27 15:53:41 +01:00
Taran Vohra 5b2bf828e3 Use stable site URL data for route rewrite in the middleware (#3053) 2025-03-27 18:26:06 +05:30
Samy Pessé c85d3f9752 Serve a markdown version of the page when adding a .md extension (#3055)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-03-27 12:35:18 +00:00
Zeno Kapitein 4d7088bbf0 Stop variant selector from growing too large (#3043) 2025-03-27 10:42:18 +01:00
Samy Pessé dd6ab6044c Fix crash when using user mention or accessing invalid change requests (#3051) 2025-03-27 01:04:00 +01:00
Samy Pessé d236bf029c Fix flash for site with dark default mode (#3050) 2025-03-26 18:12:45 +01:00
Zeno Kapitein d70d566abe Support site announcement banner (#3008)
Co-authored-by: Valentino Hudhra <v.hudhra@gmail.com>
2025-03-26 16:37:05 +01:00
Nolann B. a25fdedea9 Replace $ref with $reference in json-decycle (#3049) 2025-03-26 14:16:03 +00:00
Nolann B. 373183a2cd Safe parse OpenAPI JSON schema (#3046) 2025-03-26 13:55:22 +01:00
Samy Pessé e9fa50dfb5 Trim search query before doing search (#3042) 2025-03-26 10:18:18 +00:00
Samy Pessé 57ca4e0e88 Fix crash when integration is not installed on the space (#3040) 2025-03-26 10:19:57 +01:00
Samy Pessé bc90adb73a Allow indexation of images to ensure favicons display on Google (#3041) 2025-03-26 10:17:57 +01:00
Nolann B. 1505ddbd5a Fix multiple request examples selector not showing (#3039) 2025-03-25 20:48:57 +01:00
Greg Bergé cd99ed57fb Fix OpenAPI spec rendering (#3037) 2025-03-25 15:13:18 +00:00
Samy Pessé 27de1cd01d Default to the first ID as being active in scroll detection (#3036) 2025-03-25 15:39:45 +01:00
Nolann B. ae78fc5394 Fix XML in code sample (#3030) 2025-03-25 15:11:45 +01:00
Samy Pessé 40e8e69c6d Disallow crawling by web-robots of search/ask URLs (#3035) 2025-03-25 13:51:09 +00:00
Nolann B. e84a46a558 Fix OpenAPI tabs indicator overflow (#3032) 2025-03-25 10:03:58 +01:00
Samy Pessé 18a72d4466 v2: improve caching (#3031) 2025-03-25 09:40:54 +01:00
Zeno Kapitein d7aa5fa003 Remove loading state for recommended questions (#3029) 2025-03-24 19:19:12 +01:00
Samy Pessé 27532a5f41 Version Packages (#2988)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-24 17:43:16 +01:00
Nolann B. 48c18c03b9 Add option to pass scalar plugins (#3027) 2025-03-24 14:11:14 +01:00
Samy Pessé bba2e52e24 Fix support for site redirects that include section/variant paths (#3024) 2025-03-24 10:21:26 +01:00
Valentino Hudhra 54ee0149e1 Add support for custom fonts (#2981) 2025-03-24 08:20:58 +01:00
Greg Bergé 8fda9d2fab Update Argos (#3025) 2025-03-22 23:00:25 +01:00
Samy Pessé 77a72d79ce Update next.js to latest v14 and v15 (#3026) 2025-03-22 20:02:15 +01:00
Nolann B. 73e2b472ef Fix write only properties in request example (#3023) 2025-03-21 19:09:25 +01:00
Samy Pessé 2f28cc1323 v2: improve link and navigation between content in a same site (#3020) 2025-03-21 18:18:34 +01:00
Greg Bergé f5516d4e21 Improve OpenAPI TOC display (#3022) 2025-03-21 14:29:12 +00:00
Greg Bergé d2facb2db1 Mark OpenAPI properties as optional if not required (#3021) 2025-03-21 14:28:53 +00:00
Greg Bergé 6eae764b7c Add support for multiple body content types / examples (#2991) 2025-03-21 14:23:42 +01:00
Greg Bergé 970ef8f886 Add missing token (#3019) 2025-03-21 11:04:57 +01:00
Nolann B. ed0720699e Fix OpenAPI path overflow on mobile (#3018) 2025-03-21 11:02:32 +01:00
Greg Bergé ad94d00d5f Improve markdown parsing (#3017) 2025-03-21 10:53:01 +01:00
Greg Bergé c7843f7f52 Remove bun cache (#3016) 2025-03-21 10:42:57 +01:00
Greg Bergé 1fe3286bdb Fix OpenAPI overflow issue (#3015) 2025-03-21 10:42:09 +01:00
Samy Pessé ce030fdf76 v2: Fix redirect to strip jwt_token in url-host mode (#3014) 2025-03-21 00:46:39 +01:00
Samy Pessé 9ba8783952 v2: Fix the redirection when stripping the jwt_token from url (#3013) 2025-03-21 00:25:34 +01:00
Greg Bergé 2f3966cc53 Fix icons flaky (#3012) 2025-03-20 21:08:46 +00:00
Samy Pessé 8f65a22e13 Accept API token in query parameters for normal v2 routes (#3011) 2025-03-20 19:54:55 +01:00
Greg Bergé 2787d884cd Stabilize images & icons (#3009) 2025-03-20 19:40:42 +01:00
Nolann B. fc00b51683 Remove default value in generateSchemaExample (#3007) 2025-03-20 17:24:23 +01:00
Samy Pessé f80c3a7b37 v2: Fix display of integration blocks (#3010) 2025-03-20 15:56:31 +00:00
Samy Pessé 4e5d6c7487 v2: use static route for VA / adaptive content (#2998) 2025-03-20 15:41:17 +01:00
Greg Bergé 96f84b5917 Fix visual tests v2 (#3006) 2025-03-20 11:34:04 +01:00
Greg Bergé 6b8a5fdd1b Trigger tests (#3005) 2025-03-20 10:00:09 +00:00
Greg Bergé 89083ac790 Stabilize visual tests (#3001) 2025-03-20 09:22:09 +01:00
Nolann B. 70be2c6b2b Stringify default value (#3003) 2025-03-19 21:28:40 +01:00
Samy Pessé bcf877841b Update to latest version of the GitBook API (#3002) 2025-03-19 20:11:44 +01:00
Nolann B. a84b06bb19 Update resolveDescription and add minItems/maxItems (#3000) 2025-03-19 17:21:35 +01:00
Zeno Kapitein 24b7808d9a Fix prominent search bar width on md screens (#2996) 2025-03-19 17:14:07 +01:00
Greg Bergé 8364ee988e Fix Argos flakies (#2999) 2025-03-19 16:04:45 +01:00
Samy Pessé 07f6e2d233 Pass page.computedSeed to getComputedDocument (#2995) 2025-03-19 15:02:49 +01:00
Greg Bergé 7212973f34 Update Scalar client (#2993) 2025-03-18 16:02:40 +01:00
Greg Bergé af44e0436b fix typo (#2992) 2025-03-18 14:20:23 +01:00
Greg Bergé 3f9c35d505 Fix emoji display on other platforms than Mac OS (#2990) 2025-03-17 17:49:13 +01:00
Zeno Kapitein 2c13f8b95c Fix horizontal overscroll issue on large screens (#2989) 2025-03-17 15:38:44 +01:00
Zeno Kapitein 4dab1c56e5 Fix alignment of prominent search bar on full-width pages (#2987) 2025-03-17 15:02:23 +01:00
Zeno Kapitein 4723f03ef1 Restyle section group dropdown (#2985) 2025-03-17 13:22:54 +01:00
Samy Pessé 278689e0a0 Version Packages (#2984)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-17 11:06:18 +01:00
Nolann B. 886e204e68 Update OpenAPI operation path design (#2972) 2025-03-17 10:18:24 +01:00
Greg Bergé c59947ab39 Remove Sentry (#2986) 2025-03-17 09:00:58 +01:00
Zeno Kapitein 4f0a772a21 Override tint lightness if supplied color is out of bounds (#2983) 2025-03-14 18:24:25 +01:00
Samy Pessé 903f962673 Version Packages (#2964)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-14 18:01:38 +01:00
Nolann B. 31d800e48b Changeset for OpenAPI schemas (#2982) 2025-03-14 17:08:21 +01:00
Nolann B. c1c58eb919 Render OpenAPISchemas block (#2959) 2025-03-14 16:24:09 +01:00
Zeno Kapitein eec3eed100 Support prominent search bar style (#2936)
Co-authored-by: Nolann Biron <biron.nolann@gmail.com>
2025-03-14 15:11:21 +01:00
Samy Pessé 99da8df3d1 Resize and optimize ogimage and favicons (#2980) 2025-03-13 18:45:14 +01:00
Steven H c9ea23919e Fix an issue where iframe aspect ratio was not respected. (#2979) 2025-03-13 14:23:50 +00:00
Zeno Kapitein 16292def79 Display sidebar on no-TOC pages (#2942) 2025-03-13 10:28:12 +01:00
Taran Vohra 9108c56ca1 Fix an issue where ContentKit webframes weren't sized properly (#2978) 2025-03-13 13:29:29 +05:30
Samy Pessé b011ea0af8 Simplify deferred codeblock highlighting and fix streaming (#2977) 2025-03-13 00:25:48 +01:00
spastorelli d33171b779 Fix getVisitorAuthCookieMaxAge returning 60s even when token expiry is much later (#2974) 2025-03-12 16:17:16 +01:00
Steven H 6aaeae269e Fix an issue where ContentKit iframes were not resizing. (#2967) 2025-03-12 14:52:27 +00:00
Samy Pessé e5f0cc8663 Respond with all tags of all cache used for page generation (#2973) 2025-03-12 10:30:47 +01:00
Nolann B. 9b5f971aa1 Transparent background for OpenAPI path block (#2963) 2025-03-11 13:32:35 +00:00
Nolann B. ff3b708ad3 Remove read-only properties from codesample (#2965) 2025-03-11 13:32:10 +00:00
Nolann B. e996904013 Update target in shiki javascript engine (#2968) 2025-03-11 14:21:07 +01:00
Samy Pessé f32bf1f15d Use API cache endpoint and service binding in Cloudflare (#2952) 2025-03-11 13:06:06 +01:00
Zeno Kapitein 6cda2dd57b Fix hint hover style (#2966) 2025-03-11 11:00:28 +00:00
Nolann B. c60e9ba27f Handle OpenAPI read-only properties (#2962) 2025-03-11 11:37:11 +01:00
Zeno Kapitein 9bc3d504c8 Info hint background and link color fixes (#2953) 2025-03-11 11:30:28 +01:00
Greg Bergé b5e4fd75e9 Upgrade shiki and bring back support to C/C++ (#2961) 2025-03-11 10:53:51 +01:00
Samy Pessé a92cb49445 Version Packages (#2958)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-11 09:38:39 +00:00
Greg Bergé 88f64b0d63 Support Node.js v20 with @gitbook/icons (#2960) 2025-03-11 09:27:26 +00:00
Nolann B. 844059f9a5 Fix spacing in OpenAPISecurities (#2954) 2025-03-11 09:37:34 +01:00
Nolann B. eaf2d68308 OpenAPI operation title fallback in sections (#2955) 2025-03-11 09:37:00 +01:00
Nolann B. 12bdbf988d Fix react-openapi tests (#2956) 2025-03-11 09:20:24 +01:00
Samy Pessé 29bab79005 v2: PDF export for sites and spaces (#2949) 2025-03-10 00:08:33 +01:00
Samy Pessé 8d5de97ddf v2: fix fallback and VA redirects (#2948) 2025-03-08 11:21:49 +01:00
Samy Pessé d9b5a898ab Version Packages (#2947)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-08 00:29:16 +01:00
Nolann B. f127d28675 Rename OpenAPIModels to OpenAPISchemas (#2946) 2025-03-07 22:28:37 +01:00
Samy Pessé 65c1bdc455 Version Packages (#2945)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-07 19:37:41 +01:00
Samy Pessé 0711a171d4 Fix deployment of 2c to production 2025-03-07 18:26:51 +01:00
Samy Pessé c58c551f9e Simplify envs management for deployments (#2943) 2025-03-07 18:17:44 +01:00
Greg Bergé f574858b9b Fix OpenAPI example (#2944) 2025-03-07 17:54:35 +01:00
Samy Pessé 3e1592e16b Fix configuration for v2 staging (#2940) 2025-03-07 14:46:45 +01:00
Samy Pessé 90604c4c3b Version Packages (#2917)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-07 14:03:59 +01:00
Claire Chabas 9ffc3b606d Fix content overflowing out of their container in lists (#2939) 2025-03-07 11:19:02 +01:00
Samy Pessé e00c87d3e6 Output a x-gitbook-route-type header to distinguish static/dynamic (#2938) 2025-03-07 10:29:50 +01:00
Nolann B. bb3ca9c165 Implement OpenAPI models blocks (#2908) 2025-03-07 09:44:14 +01:00
Samy Pessé 8ee9757132 v2: cleanup deployment to Cloudflare (#2937) 2025-03-07 09:15:43 +01:00
Samy Pessé 946a463979 v2: Fix deployment to Cloudflare and improve CI (#2935) 2025-03-07 00:01:07 +01:00
Samy Pessé 7aad9f7ff8 v2: render code block with syntax highlighting on the server (#2932) 2025-03-06 15:07:06 +01:00
Greg Bergé 3fe8f7c219 Fix circular refs OpenAPI (#2934) 2025-03-06 10:32:49 +00:00
Greg Bergé 0278a14e8e Upgrade Scalar client & fix opening endpoint (#2933) 2025-03-06 11:28:51 +01:00
Samy Pessé e5366d3a49 v2: fix error handling in middleware and data fetcher (#2925) 2025-03-06 10:53:17 +01:00
Samy Pessé 38e84ffe1a Persist visitor auth token in cookie for as long as the token is valid (#2931) 2025-03-06 09:45:25 +01:00
Greg Bergé 052e07a936 Support references pointing to invalid files (#2930) 2025-03-05 21:37:46 +01:00
Samy Pessé 7e67ebc11d Fix condition for public content cache (#2929) 2025-03-05 21:22:22 +01:00
Samy Pessé c9dea94717 V2: Add back redirect for trailing slash (#2928) 2025-03-05 21:06:33 +01:00
Samy Pessé 1344754ca9 v2: enable skipTrailingSlashRedirect in the middleware 2025-03-05 19:27:48 +01:00
Samy Pessé 0f4759fb89 Fix condition for VERCEL_URL 2025-03-05 16:30:59 +01:00
Samy Pessé 6d77b647a4 Skip requests to assets/main/vercel hosts in middleware (#2927) 2025-03-05 16:22:20 +01:00
Zeno Kapitein 5907bd9af4 Adjust hint spacing (#2873) 2025-03-05 15:59:58 +01:00
Samy Pessé b66d04c9c5 v2: fix resolution of revalidation requests (#2926) 2025-03-05 14:48:07 +01:00
Nolann B. 3173d8ec2d Remove top level circular refs in alternatives (#2924) 2025-03-05 14:08:34 +01:00
Samy Pessé 31e381035e Dynamic routing for v2: customization preview and visitor auth (#2922) 2025-03-05 11:25:44 +01:00
Greg Bergé 259074b81f Fix OpenAPI spec recursion (#2920) 2025-03-05 09:25:23 +01:00
Samy Pessé 9a2f5b9295 Fix host used for image resizing in v2 (#2923) 2025-03-04 23:42:15 +01:00
Greg Bergé 58cd70b0c1 Put back logs (#2919) 2025-03-04 20:33:29 +00:00
Samy Pessé 821899a767 Fix image resizing in v2 when url is being passed as x-gitbook-url (#2921) 2025-03-04 18:57:49 +01:00
Samy Pessé bfcc119ff3 Remove support for x-gitbook-host in v2 (#2918) 2025-03-04 18:36:43 +01:00
Samy Pessé 76c797498e Implement route to invalidate data cache (#2916) 2025-03-04 13:52:02 +01:00
Samy Pessé 13514bf6fa Version Packages (#2907)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-04 13:42:32 +01:00
Samy Pessé 05ffd0e321 Proper data cache tagging for computed content (#2915) 2025-03-04 12:26:09 +01:00
Samy Pessé 701eaad92a Fix use of server actions with proxied domains by using middleware (#2913) 2025-03-04 10:00:08 +01:00
Greg Bergé c66ef5dd10 Fix OpenAPI tabs selection (#2914) 2025-03-03 21:10:24 +00:00
Greg Bergé f774f9b3a0 Fix all tabs behaviour in OpenAPI blocks (#2912) 2025-03-03 17:11:54 +00:00
Samy Pessé 29dc7ccb1a Fix next.js configuration for serverActions 2025-03-03 17:05:46 +01:00
Samy Pessé 80237c34f9 Allow all origins (including sub-domains) for server actions 2025-03-03 16:54:16 +01:00
Samy Pessé 979233c5c0 Allow all origins for serverActions in v2 2025-03-03 16:21:05 +01:00
Samy Pessé 2f03e6acee Define GITBOOK_URL as serverActions.allowedOrigins (#2911) 2025-03-03 15:24:31 +01:00
Greg Bergé c492f09a19 Fix outline not visible on OpenAPI blocks (#2910) 2025-03-03 14:56:19 +01:00
Greg Bergé 2c3af5e73c Improve scrolling with lot of highlighted blocks (#2909) 2025-03-03 13:47:30 +00:00
Samy Pessé 6597f8508d V2: search and ask AI (#2889) 2025-03-03 11:53:32 +01:00
Nolann B. 8beb5d6bcf Add input elements to ContentKit (#2882) 2025-02-28 17:50:20 +00:00
Greg Bergé 53f5dbea26 Fix typing of shouldIgnoreEntity (#2906) 2025-02-28 17:38:58 +00:00
Samy Pessé 725952a106 Version Packages (#2881)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-28 17:22:32 +00:00
Greg Bergé 3319375e9a Support OpenAPI operation block (#2902) 2025-02-28 18:12:08 +01:00
spastorelli c5a4619020 Fix multi-id/preview mode not using context id for api cache calls (#2905) 2025-02-28 18:04:16 +01:00
Greg Bergé 989fc69abe Format package.json after running changeset (#2904) 2025-02-28 15:56:44 +00:00
Greg Bergé 0924259217 Expose OpenAPI helper shouldIgnoreEntity (#2903) 2025-02-28 16:22:20 +01:00
Taran Vohra 05affac34f Add support for proxy sites using x-gitbook-url in v2 (#2901) 2025-02-28 20:03:05 +05:30
Samy Pessé 453a459566 Fix content ref resolution for pages of other spaces (#2900) 2025-02-28 13:00:54 +01:00
Nolann B. 722f02ea09 Fix recursion in OpenAPISchemaAlternative (#2892) 2025-02-28 10:07:56 +00:00
Samy Pessé 5bcea2fe3b Fix signature verification with image resizing on v1 (#2899) 2025-02-28 10:36:10 +01:00
Samy Pessé 73afd1f5d0 Improve error message for image resizing (#2898) 2025-02-28 10:23:17 +01:00
Greg Bergé 9b8a0d3e54 Fix CI tests (#2897) 2025-02-28 10:14:09 +01:00
Samy Pessé 75bd98d66c V2: robots.txt, sitemap.xml, image resizing, etc (#2895) 2025-02-28 09:52:56 +01:00
Taran Vohra 9b914d10bd Fix getProxyModeBasePath (#2896) 2025-02-28 12:27:25 +05:30
Brett Jephson 3e11678d8d fix: section groups went missing (#2891) 2025-02-28 00:00:39 +01:00
Samy Pessé 6e03638130 Remove loading.tsx for static rendering in v2 (#2894) 2025-02-27 18:23:06 +01:00
Greg Bergé 23f884efb7 Fix the fonts display on the site (#2893) 2025-02-27 17:27:17 +01:00
Taran Vohra 2ae76f999b Change how a site in proxy mode is resolved (#2890) 2025-02-27 21:43:29 +05:30
Samy Pessé 685325cdc7 Deploy v2 to staging from main using a composite action (#2888) 2025-02-27 11:12:02 +01:00
Samy Pessé ab132eadb7 Cache OpenAPI using next "use cache" for v2 (#2887) 2025-02-27 09:39:08 +01:00
Samy Pessé 14504e43b5 V2: embeds and CSP (#2885) 2025-02-27 08:44:32 +01:00
Taran Vohra 4ed79574e8 change proxy mode site lookup url (#2886) 2025-02-27 12:38:55 +05:30
Samy Pessé 31bcd1a386 Improve detection of code path not supported in v2, add visual tests (#2884) 2025-02-26 20:18:38 +01:00
Samy Pessé 270e5ef7ad Improve linking to other sections/variants in development (#2883) 2025-02-26 19:14:27 +01:00
Samy Pessé 1f4733355e Remove eslint/prettier and switch to biome (#2880) 2025-02-26 17:59:34 +01:00
Zeno Kapitein 027a859ef4 Add link styles (#2861) 2025-02-26 13:03:14 +00:00
Samy Pessé 59f50eaee7 Fix production deployments to Vercel (#2879) 2025-02-26 13:47:24 +01:00
Samy Pessé 80d2733c25 Version Packages (#2859)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-26 13:41:20 +01:00
Samy Pessé feb6735a4d Abstract data fetching between v1 and v2 (#2865) 2025-02-26 13:20:16 +01:00
Nolann B. f1d1d2fd81 Return empty string if no server provided (#2878) 2025-02-26 09:13:27 +01:00
Greg Bergé a749c67803 Extract and remove unused code around OpenAPI servers (#2867) 2025-02-25 17:39:27 +01:00
Nolann B. c808bb1483 Fallback to untrusted validation in case JSON OpenAPI spec is invalid (#2877) 2025-02-25 17:27:31 +01:00
Greg Bergé e24206ebf0 Remove dependency to swagger2openapi and yaml (#2874) 2025-02-25 15:42:11 +01:00
Greg Bergé 3410785a3a Remove .tsbuildinfo (#2875) 2025-02-25 15:24:12 +01:00
Nolann B. b4a12d606e Fix circularRef in schema + examples OpenAPI (#2872) 2025-02-24 21:26:26 +01:00
Greg Bergé a0545545d7 Faster OpenAPI spec validation (#2871) 2025-02-24 21:21:27 +01:00
Zeno Kapitein 1f11650ca1 Bump hint icon up 1px (#2869) 2025-02-24 16:21:19 +01:00
Greg Bergé da55facf2c Support GitBook blocks in OpenAPI operation description (#2868) 2025-02-24 13:44:48 +01:00
Nolann B. dc2dbc5710 Update OpenAPI code examples to support multiple content-type (#2843) 2025-02-24 13:07:35 +01:00
Nolann B. bdd6303bcc Rearrange item types in OpenAPI blocks (#2862) 2025-02-24 11:33:47 +01:00
Zeno Kapitein 66d0fc0683 Update hint block (#2840)
Co-authored-by: Valentino Hudhra <v.hudhra@gmail.com>
2025-02-21 15:32:35 +01:00
Nolann B. 05e1d8cd96 Hide x-gitbook-* symbols in OpenAPI blocks (#2863) 2025-02-21 12:38:55 +01:00
Greg Bergé 9f0de74caa Add support for new OpenAPI ref (#2860) 2025-02-20 14:31:04 +01:00
spastorelli a820739bd2 Remove ununsed search api lib methods (#2855) 2025-02-20 09:46:12 +01:00
706 changed files with 37799 additions and 18395 deletions
-1
View File
@@ -1 +0,0 @@
1.2.1
+3
View File
@@ -0,0 +1,3 @@
# Changes to the API data cache functions can invalidate all existing data cache
# causing a massive amount of revalidation, impacting our API.
packages/gitbook/src/lib/data/api.ts @SamyPesse
+29 -6
View File
@@ -53,19 +53,42 @@ After forking this repository, you'll want to [create a branch](https://docs.git
#### 3. Install dependencies and run the project locally
GitBook uses [Bun](https://bun.sh/) to run the project. Make sure you're using the specified version of `node` before running any of the development commands to ensure a smooth development experience.
##### Prerequisites:
- Node.js (Version: >=20.6)
- Use `nvm` for easy Node management
- [Bun](https://bun.sh/) (Version: >=1.2.15)
- We use a text-based lockfile which isn't supported below 1.2.15
You can easily do this by running the command `nvm use`.
##### Setup steps:
To start your local version of GitBook, run the command `bun dev`.
1. Ensure you are using the project's version of Node:
```bash
nvm use
```
2. Install dependencies using Bun:
```bash
bun install
```
3. Start the development server:
```bash
bun dev
```
Additional development commands:
- `bun format`: Format the code using Biome
- `bun typecheck`: Run TypeScript type checking
- `bun unit`: Run unit tests
- `bun e2e`: Run end-to-end tests
#### 4. Preview your changes
When running the development server, published GitBook sites can be rendered through your local version at `http://localhost:3000/`.
When running the development server, published GitBook sites can be rendered through your local version at `http://localhost:3000/url`.
For example, our published docs can be viewed using the local version by visiting `http://localhost:3000/docs.gitbook.com` after running the development server.
For example, our published docs can be viewed using the local version by visiting `http://localhost:3000/url/gitbook.com/docs` after running the development server.
You can visit any published GitBook site behind your development server. Please make sure your site is [published publicly](https://docs.gitbook.com/published-documentation/publish-your-content-as-a-docs-site) to ensure you can view the site correctly in your development version.
You can visit any published GitBook site behind your development server. Please make sure your site is [published publicly](https://gitbook.com/docs/published-documentation/publish-your-content-as-a-docs-site) to ensure you can view the site correctly in your development version.
### Commit your update
@@ -0,0 +1,83 @@
name: Gradual Deploy to Cloudflare
description: Use gradual deployment to deploy to Cloudflare. This action will upload the middleware and server versions to Cloudflare and kept them bound together
inputs:
apiToken:
description: 'Cloudflare API token'
required: true
accountId:
description: 'Cloudflare account ID'
required: true
environment:
description: 'Cloudflare environment to deploy to (staging, production, preview)'
required: true
middlewareVersionId:
description: 'Middleware version ID to deploy'
required: true
serverVersionId:
description: 'Server version ID to deploy'
required: true
outputs:
deployment-url:
description: "Deployment URL"
value: ${{ steps.deploy_middleware.outputs.deployment-url }}
runs:
using: 'composite'
steps:
- id: wrangler_status
name: Check wrangler deployment status
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: deployments status --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
# This step is used to get the version ID that is currently deployed to Cloudflare.
- id: extract_current_version
name: Extract current version
shell: bash
run: |
version_id=$(echo "${{ steps.wrangler_status.outputs.command-output }}" | grep -A 3 "(100%)" | grep -oP '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
echo "version_id=$version_id" >> $GITHUB_OUTPUT
- id: deploy_server
name: Deploy server to Cloudflare at 0%
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: versions deploy ${{ steps.extract_current_version.outputs.version_id }}@100% ${{ inputs.serverVersionId }}@0% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
# Since we use version overrides headers, we can directly deploy the middleware to 100%.
- id: deploy_middleware
name: Deploy middleware to Cloudflare at 100%
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: versions deploy ${{ inputs.middlewareVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc
- name: Deploy server to Cloudflare at 100%
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: versions deploy ${{ inputs.serverVersionId }}@100% -y --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
- name: Outputs
shell: bash
env:
DEPLOYMENT_URL: ${{ steps.deploy_middleware.outputs.deployment-url }}
run: |
echo "URL: ${{ steps.deploy_middleware.outputs.deployment-url }}"
@@ -0,0 +1,138 @@
name: 'Deploy cloudflare'
description: 'Deploy GitBook to Cloudflare'
inputs:
opItem:
description: '1Password item to load secrets from'
required: true
opServiceAccount:
description: '1Password service account token'
required: true
apiToken:
description: 'Cloudflare API token'
required: true
accountId:
description: 'Cloudflare account ID'
required: true
environment:
description: 'Cloudflare environment to deploy to (staging, production, preview)'
required: true
deploy:
description: 'Deploy as main version for all traffic instead of uploading versions'
required: true
commitTag:
description: 'Commit branch to associate with the deployment'
required: true
commitMessage:
description: 'Commit message to associate with the deployment'
required: true
outputs:
deployment-url:
description: "Deployment URL"
value: ${{ steps.upload_middleware.outputs.deployment-url }}
runs:
using: 'composite'
steps:
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
shell: bash
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Load secret
uses: 1password/load-secrets-action@v2
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ inputs.opServiceAccount }}
GITBOOK_URL: ${{ inputs.opItem }}/GITBOOK_URL
GITBOOK_ICONS_URL: ${{ inputs.opItem }}/GITBOOK_ICONS_URL
GITBOOK_ICONS_TOKEN: ${{ inputs.opItem }}/GITBOOK_ICONS_TOKEN
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ inputs.opItem }}/NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
GITBOOK_SECRET: ${{ inputs.opItem }}/GITBOOK_SECRET
GITBOOK_APP_URL: ${{ inputs.opItem }}/GITBOOK_APP_URL
GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL
GITBOOK_API_PUBLIC_URL: ${{ inputs.opItem }}/GITBOOK_API_PUBLIC_URL
GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
- name: Build worker
run: bun run turbo build:cloudflare
env:
GITBOOK_RUNTIME: cloudflare
shell: bash
- name: Upload the DO worker
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: deploy --config ./packages/gitbook/openNext/customWorkers/doWrangler.jsonc
- id: upload_server
name: Upload server to Cloudflare
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook/openNext/customWorkers/defaultWrangler.jsonc
- name: Extract server version worker ID
shell: bash
id: extract_server_version_id
run: |
version_id=$(echo '${{ steps.upload_server.outputs.command-output }}' | grep "Worker Version ID" | awk '{print $4}')
echo "version_id=$version_id" >> $GITHUB_OUTPUT
- name: Run updateWrangler scripts
shell: bash
run: |
bun run ./packages/gitbook/openNext/customWorkers/script/updateWrangler.ts ${{ steps.extract_server_version_id.outputs.version_id }}
- id: upload_middleware
name: Upload middleware to Cloudflare
uses: cloudflare/wrangler-action@v3.14.0
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
workingDirectory: ./
wranglerVersion: '4.10.0'
environment: ${{ inputs.environment }}
command: ${{ format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook/openNext/customWorkers/middlewareWrangler.jsonc
- name: Extract middleware version worker ID
shell: bash
id: extract_middleware_version_id
run: |
version_id=$(echo '${{ steps.upload_middleware.outputs.command-output }}' | grep "Worker Version ID" | awk '{print $4}')
echo "version_id=$version_id" >> $GITHUB_OUTPUT
- name: Deploy server and middleware to Cloudflare
if: ${{ inputs.deploy == 'true' }}
uses: ./.github/actions/gradual-deploy-cloudflare
with:
apiToken: ${{ inputs.apiToken }}
accountId: ${{ inputs.accountId }}
opServiceAccount: ${{ inputs.opServiceAccount }}
opItem: ${{ inputs.opItem }}
environment: ${{ inputs.environment }}
serverVersionId: ${{ steps.extract_server_version_id.outputs.version_id }}
middlewareVersionId: ${{ steps.extract_middleware_version_id.outputs.version_id }}
deploy: ${{ inputs.deploy }}
- name: Outputs
shell: bash
env:
DEPLOYMENT_URL: ${{ steps.upload_middleware.outputs.deployment-url }}
run: |
echo "URL: ${{ steps.upload_middleware.outputs.deployment-url }}"
echo "Output server: ${{ steps.upload_server.outputs.command-output }}"
@@ -0,0 +1,80 @@
name: 'Deploy vercel'
description: 'Deploy GitBook to Vercel'
inputs:
vercelOrg:
description: 'Vercel organization'
required: true
vercelProject:
description: 'Vercel project'
required: true
vercelToken:
description: 'Vercel token'
required: true
opItem:
description: '1Password item to load secrets from'
required: true
opServiceAccount:
description: '1Password service account token'
required: true
environment:
description: 'Environment to deploy to'
required: true
outputs:
deployment-url:
description: "Deployment URL"
value: ${{ steps.deploy.outputs.deployment-url }}
runs:
using: 'composite'
steps:
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
shell: bash
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Pull Vercel Environment Information
run: bun run vercel pull --yes --environment=${{ inputs.environment }} --token=${{ inputs.vercelToken }}
shell: bash
env:
VERCEL_ORG_ID: ${{ inputs.vercelOrg }}
VERCEL_PROJECT_ID: ${{ inputs.vercelProject }}
- name: Load secret
uses: 1password/load-secrets-action@v2
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ inputs.opServiceAccount }}
GITBOOK_URL: ${{ inputs.opItem }}/GITBOOK_URL
GITBOOK_ICONS_URL: ${{ inputs.opItem }}/GITBOOK_ICONS_URL
GITBOOK_ICONS_TOKEN: ${{ inputs.opItem }}/GITBOOK_ICONS_TOKEN
GITBOOK_SECRET: ${{ inputs.opItem }}/GITBOOK_SECRET
GITBOOK_APP_URL: ${{ inputs.opItem }}/GITBOOK_APP_URL
GITBOOK_API_URL: ${{ inputs.opItem }}/GITBOOK_API_URL
GITBOOK_API_PUBLIC_URL: ${{ inputs.opItem }}/GITBOOK_API_PUBLIC_URL
GITBOOK_API_TOKEN: ${{ inputs.opItem }}/GITBOOK_API_TOKEN
GITBOOK_INTEGRATIONS_HOST: ${{ inputs.opItem }}/GITBOOK_INTEGRATIONS_HOST
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_SIGNING_KEY
GITBOOK_IMAGE_RESIZE_URL: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_URL
GITBOOK_IMAGE_RESIZE_MODE: ${{ inputs.opItem }}/GITBOOK_IMAGE_RESIZE_MODE
GITBOOK_ASSETS_PREFIX: ${{ inputs.opItem }}/GITBOOK_ASSETS_PREFIX
GITBOOK_FONTS_URL: ${{ inputs.opItem }}/GITBOOK_FONTS_URL
- name: Build Project Artifacts
run: bun run vercel build --target=${{ inputs.environment }} --token=${{ inputs.vercelToken }}
shell: bash
env:
VERCEL_ORG_ID: ${{ inputs.vercelOrg }}
VERCEL_PROJECT_ID: ${{ inputs.vercelProject }}
GITBOOK_RUNTIME: vercel
- name: Deploy Project Artifacts to Vercel
id: deploy
shell: bash
run: |
DEPLOYMENT_URL=$(bun run vercel deploy --prebuilt --target=${{ inputs.environment }} --token=${{ inputs.vercelToken }})
echo "deployment-url=$DEPLOYMENT_URL" >> "$GITHUB_OUTPUT"
env:
VERCEL_ORG_ID: ${{ inputs.vercelOrg }}
VERCEL_PROJECT_ID: ${{ inputs.vercelProject }}
- name: Outputs
shell: bash
run: |
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
+1 -10
View File
@@ -3,16 +3,7 @@ description: 'Install Bun and cache dependencies'
runs:
using: 'composite'
steps:
# We cache dependencies even if Bun is fast to avoid downloading fontawesome too many times
# and impacting bandwidth there.
- name: Cache bun
uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
bun-version-file: '.bun-version'
bun-version-file: 'package.json'
+4 -302
View File
@@ -7,298 +7,10 @@ on:
env:
NPM_TOKEN_READONLY: ${{ secrets.NPM_TOKEN_READONLY }}
jobs:
deploy:
name: Deploy to Cloudflare Pages
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
issues: write
pull-requests: write
checks: write
statuses: write
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
deployment-alias-url: ${{ steps.deploy.outputs.deployment-alias-url }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Sets env vars for production
run: |
echo "SENTRY_ENVIRONMENT=production" >> $GITHUB_ENV
echo "GITBOOK_ASSETS_PREFIX=https://static.gitbook.com" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/heads/main')
- name: Sets env vars for preview
run: |
echo "SENTRY_ENVIRONMENT=preview" >> $GITHUB_ENV
if: 1 && !startsWith(github.ref, 'refs/heads/main')
- name: Build Next.js with next-on-pages
run: bun run turbo build:cloudflare
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ secrets.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
SENTRY_DSN: ${{ vars.SENTRY_DSN }}
SENTRY_RELEASE: ${{ github.sha }}
- id: deploy
name: Deploy to Cloudflare
uses: cloudflare/wrangler-action@v3.11.0
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: ./
wranglerVersion: '3.82.0'
command: pages deploy ./packages/gitbook/.vercel/output/static --project-name=${{ vars.CLOUDFLARE_PROJECT_NAME }} --branch=${{ github.ref == 'refs/heads/main' && 'main' || format('pr{0}', github.event.pull_request.number) }}
- name: Outputs
run: |
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
echo "Alias URL: ${{ steps.deploy.outputs.deployment-alias-url }}"
- name: Archive build output
uses: actions/upload-artifact@v4
with:
name: build-output
path: .vercel/
# Until https://github.com/cloudflare/wrangler-action/issues/301 is done
- name: Update Deployment Status to Success
env:
DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }}
run: |
curl -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
-d '{"state": "success", "target_url": "${{ steps.deploy.outputs.deployment-url }}", "description": "Deployed Preview URL for commit", "context": "cloudflare/preview"}' \
https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }}
deploy-v2-vercel:
name: Deploy v2 to Vercel
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
issues: write
pull-requests: write
checks: write
statuses: write
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Sets env vars for production
run: |
echo "VERCEL_ENVIRONMENT=production" >> $GITHUB_ENV
echo "SENTRY_ENVIRONMENT=production" >> $GITHUB_ENV
echo "GITBOOK_ASSETS_PREFIX=https://static.gitbook.com" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/heads/main')
- name: Sets env vars for preview
run: |
echo "VERCEL_ENVIRONMENT=preview" >> $GITHUB_ENV
echo "SENTRY_ENVIRONMENT=preview" >> $GITHUB_ENV
if: 1 && !startsWith(github.ref, 'refs/heads/main')
- name: Pull Vercel Environment Information
run: bun run vercel pull --yes --environment=$VERCEL_ENVIRONMENT --token=${{ secrets.VERCEL_TOKEN }}
- name: Build Project Artifacts
run: bun run vercel build --token=${{ secrets.VERCEL_TOKEN }}
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ secrets.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
SENTRY_DSN: ${{ vars.SENTRY_DSN }}
SENTRY_RELEASE: ${{ github.sha }}
- name: Deploy Project Artifacts to Vercel
id: deploy
run: |
DEPLOYMENT_URL=$(bun run vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }})
echo "deployment-url=$DEPLOYMENT_URL" >> "$GITHUB_OUTPUT"
- name: Outputs
run: |
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
deploy-v2-cloudflare:
name: Deploy v2 to Cloudflare Worker
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
issues: write
pull-requests: write
checks: write
statuses: write
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Sets env vars for production
run: |
echo "SENTRY_ENVIRONMENT=production" >> $GITHUB_ENV
echo "GITBOOK_ASSETS_PREFIX=https://static.gitbook.com" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/heads/main')
- name: Sets env vars for preview
run: |
echo "SENTRY_ENVIRONMENT=preview" >> $GITHUB_ENV
if: 1 && !startsWith(github.ref, 'refs/heads/main')
- name: Build worker
run: bun run turbo build:v2:cloudflare
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ secrets.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY }}
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }}
SENTRY_DSN: ${{ vars.SENTRY_DSN }}
SENTRY_RELEASE: ${{ github.sha }}
- id: deploy
name: Deploy to Cloudflare
uses: cloudflare/wrangler-action@v3.11.0
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: ./
wranglerVersion: '3.82.0'
command: versions upload --config ./packages/gitbook-v2/wrangler.toml
- name: Outputs
run: |
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
comment-deployments:
runs-on: ubuntu-latest
name: Comment Deployments
if: always() && !startsWith(github.ref, 'refs/heads/main')
needs:
- deploy
- deploy-v2-vercel
- deploy-v2-cloudflare
steps:
- name: Find GitHub Comment
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: 'Summary of the deployments'
- name: Create or update GitHub comment
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
Summary of the deployments:
### Version 1 (production)
| Version | URL | Status |
| --- | --- | --- |
| Latest commit | [${{ needs.deploy.outputs.deployment-url }}](${{ needs.deploy.outputs.deployment-url }}) | ${{ needs.deploy.result == 'success' && '✅' || '❌' }} |
| PR | [${{ needs.deploy.outputs.deployment-alias-url }}](${{ needs.deploy.outputs.deployment-alias-url }}) | ${{ needs.deploy.result == 'success' && '✅' || '❌' }} |
### Version 2 (experimental)
| Version | URL | Status |
| --- | --- | --- |
| Vercel | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}](${{ needs.deploy-v2-vercel.outputs.deployment-url }}) | ${{ needs.deploy-v2-vercel.result == 'success' && '✅' || '❌' }} |
| Cloudflare | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}) | ${{ needs.deploy-v2-cloudflare.result == 'success' && '✅' || '❌' }} |
### Test content
| Site | v1 | v2 |
| --- | --- | --- |
| GitBook | [${{ needs.deploy.outputs.deployment-url }}/docs.gitbook.com](${{ needs.deploy.outputs.deployment-url }}/docs.gitbook.com) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/static/url/docs.gitbook.com](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/static/url/docs.gitbook.com) |
| E2E | [${{ needs.deploy.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy.outputs.deployment-url }}/gitbook.gitbook.io/test-gitbook-open) | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/static/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/static/url/gitbook.gitbook.io/test-gitbook-open) |
edit-mode: replace
visual-testing:
runs-on: ubuntu-latest
name: Visual Testing
needs: deploy
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
- name: Run Playwright tests
run: bun e2e
env:
BASE_URL: ${{needs.deploy.outputs.deployment-url}}
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-test-results
path: packages/gitbook/test-results/
retention-days: 3
visual-testing-customers:
runs-on: ubuntu-latest
name: Visual Testing Customers
needs: deploy
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
- name: Run Playwright tests
run: bun e2e-customers
env:
BASE_URL: ${{needs.deploy.outputs.deployment-url}}
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
ARGOS_BUILD_NAME: 'customers'
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-test-results-customers
path: packages/gitbook/test-results/
retention-days: 3
pagespeed-testing:
runs-on: ubuntu-latest
name: PageSpeed Testing
needs: deploy
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Run pagespeed tests
run: bun ./packages/gitbook/tests/pagespeed-testing.ts $DEPLOYMENT_URL
env:
DEPLOYMENT_URL: ${{needs.deploy.outputs.deployment-url}}
PAGESPEED_API_KEY: ${{ secrets.PAGESPEED_API_KEY }}
format:
runs-on: ubuntu-latest
name: Format
timeout-minutes: 6
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -309,22 +21,10 @@ jobs:
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- run: bun format:check
lint:
runs-on: ubuntu-latest
name: Lint
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- run: bun lint --no-cache
test:
runs-on: ubuntu-latest
name: Test
timeout-minutes: 6
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -339,6 +39,7 @@ jobs:
# CI to check that the repository builds correctly on a machine without the credentials
runs-on: ubuntu-latest
name: Build (Open Source)
timeout-minutes: 6
env:
NPM_TOKEN_READONLY: ''
steps:
@@ -354,6 +55,7 @@ jobs:
typecheck:
runs-on: ubuntu-latest
name: Typecheck
timeout-minutes: 6
steps:
- name: Checkout
uses: actions/checkout@v4
+202
View File
@@ -0,0 +1,202 @@
name: Preview
on:
pull_request:
push:
branches:
- main
env:
NPM_TOKEN_READONLY: ${{ secrets.NPM_TOKEN_READONLY }}
jobs:
deploy-v2-vercel:
name: Deploy v2 to Vercel (preview)
runs-on: ubuntu-latest
environment:
name: 2v-preview
url: ${{ steps.deploy.outputs.deployment-url }}
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy to Vercel
id: deploy
uses: ./.github/composite/deploy-vercel
with:
environment: preview
vercelOrg: ${{ secrets.VERCEL_ORG_ID }}
vercelProject: ${{ secrets.VERCEL_PROJECT_ID }}
vercelToken: ${{ secrets.VERCEL_TOKEN }}
opItem: op://gitbook-open/2v-preview
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
deploy-v2-cloudflare:
name: Deploy v2 to Cloudflare Worker (preview)
runs-on: ubuntu-latest
environment:
name: 2c-preview
url: ${{ steps.deploy.outputs.deployment-url }}
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url || steps.extract-worker-id.outputs.worker-url }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy to Cloudflare
id: deploy
uses: ./.github/composite/deploy-cloudflare
with:
environment: preview
deploy: ${{ github.ref == 'refs/heads/main' }}
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
opItem: op://gitbook-open/2c-preview
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
commitTag: ${{ github.ref == 'refs/heads/main' && 'main' || format('pr{0}', github.event.pull_request.number) }}
commitMessage: ${{ github.sha }}
- name: Extract Worker ID
id: extract-worker-id
if: ${{ !steps.deploy.outputs.deployment-url }}
run: |
if [[ "${{ steps.deploy.outputs.command-output }}" =~ Worker\ Version\ ID:\ ([0-9a-f]{8})-([0-9a-f-]+) ]]; then
WORKER_ID_FIRST_PART="${BASH_REMATCH[1]}"
echo "worker-url=https://${WORKER_ID_FIRST_PART}-gitbook-open-v2-preview.gitbook.workers.dev/" >> $GITHUB_OUTPUT
fi
- name: Outputs
run: |
echo "URL: ${{ steps.deploy.outputs.deployment-url || steps.extract-worker-id.outputs.worker-url }}"
comment-deployments:
runs-on: ubuntu-latest
name: Comment Deployments (preview)
if: always() && !startsWith(github.ref, 'refs/heads/main')
needs:
- deploy-v2-vercel
- deploy-v2-cloudflare
steps:
- name: Find GitHub Comment
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: 'Summary of the deployments'
- name: Create or update GitHub comment
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: |
Summary of the deployments:
| Version | URL | Status |
| --- | --- | --- |
| Vercel | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}](${{ needs.deploy-v2-vercel.outputs.deployment-url }}) | ${{ needs.deploy-v2-vercel.result == 'success' && '✅' || '❌' }} |
| Cloudflare | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}) | ${{ needs.deploy-v2-cloudflare.result == 'success' && '✅' || '❌' }} |
### Test content
| Site | `2v` | `2c` |
| --- | --- | --- |
| GitBook | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.com/docs](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.com/docs) | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.com/docs](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.com/docs) |
| E2E | [${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) | [${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open](${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/gitbook.gitbook.io/test-gitbook-open) |
edit-mode: replace
visual-testing-v2-vercel:
runs-on: ubuntu-latest
name: Visual Testing v2
needs: deploy-v2-vercel
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
- name: Run Playwright tests
run: bun e2e
env:
BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}
SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
ARGOS_BUILD_NAME: 'v2-vercel'
visual-testing-v2-cloudflare:
runs-on: ubuntu-latest
name: Visual Testing v2 (Cloudflare)
needs: deploy-v2-cloudflare
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
- name: Run Playwright tests
run: bun e2e
env:
BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}
SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
ARGOS_BUILD_NAME: 'v2-cloudflare'
visual-testing-customers-v2:
runs-on: ubuntu-latest
name: Visual Testing Customers v2
needs: deploy-v2-vercel
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
- name: Run Playwright tests
run: bun e2e-customers
env:
BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}
SITE_BASE_URL: ${{ needs.deploy-v2-vercel.outputs.deployment-url }}/url/
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
ARGOS_BUILD_NAME: 'customers-v2'
visual-testing-customers-v2-cloudflare:
runs-on: ubuntu-latest
name: Visual Testing Customers v2 (Cloudflare)
needs: deploy-v2-cloudflare
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Setup Playwright
uses: ./.github/actions/setup-playwright
- name: Run Playwright tests
run: bun e2e-customers
env:
BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}
SITE_BASE_URL: ${{ needs.deploy-v2-cloudflare.outputs.deployment-url }}/url/
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
ARGOS_BUILD_NAME: 'customers-v2'
pagespeed-testing-v2:
runs-on: ubuntu-latest
name: PageSpeed Testing v1
needs: deploy-v2-vercel
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Run pagespeed tests
run: bun ./packages/gitbook/tests/pagespeed-testing.ts
env:
BASE_URL: ${{needs.deploy-v2-vercel.outputs.deployment-url}}
PAGESPEED_API_KEY: ${{ secrets.PAGESPEED_API_KEY }}
+55
View File
@@ -0,0 +1,55 @@
name: Production
on:
push:
branches:
- main
env:
NPM_TOKEN_READONLY: ${{ secrets.NPM_TOKEN_READONLY }}
jobs:
deploy-v2-vercel:
name: Deploy v2 to Vercel (production)
runs-on: ubuntu-latest
environment:
name: 2v-production
url: ${{ steps.deploy.outputs.deployment-url }}
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy
id: deploy
uses: ./.github/composite/deploy-vercel
with:
environment: production
vercelOrg: ${{ secrets.VERCEL_ORG_ID }}
vercelProject: ${{ secrets.VERCEL_PROJECT_ID }}
vercelToken: ${{ secrets.VERCEL_TOKEN }}
opItem: op://gitbook-open/2v-production
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
deploy-v2-cloudflare:
name: Deploy v2 to Cloudflare Worker (production)
runs-on: ubuntu-latest
environment:
name: 2c-production
url: ${{ steps.deploy.outputs.deployment-url }}
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy
id: deploy
uses: ./.github/composite/deploy-cloudflare
with:
environment: production
deploy: true
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
opItem: op://gitbook-open/2c-production
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
commitTag: main
commitMessage: ${{ github.sha }}
- name: Outputs
run: |
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
+55
View File
@@ -0,0 +1,55 @@
name: Staging
on:
push:
branches:
- main
env:
NPM_TOKEN_READONLY: ${{ secrets.NPM_TOKEN_READONLY }}
jobs:
deploy-v2-vercel:
name: Deploy v2 to Vercel (staging)
runs-on: ubuntu-latest
environment:
name: 2v-staging
url: ${{ steps.deploy.outputs.deployment-url }}
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy
id: deploy
uses: ./.github/composite/deploy-vercel
with:
environment: staging
vercelOrg: ${{ secrets.VERCEL_ORG_ID }}
vercelProject: ${{ secrets.VERCEL_PROJECT_ID }}
vercelToken: ${{ secrets.VERCEL_TOKEN }}
opItem: op://gitbook-open/2v-staging
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
deploy-v2-cloudflare:
name: Deploy v2 to Cloudflare Worker (staging)
runs-on: ubuntu-latest
environment:
name: 2c-staging
url: ${{ steps.deploy.outputs.deployment-url }}
outputs:
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Deploy
id: deploy
uses: ./.github/composite/deploy-cloudflare
with:
environment: staging
deploy: true
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
opItem: op://gitbook-open/2c-staging
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
commitTag: main
commitMessage: ${{ github.sha }}
- name: Outputs
run: |
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
+5 -19
View File
@@ -5,6 +5,9 @@ on:
branches:
- main
env:
NPM_TOKEN_READONLY: ${{ secrets.NPM_TOKEN_READONLY }}
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
@@ -28,6 +31,7 @@ jobs:
uses: changesets/action@v1
with:
publish: npm run release
version: npm run changeset-version
env:
# Using a PAT instead of GITHUB_TOKEN because we need to run workflows when releases are created
# https://github.com/orgs/community/discussions/26875#discussioncomment-3253761
@@ -35,22 +39,4 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
release-preview:
# For now it releases the cache-do to both preview and production
# Once we changed to deploy the app only on release, we should change `release:preview` in `cache-do`
name: Release Preview
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v3
- name: Setup Bun
uses: ./.github/composite/setup-bun
- name: Install dependencies
run: bun install --frozen-lockfile
env:
PUPPETEER_SKIP_DOWNLOAD: 1
- name: Release preview packages
run: bun run release:preview
env:
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+3
View File
@@ -22,3 +22,6 @@ yarn-error.log*
# Env files
.env.local
# TypeScript
*.tsbuildinfo
-14
View File
@@ -1,14 +0,0 @@
.next
.vercel
.open-next
# Generated
packages/emoji-codepoints/index.ts
packages/gitbook/public/~gitbook/static/
packages/icons/src/data/*.json
# Build files
dist/
README.md
CHANGELOG.md
-5
View File
@@ -1,5 +0,0 @@
{
"printWidth": 100,
"singleQuote": true,
"tabWidth": 4
}
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["biomejs.biome"]
}
+11 -1
View File
@@ -7,5 +7,15 @@
["style \\=([^;]*);", "\"([^\"]*)\""],
["style \\=([^;]*);", "\\`([^\\`]*)\\`"]
],
"tailwindCSS.classAttributes": ["class", "className", "style", ".*Style"]
"tailwindCSS.classAttributes": ["class", "className", "style", ".*Style"],
"prettier.enable": false,
"editor.formatOnSave": true,
"editor.defaultFormatter": "biomejs.biome",
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit",
"source.fixAll.biome": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
+8 -13
View File
@@ -1,7 +1,7 @@
<h1 align="center">GitBook</h1>
<p align="center">
<a href="https://docs.gitbook.com/">Docs</a> - <a href="https://github.com/GitbookIO/community">Community</a> - <a href="https://developer.gitbook.com/">Developer Docs</a> - <a href="https://changelog.gitbook.com/">Changelog</a> - <a href="https://github.com/GitbookIO/gitbook/issues/new?assignees=&labels=bug&template=bug_report.md">Bug reports</a>
<a href="https://gitbook.com/docs/">Docs</a> - <a href="https://github.com/GitbookIO/community">Community</a> - <a href="https://developer.gitbook.com/">Developer Docs</a> - <a href="https://changelog.gitbook.com/">Changelog</a> - <a href="https://github.com/GitbookIO/gitbook/issues/new?assignees=&labels=bug&template=bug_report.md">Bug reports</a> - <a href="https://github.com/orgs/GitbookIO/discussions/categories/feature-requests">Feature requests</a>
</p>
<p align="center">
@@ -37,8 +37,8 @@ To run a local version of this project, please follow these simple steps.
- Node.js (Version: >=20.6)
- Use nvm for easy Node management
- Bun (Version: >=1.2.1)
- We use a text-based lockfile which isn't supported below 1.2.1
- [Bun](https://bun.sh/) (Version: >=1.2.15)
- We use a text-based lockfile which isn't supported below 1.2.15
### Set up
@@ -62,20 +62,15 @@ bun install
bun dev
```
5. Open a published GitBook space in your web browser, prefixing it with `http://localhost:3000/`.
6. Open a published GitBook space in your web browser, prefixing it with `http://localhost:3000/url`.
examples:
- http://localhost:3000/docs.gitbook.com
- http://localhost:3000/open-source.gitbook.io/midjourney
- http://localhost:3000/url/gitbook.com/docs
- http://localhost:3000/url/open-source.gitbook.io/midjourney
Any published GitBook site can be accessed through your local development instance, and any updates you make to the codebase will be reflected in your browser.
### Other development commands
- `bun format`: format the code
- `bun lint`: lint the code
### CI and testing
All pull-requests will be tested against both visual and performances testing to prevent regressions.
@@ -150,11 +145,11 @@ See `LICENSE` for more information.
</p>
```md
[![GitBook](https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=gitbook&logoColor=ffffff&label=%20&labelColor=5c5c5c&color=3F89A1)](https://gitbook.com/)
[![GitBook](https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=gitbook&logoColor=ffffff&label=%20&labelColor=5c5c5c&color=3F89A1)](https://www.gitbook.com/preview?utm_source=gitbook_readme_badge&utm_medium=organic&utm_campaign=preview_documentation&utm_content=link)
```
```html
<a href="https://gitbook.com">
<a href="https://www.gitbook.com/preview?utm_source=gitbook_readme_badge&utm_medium=organic&utm_campaign=preview_documentation&utm_content=link">
<img
src="https://img.shields.io/static/v1?message=Documented%20on%20GitBook&logo=gitbook&logoColor=ffffff&label=%20&labelColor=5c5c5c&color=3F89A1"
/>
+176
View File
@@ -0,0 +1,176 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": [
"**/node_modules/**/*",
"**/dist/**/*",
"**/build/**/*",
"**/public/**/*",
"**/.next/**/*",
"**/.open-next/**/*",
"**/.turbo/**/*",
"**/.vercel/**/*",
"**/.cache/**/*",
"**/.wrangler/**/*",
"packages/openapi-parser/src/fixtures/**/*",
"packages/emoji-codepoints/index.ts",
"packages/icons/src/data/*.json",
"packages/gitbook/worker-configuration.d.ts",
"**/*.css"
]
},
"formatter": {
"enabled": true,
"useEditorconfig": true,
"formatWithErrors": false,
"indentStyle": "space",
"indentWidth": 4,
"lineEnding": "lf",
"lineWidth": 100,
"attributePosition": "auto",
"bracketSpacing": true
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"performance": {
"noDelete": "warn"
},
"security": {
"noDangerouslySetInnerHtml": "off"
},
"complexity": {
"noForEach": "off",
"noUselessFragments": "warn",
"noBannedTypes": "warn"
},
"correctness": {
"noUndeclaredVariables": "error",
"noUnusedVariables": "error",
"useArrayLiterals": "error",
"useHookAtTopLevel": "error",
"noUnusedImports": "error",
"noVoidElementsWithChildren": "warn",
"useJsxKeyInIterable": "warn",
"useExhaustiveDependencies": "warn",
"noUnknownFunction": "warn"
},
"style": {
"noNonNullAssertion": "warn",
"noParameterAssign": "off",
"useThrowOnlyError": "error"
},
"suspicious": {
"noConsole": {
"level": "warn",
"options": {
"allow": ["assert", "error", "warn"]
}
},
"noExplicitAny": "warn",
"noImplicitAnyLet": "warn",
"noConfusingVoidType": "warn",
"noControlCharactersInRegex": "warn",
"noPrototypeBuiltins": "warn",
"noAssignInExpressions": "warn",
"noArrayIndexKey": "warn"
},
"a11y": {
"useSemanticElements": "warn",
"useKeyWithClickEvents": "warn",
"noSvgWithoutTitle": "warn",
"useButtonType": "warn",
"useIframeTitle": "warn",
"useAltText": "warn",
"noPositiveTabindex": "warn",
"useFocusableInteractive": "warn",
"useAriaPropsForRole": "warn",
"useValidAnchor": "warn",
"noLabelWithoutControl": "warn",
"noNoninteractiveTabindex": "warn"
},
"nursery": {
"useSortedClasses": {
"level": "error",
"fix": "safe",
"options": {
"attributes": ["class", "className", "style"],
"functions": ["clsx", "tw"]
}
}
}
}
},
"javascript": {
"formatter": {
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "es5",
"semicolons": "always",
"arrowParentheses": "always",
"bracketSameLine": false,
"quoteStyle": "single",
"attributePosition": "auto",
"bracketSpacing": true
}
},
"overrides": [
{
"include": [
"packages/gitbook/**/*",
"packages/react-openapi/**/*",
"packages/react-math/**/*",
"packages/react-contentkit/**/*",
"packages/icons/**/*"
],
"javascript": {
"globals": ["React"]
}
},
{
"include": ["packages/gitbook/**/*"],
"javascript": {
"globals": ["React", "GitBookIntegrationEvent"]
}
},
{
"include": ["*.css"],
"javascript": {
"globals": ["theme"]
}
},
{
"include": ["*.test.ts", "packages/gitbook/tests/**/*"],
"javascript": {
"globals": ["Bun"]
}
},
{
"include": [
"packages/cache-do/**/*",
"packages/gitbook/cf-env.d.ts",
"packages/gitbook/src/cloudflare-entrypoint.ts"
],
"javascript": {
"globals": [
"DurableObjectLocationHint",
"DurableObjectNamespace",
"DurableObjectStub",
"ContinentCode",
"Fetcher",
"ExportedHandler"
]
}
}
]
}
+969 -1517
View File
File diff suppressed because it is too large Load Diff
-22
View File
@@ -1,22 +0,0 @@
# Caching
## Revalidating the cache
Invalidate cache can be done at two levels using tags:
- Data fetching cache
- Rendering cache
To invalidate and refetch the data cache, you can execute a POST request to `/~/gitbook/revalidate`:
```bash
curl --location --request POST 'https://gitbook/mycompany.com/~gitbook/revalidate' \
--header 'Content-Type: application/json' \
--data-raw '{"tags": ["space.id"]}'
```
To invalidate the rendering cache, the implementation mainly depends on the infrastructure serving the content, GitBook outputs a `Cache-Tag` header on every requests. The value of the header is a comma separated list of tags.
## Purging the cache
Purging the cache, without revalidating, is done by passing `"purge": true` in the request body.
+15 -15
View File
@@ -2,41 +2,41 @@
"name": "gitbook",
"version": "0.1.0",
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@changesets/cli": "^2.27.12",
"prettier": "^3.0.3",
"turbo": "^2.4.1-canary.2",
"turbo": "^2.5.0",
"vercel": "^39.3.0"
},
"packageManager": "bun@1.1.18",
"packageManager": "bun@1.2.15",
"overrides": {
"@codemirror/state": "6.4.1",
"react": "18.3.1",
"react-dom": "18.3.1"
"react": "^19.0.0",
"react-dom": "^19.0.0",
"esbuild": "0.24.2"
},
"private": true,
"scripts": {
"dev": "turbo run dev",
"dev:v2": "turbo run dev:v2",
"build": "turbo run build",
"build:v2": "turbo run build:v2",
"clean-deps": "rm -rf node_modules && rm -rf packages/*/node_modules",
"lint": "turbo run lint",
"lint:fix": "turbo run lint -- --fix",
"typecheck": "turbo run typecheck",
"format": "prettier ./ --ignore-unknown --write",
"format:check": "prettier ./ --ignore-unknown --list-different",
"format": "biome check --write ./",
"format:check": "biome check --diagnostic-level=error ./",
"unit": "turbo run unit",
"e2e": "turbo run e2e",
"e2e-customers": "turbo run e2e-customers",
"changeset": "changeset",
"changeset-version": "changeset version && bun run format",
"release": "turbo run release && changeset publish",
"release:preview": "turbo run release:preview",
"download:env": "op read op://gitbook-x-dev/gitbook-open/.env.local >> .env.local",
"clean": "turbo run clean"
},
"workspaces": [
"packages/*"
],
"workspaces": {
"packages": ["packages/*"],
"catalog": {
"@gitbook/api": "^0.130.0"
}
},
"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"
-3
View File
@@ -1,3 +0,0 @@
.wrangler
worker-configuration.d.ts
dist/
-18
View File
@@ -1,18 +0,0 @@
# @gitbook/cache-do
## 0.1.1
### Patch Changes
- b7a5106: Disable cloudflare observability in production
## 0.1.0
### Minor Changes
- 9b8d519: Experiment with optimizing billable duration in Cloudflare by using multiple RPC sessions instead of one
- 636b868: First version of a new cache backend powered by Cloudflare Durable Objects
### Patch Changes
- 56f5fa1: Enable Workers observability with a sampling of 0.1
-22
View File
@@ -1,22 +0,0 @@
# `@gitbook/cache-do`
Cache backend, powered by Cloudflare Durable Objects. The cache is optimized for GitBook use-cases.
### Performances
The cache backend is optimized for performances by being distributed and accessible close to the worker locations that are reading it.
### Geo-distribution
To achieve a good balance between **performances** and **consistency**, cache objects are distributed over 7 locations, representing continents.
It makes it possible to purge all 7 locations in one go and achieve fast consistency.
### Concepts
**Cache tag**: unique tag in the cache environment. A cache tag groups multiple keys that should be purged together in one operation.
Cache tags should not contain a large set of unique keys. Exceeding thousands could lead to performances or reliability issues.
**Cache key**: unique key in the cache environment. Each key should be assigned to a `tag`.
**Location**: cache is distributed over 7 unique locations, one for each continent.
-42
View File
@@ -1,42 +0,0 @@
{
"name": "@gitbook/cache-do",
"type": "module",
"private": true,
"exports": {
".": {
"types": "./dist/index.d.ts",
"development": "./src/index.ts",
"default": "./dist/index.js"
},
"./api": {
"types": "./dist/api.d.ts",
"development": "./src/api.ts",
"default": "./dist/api.js"
}
},
"version": "0.1.1",
"dependencies": {
"@msgpack/msgpack": "^3.0.0-beta2",
"lru_map": "^0.4.1"
},
"devDependencies": {
"typescript": "^5.5.3",
"wrangler": "3.82.0"
},
"scripts": {
"generate": "wrangler types --experimental-include-runtime",
"build": "tsc",
"typecheck": "tsc --noEmit",
"dev": "tsc -w",
"release": "wrangler deploy",
"release:preview": "wrangler deploy && wrangler deploy --env preview"
},
"files": [
"dist",
"src",
"bin",
"data",
"README.md",
"CHANGELOG.md"
]
}
-299
View File
@@ -1,299 +0,0 @@
import { encode, decode } from '@msgpack/msgpack';
import { DurableObject } from 'cloudflare:workers';
import { LRUMap } from 'lru_map';
export interface CacheObjectDescriptor {
get: <Value = unknown>(key: string) => Promise<Value | undefined>;
set: <Value = unknown>(key: string, value: Value, expiresAt: number) => Promise<void>;
}
/**
* Value stored in a chunked binary msgpack format.
* Stored under the key `prop.${key}.${index}`.
*/
interface CacheObjectProp<Value = unknown> {
value: Value;
expiresAt: number;
}
/**
* Expiration clock stored under the key `exp.${expiresAt}.${key}`.
*/
interface CacheObjectExp {
/** Key of the property */
k: string;
/** Number of chunks */
c: number;
}
/**
* Durable Object class being deployed as a distributed cache.
*/
export class CacheObject extends DurableObject {
private lru = new LRUMap<string, { match: CacheObjectProp | undefined }>(500);
/**
* Open a descriptor to access the cache object.
* The goal is to minimize the amount of RPC sessions between the client and the cache object.
* One session is opened per request on the client side and used to perform multiple operations.
* https://developers.cloudflare.com/workers/runtime-apis/rpc/#return-functions-from-rpc-methods
*/
public open(): CacheObjectDescriptor {
return {
get: async <Value = unknown>(key: string) => {
return this.get<Value>(key);
},
set: async <Value = unknown>(key: string, value: Value, expiresAt: number) => {
await this.set(key, value, expiresAt);
},
};
}
/**
* Get the value of a property.
*/
public async get<Value = unknown>(key: string) {
return this.logOperation({ operation: 'get', key }, async (setLog) => {
// Try the memory state first.
const memoryEntry = this.lru.get(key);
if (memoryEntry) {
setLog({ memory: true });
setLog({ memoryMatch: !!memoryEntry.match });
if (!memoryEntry.match) {
return;
}
const isExpired = memoryEntry.match.expiresAt < Date.now();
setLog({ memoryExpired: isExpired });
if (!isExpired) {
return memoryEntry.match.value as Value;
}
}
return await this.getFromStorage<Value>(key);
});
}
/**
* Get the value of a property from the DO storage.
*/
public async getFromStorage<Value = unknown>(key: string) {
return this.logOperation({ operation: 'getFromStorage', key }, async (setLog) => {
const entries = await this.ctx.storage.list<Uint8Array>({
prefix: getStoragePropKey(key),
noCache: true,
});
if (entries.size) {
const entry = decodeChunks<CacheObjectProp<Value>>(entries);
setLog({ chunks: entries.size, chunksSize: entry?.size ?? 0 });
if (entry && entry.value.expiresAt > Date.now()) {
// Found
this.lru.set(key, { match: entry.value });
return entry.value.value;
}
}
// Not found
this.lru.set(key, { match: undefined });
});
}
/**
* Set a value in the cache object.
*/
public async set<Value = unknown>(key: string, value: Value, expiresAt: number) {
return this.logOperation({ operation: 'set', key }, async (setLog) => {
const prop: CacheObjectProp<Value> = {
value,
expiresAt,
};
this.lru.set(key, { match: prop });
await this.ctx.storage.transaction(async (tx) => {
const entries = encodeChunks(key, prop);
const chunks = Object.keys(entries).length;
setLog({ chunks });
const clockValue: CacheObjectExp = {
k: key,
c: chunks,
};
await tx.put(getGCClockKey(key, expiresAt), clockValue);
await tx.put(entries);
const currentAlarm = await tx.getAlarm();
if (!currentAlarm) {
// Set an alarm to garbage collect all entries that have expired in 12h.
await tx.setAlarm(Date.now() + 12 * 60 * 60 * 1000);
}
});
});
}
/**
* Purge all keys in the cache object.
*/
public async purge() {
return this.logOperation({ operation: 'purge' }, async (setLog) => {
let result = new Set<string>();
try {
// List all the keys in the cache object.
const entries = await this.ctx.storage.list<CacheObjectExp>({
prefix: 'exp.',
noCache: true,
});
setLog({ entries: entries.size });
entries.forEach((exp) => {
result.add(exp.k);
});
} catch (error) {
// If an error occurs, reset the cache object.
// This is a safety mechanism to prevent the cache object from being stuck in a bad state.
console.error('Error during purge, resetting the cache object', error);
}
await this.reset();
return Array.from(result);
});
}
/**
* Alarm to garbage collect all entries that have expired.
*/
async alarm() {
return this.logOperation({ operation: 'alarm' }, async (setLog) => {
try {
const entries = await this.ctx.storage.list<CacheObjectExp>({
prefix: 'exp.',
noCache: true,
});
setLog({ entries: entries.size });
const toDeleteSet = new Set<string>();
for (const [key, exp] of entries) {
const timestamp = parseInt(key.split('.')[1]);
if (timestamp < Date.now()) {
toDeleteSet.add(key);
for (let i = 0; i < exp.c; i++) {
toDeleteSet.add(getStoragePropChunkKey(exp.k, i));
}
}
}
// Delete the keys by batch of 128.
const toDelete = Array.from(toDeleteSet);
setLog({ toDelete: toDelete.length });
for (let i = 0; i < toDelete.length; i += 128) {
await this.ctx.storage.delete(toDelete.slice(i, i + 128));
}
// If there are still keys to delete, set an alarm to continue the deletion in 12h.
if (toDelete.length) {
await this.ctx.storage.setAlarm(Date.now() + 12 * 60 * 60 * 1000);
}
} catch (error) {
// If an error occurs, reset the cache object.
// This is a safety mechanism to prevent the cache object from being stuck in a bad state.
console.error('Error during alarm, reset the cache object', error);
await this.reset();
}
});
}
/**
* Reset the cache object.
*/
async reset() {
return this.logOperation({ operation: 'reset' }, async () => {
this.lru.clear();
await this.ctx.storage.deleteAll();
});
}
/**
* Time and log an operation.
*/
async logOperation<T>(
log: Record<string, unknown>,
fn: (update: (log: Record<string, unknown>) => void) => Promise<T>,
): Promise<T> {
const objectId = this.ctx.id.name ?? this.ctx.id.toString();
let update: Record<string, unknown> = {};
const start = performance.now();
try {
return await fn((arg) => {
Object.assign(update, arg);
});
} finally {
const duration = performance.now() - start;
console.log({ ...log, ...update, objectId, duration });
}
}
}
function getStoragePropKey(key: string): string {
return `prop.${key}.`;
}
function getStoragePropChunkKey(key: string, index: number): string {
return `${getStoragePropKey(key)}${index}`;
}
function getGCClockRootKey(timestamp: number): string {
return `exp.${timestamp}.`;
}
function getGCClockKey(key: string, expiresAt: number): string {
return `${getGCClockRootKey(expiresAt)}${key}`;
}
function encodeChunks<T>(key: string, value: T): Record<string, Uint8Array> {
const buf = encode(value);
const entries: Record<string, Uint8Array> = {};
const chunks = chunkUint8Array(buf, 128 * 1024);
for (let index = 0; index < chunks.length; index++) {
entries[getStoragePropChunkKey(key, index)] = chunks[index];
}
return entries;
}
function decodeChunks<T>(entries: Map<string, Uint8Array>): { value: T; size: number } | undefined {
const chunks = Array.from(entries.entries())
.map(([key, value]) => {
const index = parseInt(key.split('.').pop()!);
return [index, value] as const;
})
.sort(([a], [b]) => a - b)
.map(([, value]) => value);
if (chunks.length === 0) {
return;
}
const buf = mergeUint8Array(chunks);
return { value: decode(buf) as T, size: buf.length };
}
function chunkUint8Array(input: Uint8Array, chunkSize: number): Uint8Array[] {
const chunks: Uint8Array[] = [];
for (let i = 0; i < input.length; i += chunkSize) {
chunks.push(input.slice(i, i + chunkSize));
}
return chunks;
}
function mergeUint8Array(chunks: Uint8Array[]): Uint8Array {
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
-97
View File
@@ -1,97 +0,0 @@
import type { CacheObject, CacheObjectDescriptor } from './CacheObject';
export type CacheLocationId = ContinentCode;
const allLocations: CacheLocationId[] = ['AF', 'AS', 'NA', 'SA', 'AN', 'EU', 'OC'];
/**
* Location hint for the CacheObject durable object.
*/
const doLocationHints: {
[key in CacheLocationId]: DurableObjectLocationHint;
} = {
AF: 'afr',
AS: 'apac',
NA: 'wnam',
SA: 'sam',
AN: 'oc',
EU: 'weur',
OC: 'oc',
};
/**
* Client to access a cache tag.
*/
export class CacheObjectStub {
private stub: DurableObjectStub<CacheObject>;
constructor(
/** Binding to the CacheObject durable object */
private doNamespace: DurableObjectNamespace<CacheObject>,
/** ID of the location to target */
private locationId: CacheLocationId,
/** Name of the tag */
private tag: string,
) {
const groupId = getCacheObjectIdName(this.locationId, this.tag);
this.stub = this.doNamespace.get(this.doNamespace.idFromName(groupId), {
// Initialize the object with a locaiton hint,
// as we might want to purge all locations before the object is created.
// https://developers.cloudflare.com/durable-objects/reference/data-location/
locationHint: doLocationHints[this.locationId],
});
}
/**
* Open a descriptor to the cache object.
* It can be used to perform multiple operations in a single RPC session.
* Ex:
* ```ts
* using desc = cache.open();
* await desc.set('key', 'value', Date.now() + 1000);
* await desc.get('key');
* ```
*/
async open() {
return await this.stub.open();
}
/**
* Get a value from the cache.
*/
async get<Value = unknown>(key: string) {
return (await this.stub.get(key)) as Value | undefined;
}
/**
* Set a value in the cache.
*/
async set<Value = unknown>(key: string, value: Value, expiresAt: number) {
return await this.stub.set(key, value, expiresAt);
}
/**
* Purge all keys in the cache tag.
*/
async purge() {
const keys = new Set<string>();
await Promise.all(
allLocations.map(async (locationId) => {
const groupId = getCacheObjectIdName(locationId, this.tag);
const cacheGroup = this.doNamespace.get(this.doNamespace.idFromName(groupId), {
// Initialize the object with a locaiton hint,
// as we might want to purge all locations before the object is created.
// https://developers.cloudflare.com/durable-objects/reference/data-location/
locationHint: doLocationHints[this.locationId],
});
const locationkeys = await cacheGroup.purge();
locationkeys.forEach((key) => keys.add(key));
}),
);
return keys;
}
}
function getCacheObjectIdName(locationId: CacheLocationId, tag: string): string {
return `${locationId}:${tag}`;
}
-1
View File
@@ -1 +0,0 @@
export * from './CacheObjectStub';
-9
View File
@@ -1,9 +0,0 @@
import { WorkerEntrypoint } from 'cloudflare:workers';
export * from './CacheObject';
export default class Worker extends WorkerEntrypoint {
fetch() {
return new Response('Hello, world!');
}
}
-27
View File
@@ -1,27 +0,0 @@
main = "./src/index.ts"
name = "gitbook-open-cache"
compatibility_date = "2024-09-02"
durable_objects.bindings = [
{name = "CACHE", class_name = "CacheObject"}
]
migrations = [
{tag = "v1", new_classes = ["CacheObject"]}
]
[observability]
enabled = false
[env.preview]
name = "gitbook-open-cache-preview"
durable_objects.bindings = [
{name = "CACHE", class_name = "CacheObject"}
]
migrations = [
{tag = "v1", new_classes = ["CacheObject"]}
]
[env.preview.observability]
enabled = true
head_sampling_rate = 0.1
+25
View File
@@ -0,0 +1,25 @@
# @gitbook/cache-tags
## 0.3.1
### Patch Changes
- 77397ca: Fix version of @gitbook/api referenced in package.json
## 0.3.0
### Minor Changes
- 116575c: Improve typing of getComputedContentSourceCacheTags to match latest API specification
## 0.2.0
### Minor Changes
- f32bf1f: Export function `getCacheTagForURL` to easily get the cache tag for a URL.
## 0.1.0
### Minor Changes
- 05ffd0e: Initial version of the package
+3
View File
@@ -0,0 +1,3 @@
# `@gitbook/cache-tags`
Utility to generate cache tags for GitBook Open.
@@ -1,7 +1,6 @@
{
"name": "@gitbook/proxy",
"description": "Host a GitBook site on your own domain as a subpath",
"version": "0.1.0",
"name": "@gitbook/cache-tags",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
@@ -9,20 +8,18 @@
"default": "./dist/index.js"
}
},
"dependencies": {},
"version": "0.3.1",
"dependencies": {
"@gitbook/api": "catalog:",
"assert-never": "^1.2.1"
},
"devDependencies": {
"typescript": "^5.5.3"
},
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit",
"clean": "rm -rf ./dist",
"unit": "bun test"
"dev": "tsc -w"
},
"files": [
"dist",
"src",
"README.md",
"CHANGELOG.md"
]
"files": ["dist", "src", "README.md", "CHANGELOG.md"]
}
+213
View File
@@ -0,0 +1,213 @@
import type { ComputedContentSource } from '@gitbook/api';
import assertNever from 'assert-never';
/**
* Get a stringified cache tag for a given object.
*/
export function getCacheTag(
spec: /**
* All data related to a user
* @deprecated - in v2, no tag as this is an immutable data
*/
| {
tag: 'user';
user: string;
}
/**
* All data related to a space
*/
| {
tag: 'space';
space: string;
}
/**
* All data related to an integration.
*/
| {
tag: 'integration';
integration: string;
}
/**
* All data related to a change request
*/
| {
tag: 'change-request';
space: string;
changeRequest: string;
}
/**
* Immutable data related to a revision
* @deprecated - in v2, no tag as this is an immutable data
*/
| {
tag: 'revision';
space: string;
revision: string;
}
/**
* Immutable data related to a document
* @deprecated - in v2, no tag as this is an immutable data
*/
| {
tag: 'document';
space: string;
document: string;
}
/**
* Immutable data related to a computed document
* @deprecated - in v2, no tag as this is an immutable data
*/
| {
tag: 'computed-document';
space: string;
sourceType: string;
}
/**
* All data related to the URL of a content
*/
| {
tag: 'url';
hostname: string;
}
/**
* All data related to a site
*/
| {
tag: 'site';
site: string;
}
/**
* All data related to an OpenAPI spec
*/
| {
tag: 'openapi';
organization: string;
openAPISpec: string;
}
/**
* All data related to a translation
*/
| {
tag: 'translation';
organization: string;
translation: string;
}
): string {
switch (spec.tag) {
case 'user':
return `user:${spec.user}`;
case 'url':
return `url:${spec.hostname}`;
case 'space':
return `space:${spec.space}`;
case 'change-request':
return `space:${spec.space}:change-request:${spec.changeRequest}`;
case 'revision':
return `space:${spec.space}:revision:${spec.revision}`;
case 'document':
return `space:${spec.space}:document:${spec.document}`;
case 'computed-document':
return `space:${spec.space}:computed-document:${spec.sourceType}`;
case 'site':
return `site:${spec.site}`;
case 'integration':
return `integration:${spec.integration}`;
case 'openapi':
return `organization:${spec.organization}:openapi:${spec.openAPISpec}`;
case 'translation':
return `organization:${spec.organization}:translation:${spec.translation}`;
default:
assertNever(spec);
}
}
/**
* Get the cache tag for a given URL.
*/
export function getCacheTagForURL(url: string | URL) {
const parsedURL = url instanceof URL ? url : new URL(url);
return getCacheTag({
tag: 'url',
hostname: parsedURL.hostname,
});
}
/**
* Get the tags for a computed content source.
*/
export function getComputedContentSourceCacheTags(
inContext: {
spaceId: string;
organizationId: string;
},
source: ComputedContentSource
) {
const tags: string[] = [];
if (!('dependencies' in source)) {
return tags;
}
// We add the dependencies as tags, to ensure that the computed content is invalidated
// when the dependencies are updated.
const dependencies = Object.values(source.dependencies ?? {});
if (dependencies.length > 0) {
dependencies.forEach((dependency) => {
switch (dependency.ref.kind) {
case 'space':
tags.push(
getCacheTag({
tag: 'space',
space: dependency.ref.space,
})
);
break;
case 'openapi':
tags.push(
getCacheTag({
tag: 'openapi',
organization: inContext.organizationId,
openAPISpec: dependency.ref.spec,
})
);
break;
case 'translation':
tags.push(
getCacheTag({
tag: 'translation',
organization: inContext.organizationId,
translation: dependency.ref.translation,
})
);
break;
default:
// Do not throw for unknown dependency types
// as it might mean we are lacking behind the API version
break;
}
});
} else {
// Push a dummy tag, as the v1 is only using the first tag
tags.push(
getCacheTag({
tag: 'computed-document',
space: inContext.spaceId,
sourceType: source.type,
})
);
}
// We invalidate the computed content when a new version of the integration is deployed.
if (source.type.startsWith('integration:')) {
const integration = source.type.split(':')[1];
tags.push(
getCacheTag({
tag: 'integration',
integration,
})
);
}
return tags;
}
@@ -13,11 +13,12 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"types": [
"bun-types" // add Bun global
]
},
"include": ["src/**/*.ts"],
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"]
}
+26
View File
@@ -1,5 +1,31 @@
# @gitbook/colors
## 0.3.3
### Patch Changes
- c3f6b8c: Update chroma ratio per step
- 5e975ab: Fix code highlighting for HTTP
- f7a3470: Change lightness check for color step 9 to allow input colors with a higher-than-needed contrast
## 0.3.2
### Patch Changes
- cdffd7c: Desaturate text colors by decreasing chroma for the last steps of the color scale
## 0.3.1
### Patch Changes
- fb90eb0: Reduce chroma of first color scale step
## 0.3.0
### Minor Changes
- 4f0a772: Override tint lightness if supplied color is out of bounds
## 0.2.0
### Minor Changes
+2 -7
View File
@@ -8,7 +8,7 @@
"default": "./dist/index.js"
}
},
"version": "0.2.0",
"version": "0.3.3",
"devDependencies": {
"typescript": "^5.5.3"
},
@@ -17,10 +17,5 @@
"typecheck": "tsc --noEmit",
"dev": "tsc -w"
},
"files": [
"dist",
"src",
"README.md",
"CHANGELOG.md"
]
"files": ["dist", "src", "README.md", "CHANGELOG.md"]
}
+37 -5
View File
@@ -1,4 +1,4 @@
import { DARK_BASE, LIGHT_BASE, DEFAULT_TINT_COLOR } from './colors';
import { DARK_BASE, DEFAULT_TINT_COLOR, LIGHT_BASE } from './colors';
type ColorShades = {
[key: string]: string;
@@ -178,12 +178,13 @@ export function colorScale(
background = darkMode ? DARK_BASE : LIGHT_BASE,
foreground = darkMode ? LIGHT_BASE : DARK_BASE,
mix,
}: ColorScaleOptions = {},
}: ColorScaleOptions = {}
) {
const baseColor = rgbToOklch(hexToRgbArray(hex));
const mixColor = mix?.color ? rgbToOklch(hexToRgbArray(mix.color)) : null;
const foregroundColor = rgbToOklch(hexToRgbArray(foreground));
const backgroundColor = rgbToOklch(hexToRgbArray(background));
let mapping = darkMode ? colorMixMapping.dark : colorMixMapping.light;
if (mixColor && mix?.ratio && mix.ratio > 0) {
// If defined, we mix in a (tiny) bit of the mix color with the base color.
@@ -192,7 +193,20 @@ export function colorScale(
baseColor.H = mix.color === DEFAULT_TINT_COLOR ? baseColor.H : mixColor.H;
}
const mapping = darkMode ? colorMixMapping.dark : colorMixMapping.light;
if (
(darkMode && baseColor.L < backgroundColor.L) ||
(!darkMode && baseColor.L > backgroundColor.L)
) {
// If the supplied color is outside of our lightness bounds, use the supplied color's lightness.
// This is mostly used to allow darker-than-dark backgrounds for brands that specifically want that look.
const difference = (backgroundColor.L - baseColor.L) / backgroundColor.L;
backgroundColor.L = baseColor.L;
// At the edges of the scale, the subtle lightness changes stop being perceptible. We need to amp up our mapping to still stand out.
const amplifier = 1;
mapping = mapping.map((step, index) =>
index < 9 ? step + step * amplifier * difference : step
);
}
const result = [];
@@ -200,13 +214,31 @@ export function colorScale(
const targetL =
foregroundColor.L * mapping[index] + backgroundColor.L * (1 - mapping[index]);
if (index === 8 && !mix && Math.abs(baseColor.L - targetL) < 0.2) {
if (
index === 8 &&
!mix &&
(darkMode ? targetL - baseColor.L < 0.2 : baseColor.L - targetL < 0.2)
) {
// Original colour is close enough to target, so let's use the original colour as step 9.
result.push(hex);
continue;
}
const chromaRatio = index < 8 ? (index + 1) * 0.05 : 1;
const chromaRatio = (() => {
switch (index) {
// Step 9 and 10 have max chroma, meaning they are fully saturated.
case 8:
case 9:
return 1;
// Step 11 and 12 have a reduced chroma
case 10:
return 0.4;
case 11:
return 0.1;
default:
return index * 0.05;
}
})();
const shade = {
L: targetL, // Blend lightness
+1 -1
View File
@@ -13,7 +13,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react",
"jsx": "react-jsx",
"incremental": true,
"types": [
"bun-types" // add Bun global
+1 -2
View File
@@ -17,12 +17,11 @@ Object.entries(emojis).forEach(([key, value]) => {
if (emoji && key !== emoji) {
output[key] = emoji;
} else if (!emoji) {
console.log('No emoji for', key);
}
});
fs.mkdirSync(path.resolve(__dirname, 'dist'), { recursive: true });
fs.writeFileSync(
path.resolve(__dirname, 'dist/index.ts'),
`export const emojiCodepoints: Record<string, string> = ${JSON.stringify(output, null, 4)};`,
`export const emojiCodepoints: Record<string, string> = ${JSON.stringify(output, null, 4)};`
);
+2
View File
@@ -0,0 +1,2 @@
dist/
src/data/*.json
+7
View File
@@ -0,0 +1,7 @@
# @gitbook/fonts
## 0.1.0
### Minor Changes
- fbfcca5: Initial version of the package
+3
View File
@@ -0,0 +1,3 @@
# `@gitbook/fonts`
Utilities to lookup default fonts supported by GitBook.
+91
View File
@@ -0,0 +1,91 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { APIv2 } from 'google-font-metadata';
import { CustomizationDefaultFont } from '@gitbook/api';
import type { FontDefinitions } from '../src/types';
const googleFontsMap: { [fontName in CustomizationDefaultFont]: string } = {
[CustomizationDefaultFont.Inter]: 'inter',
[CustomizationDefaultFont.FiraSans]: 'fira-sans-extra-condensed',
[CustomizationDefaultFont.IBMPlexSerif]: 'ibm-plex-serif',
[CustomizationDefaultFont.Lato]: 'lato',
[CustomizationDefaultFont.Merriweather]: 'merriweather',
[CustomizationDefaultFont.NotoSans]: 'noto-sans',
[CustomizationDefaultFont.OpenSans]: 'open-sans',
[CustomizationDefaultFont.Overpass]: 'overpass',
[CustomizationDefaultFont.Poppins]: 'poppins',
[CustomizationDefaultFont.Raleway]: 'raleway',
[CustomizationDefaultFont.Roboto]: 'roboto',
[CustomizationDefaultFont.RobotoSlab]: 'roboto-slab',
[CustomizationDefaultFont.SourceSansPro]: 'source-sans-3',
[CustomizationDefaultFont.Ubuntu]: 'ubuntu',
[CustomizationDefaultFont.ABCFavorit]: 'inter',
};
/**
* Scripts to generate the list of all icons.
*/
async function main() {
// @ts-expect-error - we build the object
const output: FontDefinitions = {};
for (const font of Object.values(CustomizationDefaultFont)) {
const googleFontName = googleFontsMap[font];
const fontMetadata = APIv2[googleFontName.toLowerCase()];
if (!fontMetadata) {
throw new Error(`Font ${googleFontName} not found`);
}
output[font] = {
font: googleFontName,
unicodeRange: fontMetadata.unicodeRange,
variants: {
'400': {},
'700': {},
},
};
Object.keys(output[font].variants).forEach((weight) => {
const variants = fontMetadata.variants[weight];
const normalVariant = variants.normal;
if (!normalVariant) {
throw new Error(`Font ${googleFontName} has no normal variant`);
}
output[font].variants[weight] = {};
Object.entries(normalVariant).forEach(([script, url]) => {
output[font].variants[weight][script] = url.url.woff;
});
});
}
await writeDataFile('fonts', JSON.stringify(output, null, 2));
}
/**
* We write both in dist and src as the build process might have happen already
* and tsc doesn't copy the files.
*/
async function writeDataFile(name, content) {
const srcData = path.resolve(__dirname, '../src/data');
const distData = path.resolve(__dirname, '../dist/data');
// Ensure the directories exists
await Promise.all([
fs.mkdir(srcData, { recursive: true }),
fs.mkdir(distData, { recursive: true }),
]);
await Promise.all([
fs.writeFile(path.resolve(srcData, `${name}.json`), content),
fs.writeFile(path.resolve(distData, `${name}.json`), content),
]);
}
main().catch((error) => {
console.error(`Error generating icons list: ${error}`);
process.exit(1);
});
+31
View File
@@ -0,0 +1,31 @@
{
"name": "@gitbook/fonts",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"development": "./src/index.ts",
"default": "./dist/index.js"
}
},
"version": "0.1.0",
"dependencies": {
"@gitbook/api": "catalog:"
},
"devDependencies": {
"google-font-metadata": "^6.0.3",
"typescript": "^5.5.3"
},
"scripts": {
"generate": "bun ./bin/generate.js",
"build": "tsc --project tsconfig.build.json",
"typecheck": "tsc --noEmit",
"dev": "tsc -w",
"clean": "rm -rf ./dist && rm -rf ./src/data",
"unit": "bun test"
},
"files": ["dist", "src", "bin", "README.md", "CHANGELOG.md"],
"engines": {
"node": ">=20.0.0"
}
}
@@ -0,0 +1,57 @@
// Bun Snapshot v1, https://goo.gl/fbAQLP
exports[`getDefaultFont should return correct object for Latin text 1`] = `
{
"font": "Inter",
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hjp-Ek-_0ew.woff",
}
`;
exports[`getDefaultFont should return correct object for Cyrillic text 1`] = `
{
"font": "Inter",
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZthjp-Ek-_0ewmM.woff",
}
`;
exports[`getDefaultFont should return correct object for Greek text 1`] = `
{
"font": "Inter",
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZxhjp-Ek-_0ewmM.woff",
}
`;
exports[`getDefaultFont should handle mixed script text 1`] = `
{
"font": "Inter",
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZthjp-Ek-_0ewmM.woff",
}
`;
exports[`getDefaultFont should handle different font weights: regular 1`] = `
{
"font": "Inter",
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hjp-Ek-_0ew.woff",
}
`;
exports[`getDefaultFont should handle different font weights: bold 1`] = `
{
"font": "Inter",
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuFuYAZ9hjp-Ek-_0ew.woff",
}
`;
exports[`getDefaultFont should handle different fonts: inter 1`] = `
{
"font": "Inter",
"url": "https://fonts.gstatic.com/s/inter/v18/UcCO3FwrK3iLTeHuS_nVMrMxCp50SjIw2boKoduKmMEVuLyfAZ9hjp-Ek-_0ew.woff",
}
`;
exports[`getDefaultFont should handle different fonts: roboto 1`] = `
{
"font": "Roboto",
"url": "https://fonts.gstatic.com/s/roboto/v32/KFOmCnqEu92Fr1Mu4mxMKTU1Kg.woff",
}
`;
+5
View File
@@ -0,0 +1,5 @@
import type { FontDefinitions } from './types';
import rawFonts from './data/fonts.json' with { type: 'json' };
export const fonts: FontDefinitions = rawFonts;
+119
View File
@@ -0,0 +1,119 @@
import { describe, expect, it } from 'bun:test';
import { CustomizationDefaultFont } from '@gitbook/api';
import { getDefaultFont } from './getDefaultFont';
describe('getDefaultFont', () => {
it('should return null for invalid font', () => {
const result = getDefaultFont({
font: 'invalid-font' as CustomizationDefaultFont,
text: 'Hello',
weight: 400,
});
expect(result).toBeNull();
});
it('should return null for invalid weight', () => {
const result = getDefaultFont({
font: CustomizationDefaultFont.Inter,
text: 'Hello',
weight: 999 as any,
});
expect(result).toBeNull();
});
it('should return null for text not supported by any script', () => {
const result = getDefaultFont({
font: CustomizationDefaultFont.Inter,
text: '😀', // Emoji not supported by Inter
weight: 400,
});
expect(result).toBeNull();
});
it('should return correct object for Latin text', () => {
const result = getDefaultFont({
font: CustomizationDefaultFont.Inter,
text: 'Hello World',
weight: 400,
});
expect(result).not.toBeNull();
expect(result?.font).toBe(CustomizationDefaultFont.Inter);
expect(result).toMatchSnapshot();
});
it('should return correct object for Cyrillic text', () => {
const result = getDefaultFont({
font: CustomizationDefaultFont.Inter,
text: 'Привет мир',
weight: 400,
});
expect(result).not.toBeNull();
expect(result?.font).toBe(CustomizationDefaultFont.Inter);
expect(result).toMatchSnapshot();
});
it('should return correct object for Greek text', () => {
const result = getDefaultFont({
font: CustomizationDefaultFont.Inter,
text: 'Γεια σας',
weight: 400,
});
expect(result).not.toBeNull();
expect(result?.font).toBe(CustomizationDefaultFont.Inter);
expect(result).toMatchSnapshot();
});
it('should handle mixed script text', () => {
const result = getDefaultFont({
font: CustomizationDefaultFont.Inter,
text: 'Hello Привет',
weight: 400,
});
expect(result).not.toBeNull();
expect(result?.font).toBe(CustomizationDefaultFont.Inter);
expect(result).toMatchSnapshot();
});
it('should handle different font weights', () => {
const regular = getDefaultFont({
font: CustomizationDefaultFont.Inter,
text: 'Hello',
weight: 400,
});
const bold = getDefaultFont({
font: CustomizationDefaultFont.Inter,
text: 'Hello',
weight: 700,
});
expect(regular).not.toBeNull();
expect(bold).not.toBeNull();
expect(regular).toMatchSnapshot('regular');
expect(bold).toMatchSnapshot('bold');
});
it('should handle empty string', () => {
const result = getDefaultFont({
font: CustomizationDefaultFont.Inter,
text: '',
weight: 400,
});
expect(result).toBeNull();
});
it('should handle different fonts', () => {
const inter = getDefaultFont({
font: CustomizationDefaultFont.Inter,
text: 'Hello',
weight: 400,
});
const roboto = getDefaultFont({
font: CustomizationDefaultFont.Roboto,
text: 'Hello',
weight: 400,
});
expect(inter).not.toBeNull();
expect(roboto).not.toBeNull();
expect(inter).toMatchSnapshot('inter');
expect(roboto).toMatchSnapshot('roboto');
});
});
+112
View File
@@ -0,0 +1,112 @@
import type { CustomizationDefaultFont } from '@gitbook/api';
import { fonts } from './fonts';
import type { FontWeight } from './types';
/**
* Get the URL to load a font for a text.
*/
export function getDefaultFont(input: {
/**
* GitBook font to use.
*/
font: CustomizationDefaultFont;
/**
* Text to display with the font.
*/
text: string;
/**
* Font weight to use.
*/
weight: FontWeight;
}): { font: string; url: string } | null {
if (!input.text.trim()) {
return null;
}
const fontDefinition = fonts[input.font];
if (!fontDefinition) {
return null;
}
const variant = fontDefinition.variants[`${input.weight}`];
if (!variant) {
return null;
}
const script = getBestUnicodeRange(input.text, fontDefinition.unicodeRange);
if (!script) {
return null;
}
return variant[script]
? {
font: input.font,
url: variant[script],
}
: null;
}
/**
* Determine which named @font-face unicode-range covers
* the greatest share of the characters in `text`.
*
* @param text The text you want to inspect.
* @param ranges An object whose keys are range names and whose
* values are CSS-style comma-separated unicode-range
* declarations (e.g. "U+0370-03FF,U+1F00-1FFF").
* @returns The key of the best-matching range, or `null`
* when nothing matches at all.
*/
function getBestUnicodeRange(text: string, ranges: Record<string, string>): string | null {
// ---------- helper: parse "U+XXXX" or "U+XXXX-YYYY" ----------
const parseOne = (token: string): [number, number] | null => {
token = token.trim().toUpperCase();
if (!token.startsWith('U+')) return null;
const body = token.slice(2); // drop "U+"
const [startHex, endHex] = body.split('-');
const start = Number.parseInt(startHex, 16);
const end = endHex ? Number.parseInt(endHex, 16) : start;
if (Number.isNaN(start) || Number.isNaN(end) || end < start) return null;
return [start, end];
};
// ---------- helper: build lookup table ----------
const parsed: Record<string, [number, number][]> = {};
for (const [label, list] of Object.entries(ranges)) {
parsed[label] = list
.split(',')
.map(parseOne)
.filter((x): x is [number, number] => x !== null);
}
// ---------- tally code-point hits ----------
const hits: Record<string, number> = Object.fromEntries(Object.keys(parsed).map((k) => [k, 0]));
for (let i = 0; i < text.length; ) {
const cp = text.codePointAt(i)!;
i += cp > 0xffff ? 2 : 1; // advance by 1 UTF-16 code-unit (or 2 for surrogates)
for (const [label, rangesArr] of Object.entries(parsed)) {
if (rangesArr.some(([lo, hi]) => cp >= lo && cp <= hi)) {
hits[label]++;
}
}
}
// ---------- choose the "best" ----------
let winner: string | null = null;
let maxCount = 0;
for (const [label, count] of Object.entries(hits)) {
if (count > maxCount) {
maxCount = count;
winner = label;
}
}
return maxCount > 0 ? winner : null;
}
+2
View File
@@ -0,0 +1,2 @@
export * from './getDefaultFont';
export * from './types';
+17
View File
@@ -0,0 +1,17 @@
import type { CustomizationDefaultFont } from '@gitbook/api';
export type FontWeight = 400 | 700;
export type FontDefinition = {
font: string;
unicodeRange: {
[script: string]: string;
};
variants: {
[weight in string]: {
[script: string]: string;
};
};
};
export type FontDefinitions = { [fontName in CustomizationDefaultFont]: FontDefinition };
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "src/**/*.test.ts"]
}
@@ -1,20 +1,23 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"lib": ["es2023"],
"module": "ESNext",
"target": "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowJs": true,
"noEmit": false,
"declaration": true,
"outDir": "dist",
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"types": ["./.wrangler/types/runtime.d.ts"]
"types": [
"bun-types" // add Bun global
]
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
+9
View File
@@ -0,0 +1,9 @@
{
"extends": ["//"],
"tasks": {
"generate": {
"inputs": ["bin/**/*", "package.json"],
"outputs": ["src/data/*.json", "dist/data/*.json"]
}
}
}
-8
View File
@@ -1,8 +0,0 @@
# next.js
/.next/
# vercel
.vercel
# cloudflare
.open-next
-7
View File
@@ -1,7 +0,0 @@
# gitbook-v2
## 0.1.0
### Minor Changes
- cfccc44: Setup structure and deployment for new version
-104
View File
@@ -1,104 +0,0 @@
# GitBook V2
This package contains the code for an experiment new version of the GitBook renderer.
The goals of this new version are:
- Optimize rendering to leverage Next.js ISR and static rendering
- Leverage Node.js runtime and bigger instances to render bigger content
- Evaluate Vercel and Cloudflare deployment options
## Deployment
#### Vercel
The new version is deployed automatically to Vercel using a Github Action on every push.
#### Cloudflare with Opennext-js
The new version is deployed automatically to Cloudflare using a Github Action on every push.
## Potential issues
- [ ] We can't use `dynamicIO` because our root layout needs to fetch the params (https://github.com/vercel/next.js/discussions/71927)
- [ ] How to do `nonce` for static resources when we can't use `headers()` (dynamic API) (https://nextjs.org/docs/app/building-your-application/configuring/content-security-policy)
## Structure
#### Middleware:
Goals:
- Route between dynamic and static routes
- If a token is in the cookies => dynamic route
- If a token is in the url => dynamic route
- Else => static route
- High performances:
- It should not rely on data fetching
#### URLs to support:
Content URLs:
- `docs.company.com/page`
- Middleware: rewrite to `/static/url/docs.company.com/page`
- Page component renders the page
- `docs.company2.com/section/page`
- Middleware: rewrite to `/static/url/docs.company2.com/section/page`
- Page component renders the page
- `docs.company3.com/section/sitespace/page`
- Middleware: rewrite to `/static/url/docs.company3.com/section/sitespace/page`
- Page component renders the page
Content URLs (adaptive content or visitor authentication):
- `docs.company.com/page`
- Middleware:
- If a token is in the cookies or url => dynamic route
- Else => rewrite to `/dynamic/url/docs.company.com/page`
- Page component renders the page
- Read the token from the url query params or cookies
PDF export URLs:
- `docs.company.com/page/~gitbook/pdf`
- Middleware: rewrite to `/dynamic/pdf/docs.company.com/page`
- Page component renders the page
Images resizing:
- `docs.company.com/~gitbook/image`
- Middleware: rewrite to `/dynamic/image/docs.company.com`
- Route handlers process the request
Preview URLs:
- `open.gitbook.com/sites/site_abc/page` (or `open.gitbook.com/~site/site_abc/page` to stay compatible?)
- Middleware: no rewrite
- Page component renders the page (dynamic?)
- Read the token from the url query params
- Read customizations override from the url query params
Space PDF URLs:
- `open.gitbook.com/spaces/space_abc/pdf`
- Middleware: no rewrite
- Page component renders the page (dynamic?)
- Read the token from the url query params
- Read customizations override from the url query params
Development:
- `localhost:3000/sites/site_abc/page`
#### Routes:
- Content
- [ ] `/static/url/[[...url]]/page.tsx`
- [ ] `/dynamic/url/[[...url]]/page.tsx`
- Images resizing
- [ ] `/dynamic/image/[hostname]/route.ts`
- PDF export
- [ ] `/dynamic/pdf/[[...url]]/page.tsx`
- [ ] `/spaces/[space]/pdf/page.tsx`
-5
View File
@@ -1,5 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
-15
View File
@@ -1,15 +0,0 @@
// @ts-check
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
experimental: {
useCache: true,
// We can't use dynamicIO because it doesn't accept reading params in the root layout
// dynamicIO: true,
},
};
export default nextConfig;
-30
View File
@@ -1,30 +0,0 @@
// default open-next.config.ts file created by @opennextjs/cloudflare
import cache from '@opennextjs/cloudflare/kvCache';
const config = {
default: {
override: {
wrapper: 'cloudflare-node',
converter: 'edge',
incrementalCache: async () => cache,
tagCache: 'dummy',
queue: 'dummy',
},
},
middleware: {
external: true,
override: {
wrapper: 'cloudflare-edge',
converter: 'edge',
proxyExternalRequest: 'fetch',
},
},
dangerous: {
enableCacheInterception: false,
},
};
export default config;
-22
View File
@@ -1,22 +0,0 @@
{
"name": "gitbook-v2",
"version": "0.1.0",
"private": true,
"dependencies": {
"next": "canary",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@gitbook/api": "^0.93.0"
},
"devDependencies": {
"@opennextjs/cloudflare": "^0.4.3"
},
"scripts": {
"dev:v2": "next",
"build": "next build",
"build:v2": "next build",
"start": "next start",
"build:v2:cloudflare": "opennextjs-cloudflare",
"dev:v2:cloudflare": "wrangler dev --port 8771"
}
}
@@ -1,15 +0,0 @@
import { SiteContentLayout } from '@/components/routes/SiteContentLayout';
import { createDynamicSiteContext } from '@/lib/context';
export default async function RootLayout({
params,
children,
}: {
params: Promise<{ url: string[] }>;
children: React.ReactNode;
}) {
const { url } = await params;
const context = await createDynamicSiteContext(url);
return <SiteContentLayout context={context}>{children}</SiteContentLayout>;
}
@@ -1,9 +0,0 @@
import { SiteContentPage } from '@/components/routes/SiteContentPage';
import { createDynamicSiteContext } from '@/lib/context';
export default async function Page({ params }: { params: Promise<{ url: string[] }> }) {
const { url } = await params;
const context = await createDynamicSiteContext(url);
return <SiteContentPage context={context} />;
}
@@ -1,21 +0,0 @@
import { unstable_cacheTag as cacheTag } from 'next/cache';
import { SiteContentLayout } from '@/components/routes/SiteContentLayout';
import { getSiteCacheTag } from '@/lib/cache';
import { createStaticSiteContext } from '@/lib/context';
export default async function RootLayout({
params,
children,
}: {
params: Promise<{ url: string[] }>;
children: React.ReactNode;
}) {
'use cache';
const { url } = await params;
const context = await createStaticSiteContext(url);
cacheTag(getSiteCacheTag(context.siteId));
return <SiteContentLayout context={context}>{children}</SiteContentLayout>;
}
@@ -1,15 +0,0 @@
import { unstable_cacheTag as cacheTag } from 'next/cache';
import { createStaticSiteContext } from '@/lib/context';
import { getSiteCacheTag } from '@/lib/cache';
import { SiteContentPage } from '@/components/routes/SiteContentPage';
export default async function Page({ params }: { params: Promise<{ url: string[] }> }) {
'use cache';
const { url } = await params;
const context = await createStaticSiteContext(url);
cacheTag(getSiteCacheTag(context.siteId));
return <SiteContentPage context={context} />;
}
@@ -1,27 +0,0 @@
import { GitBookSiteContext } from '@/lib/context';
/**
* Layout component to render the site content.
*/
export async function SiteContentLayout({
context,
children,
}: {
context: GitBookSiteContext;
children: React.ReactNode;
}) {
const { api } = context;
const { data: publishedSite } = await api.orgs.getPublishedContentSite(
context.organizationId,
context.siteId,
);
return (
<html lang="en">
<body>
<h1>{publishedSite.site.title}</h1>
{children}
</body>
</html>
);
}
@@ -1,12 +0,0 @@
import { GitBookSiteContext } from '@/lib/context';
export async function SiteContentPage({ context }: { context: GitBookSiteContext }) {
const { api } = context;
const { data } = await api.orgs.getPublishedContentSite(context.organizationId, context.siteId);
return (
<div>
<p>Found site {data.site.id}</p>
</div>
);
}
-3
View File
@@ -1,3 +0,0 @@
export function getSiteCacheTag(siteId: string) {
return `sites/${siteId}`;
}
-112
View File
@@ -1,112 +0,0 @@
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';
import { GitBookAPI } from '@gitbook/api';
import { GITBOOK_API_TOKEN, GITBOOK_API_URL, GITBOOK_USER_AGENT } from '@/lib/env';
/**
* Generic context about rendering content.
*/
export interface GitBookContext {
/**
* API client to fetch data from GitBook.
*/
api: GitBookAPI;
}
/**
* Context when rendering a site.
*/
export interface GitBookSiteContext extends GitBookContext {
/**
* ID of the organization.
*/
organizationId: string;
/**
* Site ID.
*/
siteId: string;
}
/**
* Create a site context, when rendering a static page.
*/
export async function createStaticSiteContext(url: string[]): Promise<GitBookSiteContext> {
const context = createStaticContext();
return fetchSiteContext(url, context);
}
/**
* Create a site context, when rendering a dynamic page.
*/
export async function createDynamicSiteContext(url: string[]): Promise<GitBookSiteContext> {
const context = await createDynamicContext();
return fetchSiteContext(url, context);
}
/**
* Fetch the context of a site for a given URL and a base context.
*/
async function fetchSiteContext(
urlParts: string[],
baseContext: GitBookContext,
): Promise<GitBookSiteContext> {
const { api } = baseContext;
const url = getURLFromParams(urlParts);
const { data } = await api.urls.getPublishedContentByUrl({
url,
});
if ('redirect' in data) {
redirect(data.redirect);
}
return {
...baseContext,
api: new GitBookAPI({
endpoint: api.endpoint,
authToken: data.apiToken,
userAgent: api.userAgent,
}),
siteId: data.site,
organizationId: data.organization,
};
}
/**
* Create the base context when rendering statically.
*/
function createStaticContext(): GitBookContext {
const api = new GitBookAPI({
authToken: GITBOOK_API_TOKEN,
endpoint: GITBOOK_API_URL,
userAgent: GITBOOK_USER_AGENT,
});
return {
api,
};
}
/**
* Create the base context when rendering dynamically.
* The context will depend on the request.
*/
async function createDynamicContext(): Promise<GitBookContext> {
const headersSet = await headers();
const api = new GitBookAPI({
authToken: headersSet.get('x-gitbook-token') ?? GITBOOK_API_TOKEN,
endpoint: headersSet.get('x-gitbook-api') ?? GITBOOK_API_URL,
userAgent: GITBOOK_USER_AGENT,
});
return {
api,
};
}
function getURLFromParams(input: string[]) {
const url = new URL('https://' + input.join('/'));
return url.toString();
}
-15
View File
@@ -1,15 +0,0 @@
/**
* Default GitBook API URL endpoint.
*/
export const GITBOOK_API_URL = process.env.GITBOOK_API_URL ?? 'https://api.gitbook.com';
/**
* Default GitBook API token.
* It can be use to avoid rate-limiting.
*/
export const GITBOOK_API_TOKEN = process.env.GITBOOK_API_TOKEN ?? null;
/**
* User agent to use for API requests.
*/
export const GITBOOK_USER_AGENT = process.env.GITBOOK_USER_AGENT ?? 'GitBook-Open/2.0.0';
-11
View File
@@ -1,11 +0,0 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export const config = {
matcher: ['/((?!_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)'],
};
export function middleware(request: NextRequest) {
// No rewrite
return NextResponse.next();
}
-28
View File
@@ -1,28 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"noEmit": true,
"incremental": true,
"module": "esnext",
"esModuleInterop": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"],
"@v1/*": ["../gitbook/src/*"]
}
},
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
-5
View File
@@ -1,5 +0,0 @@
main = ".open-next/worker.js"
name = "gitbook-v2"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
assets = { directory = ".open-next/assets", binding = "ASSETS" }
-30
View File
@@ -1,30 +0,0 @@
# Configurations
### API ###
## API token to use when looking up the published content
# GITBOOK_API_URL=https://api.gitbook.com
# GITBOOK_TOKEN=xxx
## User agent to use when making requests to the API
# GITBOOK_USER_AGENT=GitBook/1.0.0
# GITBOOK_USER_AGENT_COMMENT=something
### URL of the application ###
# NEXT_PUBLIC_GITBOOK_APP_URL=https://app.gitbook.com
### Image resizing ###
# GITBOOK_IMAGE_RESIZE_SIGNING_KEY=1234567890
# GITBOOK_IMAGE_RESIZE_URL=https://mycompany.com/cdn-cgi/image/
### SEO ###
# GITBOOK_BLOCK_SEARCH_INDEXATION=true
## Caching
# GITBOOK_OUTPUT_CACHE=true
### Sentry ###
# SENTRY_DSN=xxx
### Silent logs
# SILENT=true
-31
View File
@@ -1,31 +0,0 @@
{
"extends": "next/core-web-vitals",
"plugins": ["import"],
"rules": {
"import/order": [
"error",
{
"groups": ["builtin", "external", ["internal", "parent", "sibling", "index"]],
"newlines-between": "always",
"distinctGroup": true,
"pathGroups": [
{
"pattern": "@/**",
"group": "external",
"position": "after"
},
{
"pattern": "@gitbook/**",
"group": "external",
"position": "after"
}
],
"alphabetize": {
"order": "asc",
"caseInsensitive": true
}
}
],
"@next/next/no-img-element": ["off"]
}
}
+6 -3
View File
@@ -1,3 +1,4 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# testing
@@ -23,9 +24,6 @@ next-env.d.ts
# visual tests
screenshots/
# Sentry Config File
.sentryclirc
/test-results/
/playwright-report/
/blob-report/
@@ -34,3 +32,8 @@ screenshots/
# Generated public files
/public/~gitbook/static/*
!/public/~gitbook/static/images
# cloudflare
.open-next
.wrangler
worker-configuration.d.ts
+508 -1
View File
@@ -1,5 +1,512 @@
# gitbook
## 0.15.0
### Minor Changes
- d130532: Suggest questions in the current space context
### Patch Changes
- 44f4151: Fix close buttons tooltips
- d903273: Strip visitor params from URL
- df7de8f: Fix an issue where links were not rendering properly in Ask AI answers
- d270b4a: All tracking is now disabled for dynamic routes such as the site preview.
- b1f608b: Implement basic URL scheme for assistant with ask
- d024658: Prevent page breaks inside blocks when printing
- 185cdb4: Fix scrolling to anchor positionning
- 5c49235: Remove highlighting in Safari for PowerShell and C++ to avoid page crash until next version with bug fix is released
- f1a6dec: Update OpenAPI parser
- 2cdba53: Upgrade to Tailwind v4
- Updated dependencies [2cdba53]
- Updated dependencies [f1a6dec]
- @gitbook/react-math@0.6.1
- @gitbook/icons@0.2.2
- @gitbook/openapi-parser@3.0.0
- @gitbook/react-contentkit@0.7.3
- @gitbook/react-openapi@1.3.5
## 0.14.1
### Patch Changes
- 7c951ef: 'Support new colors in text formatting
- 39c4f76: Add fullwidth page option
- eb1bd3a: AI response feedback buttons
- 2ba7e54: Fix headings styles in hint block
- 250c194: Add nosnippet to announcement banner.
- 4aeb81b: Fix search input styling & text cutoff
- 68f0dbc: Add confirmation modal to contentkit button
- 611e286: Remove padding on first Heading in Columns
- b0c534f: Tweaks to AI Chat and AI Actions Dropdown
- Updated dependencies [b5ad0ce]
- @gitbook/openapi-parser@2.2.2
- @gitbook/react-openapi@1.3.4
## 0.14.0
### Minor Changes
- 0003030: Implement AI actions dropdown
- acb9f53: New search layout
### Patch Changes
- 334cfdd: Fix responsive class for SearchInput button
- 6816f0f: add browserlists and fix old browser css-masks
- 9ee9082: Refactor icon loading state in AIAction components
- 52ab368: Reposition AI Actions dropdown
- 7212345: Fix AI Actions dropdown and LLM integration
- 8daede5: Ensure all content links are resolved relatively to preview.
- ed684c1: Move AI Actions markdown fetching to client-side
- 9cc5a78: Generalise keyboard shortcuts, add Cmd+J to AI Chat
- Updated dependencies [6816f0f]
- Updated dependencies [1738677]
- @gitbook/icons@0.2.1
- @gitbook/openapi-parser@2.2.1
- @gitbook/react-contentkit@0.7.2
- @gitbook/react-openapi@1.3.3
## 0.13.1
### Patch Changes
- f5894bc: Fix incorrect revision ID in Markdown fetch logic
- 1b59e7c: Styling improvements to AI Chat Followup questions
- Updated dependencies [bd553bc]
- @gitbook/openapi-parser@2.2.0
- @gitbook/react-openapi@1.3.2
## 0.13.0
### Minor Changes
- af98402: Add support for inline icons.
- 7d3fe23: Add circular corners and depth styling
- f3affc3: Display MCP tool calls in AI chat.
- fa12f9e: Support dark-mode specific page cover image
- df848ef: Add support for icons in buttons.
- b7a0db3: Fix rendering of ogimage with SVG logos.
- c3b620e: Best effort at preserving current variant when navigating between sections by matching the pathname against site spaces in the new section.
- 4fb2a4a: Rework full-width layout, add support for full-width page option
- df848ef: Add support for text alignment for headings and paragraphs.
- f033734: Add support for site customization option to change how external links open.
- 7b38f89: Enable AI chat when AI mode is configured to assistant
- 8d65983: Add AI chat
### Patch Changes
- 42d88da: Fix UX issue about highlighting the search term in search result sections
- 6aa3ff9: Fix three small visual issues
- Fix sidebar showing on `no-toc` pages in the gradient theme
- Fix variant selector truncating incorrectly in header when sections are present
- Fix page cover alignment on `lg` screens without TOC
- 015615d: Respect fullWidth and defaultWidth for images
- e8fb84d: Fix hash with align in columns
- 4721403: Hide scrollbar on sections
- d410381: Add docs.testgitbook.com to ADAPTIVE_CONTENT_HOSTS list
- 7d5a6d2: fix nested a tag causing hydration error
- bc1eca8: Error handling for AI Chat
- b3a7ad6: fix href being empty in TOC
- dc4268d: Fix navigation between sections/variants when previewing a site in v2
- 58d7f3c: Fix revision id for computed content
- 11a6511: Fix crash when integration script fails to render block.
- 7a00880: Improve support for OAuth2 security type
- c0ee60e: Adds Columns layout block to GBO
- 9316ccd: Update Models page styling
- 42d43e0: Show scrollbars
- 72cd0e5: Optimize performances by using a smarter per-request cache arround data cached functions
- af66ff7: add a force-revalidate api route to force bust the cache in case of errors
- e2afc07: Fix resolution of page by resolving site redirects before space redirects
- 88a35ed: Fix crash when integration is triggering invalid requests.
- 711cf38: Optimize the fetch of revision files by using only the getRevision cache.
- f58b904: encode customization header
- 4f5fec7: Fix CodeBlock layout
- 3bfe347: Show tabs when there is a single section group present
- a7a713b: Scroll to active TOC when clicking a link
- ba0094a: fix ISR on preview env
- 521052d: Fix concurrent execution in Vercel causing pages to not be attached to the proper tags.
- 33726c8: Generate a llms-full.txt version of the docs site
- 500c8cb: Don't crash ogimage generation on RTL text, as a workaround until we can support it.
- 6859f7d: Fix rendering of ogimage when logo or icon are AVIF images.
- a28a997: Add margin to adjacent buttons
- 67998b6: Fix ogimage generation failing with some JPEG images.
- 8c0a53a: Fix page group not expanded by default
- dfa8a37: Don't cache unexpected API errors for more than a few minutes.
- 73e0cbb: Fix an issue where PDF export URLs were not keeping their query params.
- e5bac69: Fix markdown page generation for groups
- 6294bbb: add a global error boundary
- b60039b: Fix links to other spaces within a section.
- 59da30f: Add support for cover repositioning
- b403962: Handle nullish OpenAPI mediaTypeObject
- c730845: Fix missing title on button to close the announcement banner.
- 231167d: Make icons for page groups more contrasting
- d99da6a: Ignore case while highlighting search results.
- dae019c: Consistently show variant selector in section bar if site has sections
- dd65987: Include page group children under the .md route
- 57bb146: Make TOC height dynamic based on visible header and footer elements
- 4f7c0ee: Clicking an active TOC item toggles its descendants
- ca3b9ac: Improve AI Chat context popup
- 5726999: Fix viewing a page from a revision
- 392f594: Fix InlineLinkTooltip having a negative impact on performance, especially on larger pages.
- c9373ef: Fix bold header links hover color
- fa3eb07: cache fonts and static image used in OGImage in memory
- e7a591d: Fix border being added to cards
- 427f748: Add metadata for adding site to Apple devices home
- a3a944d: Fix crash during rendering of ogimage for VA sites with default icon.
- 4b67fe5: Add `urlObject.hash` to `linker.toLinkForContent` to pass through URL fragment identifiers, used in search
- caaa692: Allow to zoom images on mobile if relevant
- 902c3c6: apply customization for dynamic context
- b6b5975: Reverse order of feedback smileys
- fbfcca5: Fix ogimage using incorrect Google Font depending on language.
- 2932077: remove trailing slash from linker
- 2350baa: Support for OpenAPI Array request body
- Updated dependencies [957afd9]
- Updated dependencies [7a00880]
- Updated dependencies [11a6511]
- Updated dependencies [fbfcca5]
- Updated dependencies [a0c06a7]
- Updated dependencies [b403962]
- Updated dependencies [1e013cd]
- Updated dependencies [4f5cbfe]
- Updated dependencies [4c9a9d0]
- Updated dependencies [40df91a]
- Updated dependencies [2350baa]
- @gitbook/react-openapi@1.3.1
- @gitbook/react-contentkit@0.7.1
- @gitbook/fonts@0.1.0
- @gitbook/openapi-parser@2.1.5
## 0.12.0
### Minor Changes
- 8339e91: Fix images in reusable content across spaces.
- 326e28e: Design tweaks to code blocks and OpenAPI pages
- 3119066: Add support for reusable content across spaces.
- 7d7806d: Pass SVG images through image resizing without resizing them to serve them from optimal host.
### Patch Changes
- c4ebb3f: Fix openapi-select hover in responses
- aed79fd: Decrease rounding of header logo
- 42ca7e1: Fix openapi CR preview
- e6ddc0f: Fix URL in sitemap
- 5e975ab: Fix code highlighting for HTTP
- 5d504ff: Fix resolution of links in reusable contents
- 95a1f65: Better print layouts: wrap code blocks & force table column auto-sizing
- 0499966: Fix invalid sitemap.xml generated with relative URLs instead of absolute ones
- 2a805cc: Change OpenAPI schema-optional from `info` to `tint` color
- 580101d: Fix schemas disclosure label causing client error
- 12a455d: Fix OpenAPI layout issues
- 97b7c79: Increase logging around caching behaviour causing page crashes.
- 373f18f: Prevent section group popovers from opening on click
- 3f29206: Update the regex for validating site redirect
- 0c973a3: Always link main logo to the root of the site
- ae5f1ab: Change `Dropdown`s to use Radix's `DropdownMenu`
- 0e201d5: Add border to filled sidebar on gradient theme
- dd043df: Revert investigation work around URL caches.
- 89a5816: Fix OpenAPI disclosure label ("Show properties") misalignment on mobile
- Updated dependencies [c3f6b8c]
- Updated dependencies [d00dc8c]
- Updated dependencies [42ca7e1]
- Updated dependencies [326e28e]
- Updated dependencies [5e975ab]
- Updated dependencies [f7a3470]
- Updated dependencies [580101d]
- Updated dependencies [20ebecb]
- Updated dependencies [80cb52a]
- Updated dependencies [cb5598d]
- Updated dependencies [c6637b0]
- Updated dependencies [a3ec264]
- @gitbook/colors@0.3.3
- @gitbook/openapi-parser@2.1.4
- @gitbook/react-openapi@1.3.0
## 0.11.1
### Patch Changes
- Updated dependencies [ebc39e9]
- Updated dependencies [b6b09d4]
- @gitbook/react-openapi@1.2.1
## 0.11.0
### Minor Changes
- d67699a: Add OpenAPI Webhook block
### Patch Changes
- 4b8a621: Show sections tabs only if there is at least two sections
- 8ed1bda: Translate OpenAPI blocks
- 7588cfe: Improve OpenAPIResponses examples and schemas
- Updated dependencies [eeb977f]
- Updated dependencies [3363a18]
- Updated dependencies [d67699a]
- Updated dependencies [8ed1bda]
- Updated dependencies [7588cfe]
- Updated dependencies [ad1dc0b]
- @gitbook/react-openapi@1.2.0
## 0.10.1
### Patch Changes
- Updated dependencies [77397ca]
- @gitbook/cache-tags@0.3.1
## 0.10.0
### Minor Changes
- b62b101: Do not set cookie to identify visitor for insights when disabled.
### Patch Changes
- 95ea22d: Cache AI Page Link summary
- daf41fc: Tweak footer design (and refactor)
- de53946: Fix security issue with injection of "javacript:` url in the back button of PDFs
- b92ecfa: Implement retry logic for the DO cache to prevent when revalidating content.
- 528eee3: Add superscript and subscript text rendering
- aa3357a: Fix OpenAPISchemas description padding
- 168a4fa: Add support for buttons to GitBook.
- 70c4182: Improve OpenAPI schema style
- 2b6c593: Remove stable from x-stability
- 580f7ad: Improve the error message returned by the revalidate endpoint.
- cbd768a: Improve OpenAPI codesample (add OpenAPISelect component)
- c765463: Fix ogimage generation crashing when site is using a custom WOFF2 font
- e59076a: Improve OpenAPI schemas block ungrouped style. Classnames have changed, please refer to this PR to update GBX.
- 29aaba5: Override Scalar's overscroll-behavior
- 90ead98: Better error handling in cache revalidation.
- Updated dependencies [116575c]
- Updated dependencies [cdffd7c]
- Updated dependencies [70c4182]
- Updated dependencies [2b6c593]
- Updated dependencies [cbd768a]
- Updated dependencies [e59076a]
- Updated dependencies [eedefdd]
- Updated dependencies [23cedd2]
- @gitbook/cache-tags@0.3.0
- @gitbook/colors@0.3.2
- @gitbook/react-openapi@1.1.10
- @gitbook/openapi-parser@2.1.3
## 0.9.2
### Patch Changes
- da7b369: Fix missing headers in OpenAPIResponses
- 139a805: Fix OpenAPI enum display
- Updated dependencies [da7b369]
- Updated dependencies [da485f5]
- Updated dependencies [139a805]
- @gitbook/react-openapi@1.1.9
## 0.9.1
### Patch Changes
- fb90eb0: Limit tinted background on bold theme to sites with filled sidebar
- 7d0b422: Handle grouped OpenAPISchemas
- Updated dependencies [7d0b422]
- Updated dependencies [fb90eb0]
- @gitbook/react-openapi@1.1.8
- @gitbook/colors@0.3.1
## 0.9.0
### Minor Changes
- 77fd393: Track event when visitor is opening a search result.
- d70d566: Support site announcement banner
- 77fd393: Track event when clicking announcement banner link.
### Patch Changes
- e84a46a: Fix OpenAPI tabs indicator overflow
- bc90adb: Fix favicon not being displayed in Google because `robots.txt` was preventing the indexation of the image route
- 434af90: Fix image resizing when using the proxy feature in a site.
- c756761: Add breadcrumbs to search results
- 40e8e69: Disallow crawling by web-robots of search/ask URLs
- 77fd393: Fix clicking search results when the site is embedded in an iframe.
- 1505ddb: Fix multiple request examples selector not showing
- 61db166: Add OpenAPI write-only indicator
- 6f71da8: Fix padding in schemas
- fa91eb7: Fix PDF generation when user has dark mode configured.
- 5b1e01c: Support for x-stability property
- 57ca4e0: Fix a crash when a page contains a block of an integration that is no longer installed
- d236bf0: Fix flash when loading sites with dark mode as default theme
- cd99ed5: Fix spec properties rendering and missing keys
- 813b2af: Support for x-enumDescriptions and x-gitbook-enum
- e9fa50d: Trim the search query to avoid showing a loading state when typing
- Updated dependencies [bd35348]
- Updated dependencies [ae78fc5]
- Updated dependencies [7bb37c7]
- Updated dependencies [373183a]
- Updated dependencies [1505ddb]
- Updated dependencies [61db166]
- Updated dependencies [5b1e01c]
- Updated dependencies [cd99ed5]
- Updated dependencies [813b2af]
- Updated dependencies [a25fded]
- @gitbook/react-openapi@1.1.7
- @gitbook/openapi-parser@2.1.2
## 0.8.2
### Patch Changes
- ed07206: Fix OpenAPI path overflow on mobile
- 4dab1c5: Fix alignment of `prominent` search bar on full-width pages
- 6eae764: Support body examples
- 54ee014: Add initial support for loading custom fonts
- d2facb2: Mark properties as optional if not required
- bba2e52: Fix site redirects when it includes a section/variant path
- 4723f03: Restyle section group dropdown
- 24b7808: Fix `prominent` search bar width on `md` screens
- 1fe3286: Fix OpenAPI block overflow issue
- Updated dependencies [48c18c0]
- Updated dependencies [6eae764]
- Updated dependencies [7212973]
- Updated dependencies [d2facb2]
- Updated dependencies [73e2b47]
- Updated dependencies [70be2c6]
- Updated dependencies [fc00b51]
- Updated dependencies [a84b06b]
- @gitbook/openapi-parser@2.1.1
- @gitbook/react-openapi@1.1.6
## 0.8.1
### Patch Changes
- 886e204: Update OpenAPI operation path design
- Updated dependencies [886e204]
- Updated dependencies [4f0a772]
- @gitbook/react-openapi@1.1.5
- @gitbook/colors@0.3.0
## 0.8.0
### Minor Changes
- eec3eed: Add styling for prominent search bar option
### Patch Changes
- 16292de: Display sidebar on no-TOC pages
- 9b5f971: Transparent background for OpenAPI path block
- 99da8df: Optimize favicons and og:image using the image resizer
- b011ea0: Fix rendering of code blocks in Ask AI when being streamed
- 9bc3d50: Info hint background and link color fixes
- 31d800e: Render OpenAPISchemas block
- Updated dependencies [6aaeae2]
- Updated dependencies [c60e9ba]
- Updated dependencies [9108c56]
- Updated dependencies [31d800e]
- Updated dependencies [ff3b708]
- Updated dependencies [f32bf1f]
- Updated dependencies [c9ea239]
- @gitbook/react-contentkit@0.7.0
- @gitbook/react-openapi@1.1.4
- @gitbook/cache-tags@0.2.0
## 0.7.3
### Patch Changes
- eaf2d68: OpenAPI operation title fallback in sections
- Updated dependencies [844059f]
- Updated dependencies [88f64b0]
- @gitbook/react-openapi@1.1.3
- @gitbook/icons@0.2.0
- @gitbook/react-contentkit@0.6.2
## 0.7.2
### Patch Changes
- f127d28: Rename OpenAPIModels to OpenAPISchemas
- Updated dependencies [f127d28]
- @gitbook/react-openapi@1.1.2
## 0.7.1
### Patch Changes
- Updated dependencies [f574858]
- @gitbook/react-openapi@1.1.1
## 0.7.0
### Minor Changes
- bb3ca9c: Implement OpenAPI models blocks
### Patch Changes
- 5907bd9: Adjust hint block spacing
- 9ffc3b6: Fix content overflowing out of its container in tabs
- Updated dependencies [0278a14]
- Updated dependencies [bb3ca9c]
- Updated dependencies [3173d8e]
- Updated dependencies [052e07a]
- @gitbook/openapi-parser@2.1.0
- @gitbook/react-openapi@1.1.0
## 0.6.5
### Patch Changes
- 05ffd0e: Improving data cache management for computed content
- 8beb5d6: Add input elements to ContentKit
- Updated dependencies [53f5dbe]
- Updated dependencies [05ffd0e]
- Updated dependencies [8beb5d6]
- @gitbook/openapi-parser@2.0.2
- @gitbook/cache-tags@0.1.0
- @gitbook/react-contentkit@0.6.1
- @gitbook/react-openapi@1.0.5
## 0.6.4
### Patch Changes
- 9b914d1: Fix getProxyModeBasePath that was computing incorrect base path in some scenarios
- 2ae76f9: Change how a site in proxy mode is resolved
- 027a859: Add support for links style customization option
- 3e11678: fix: lost section groups
- 3319375: Support OpenAPI operation block
- Updated dependencies [722f02e]
- Updated dependencies [0924259]
- @gitbook/react-openapi@1.0.4
- @gitbook/openapi-parser@2.0.1
## 0.6.3
### Patch Changes
- a820739: Remove unused search api method from gitbook/api/lib
- a054554: Implement a trusted mode to speed up OpenAPI spec validation
- 66d0fc0: Update design for hint block: use semantic colors (info, warning, danger, success) and add alternative styling for hints with headings
- 9f0de74: Add support for new OpenAPI ref
- da55fac: Render GitBook blocks in OpenAPI operation description
- Updated dependencies [c808bb1]
- Updated dependencies [dc2dbc5]
- Updated dependencies [f1d1d2f]
- Updated dependencies [e24206e]
- Updated dependencies [a054554]
- Updated dependencies [05e1d8c]
- Updated dependencies [b4a12d6]
- Updated dependencies [9f0de74]
- Updated dependencies [da55fac]
- @gitbook/openapi-parser@2.0.0
- @gitbook/react-openapi@1.0.3
## 0.6.2
### Patch Changes
@@ -282,7 +789,7 @@
- 4cbcc5b: Rollback of scalar modal while fixing perf issue
- 3996110: Optimize images rendered in community ads
- 133c3e7: Update design of Checkbox to be more consistent and readable
- 5096f7f: Disable KV cache for docs.gitbook.com as a test, also disable it for change-request to improve consistency
- 5096f7f: Disable KV cache for gitbook.com/docs as a test, also disable it for change-request to improve consistency
- 0f1565c: Add optional env `GITBOOK_INTEGRATIONS_HOST` to configure the host serving the integrations
- 2ff7ed1: Fix table of contents being visible on mobile when disabled at the page level
- b075f0f: Fix accessibility of the table of contents by using `aria-current` instead of `aria-selected`
-4
View File
@@ -1,4 +0,0 @@
{
"version": 1,
"exclude": ["/~gitbook/static/*"]
}
-7
View File
@@ -1,7 +0,0 @@
import type { CacheObject } from '@gitbook/cache-do';
declare global {
interface CloudflareEnv {
CACHE?: DurableObjectNamespace<CacheObject>;
}
}
+100 -87
View File
@@ -1,239 +1,252 @@
import { runTestCases, TestsCase, waitForCookiesDialog } from './util';
import { type TestsCase, runTestCases, waitForCookiesDialog } from './util';
/** A list of test cases to run on the customers' docs sites. */
const testCases: TestsCase[] = [
{
name: 'Snyk',
baseUrl: 'https://docs.snyk.io',
contentBaseURL: 'https://docs.snyk.io',
tests: [
{ name: 'Home', url: '/' },
{ name: 'OpenAPI', url: '/snyk-api/reference/apps' },
{ name: 'Home', url: '/', run: waitForCookiesDialog },
{ name: 'OpenAPI', url: '/snyk-api/reference/apps', run: waitForCookiesDialog },
],
},
{
name: 'Nexthink',
baseUrl: 'https://docs.nexthink.com',
tests: [{ name: 'Home', url: '/', screenshot: { waitForTOCScrolling: false } }],
},
// {
// name: 'Nexthink',
// contentBaseURL: 'https://docs.nexthink.com',
// tests: [
// {
// name: 'Home',
// url: '/',
// screenshot: { waitForTOCScrolling: false },
// run: waitForCookiesDialog,
// },
// ],
// },
{
name: 'asiksupport-stg.dto.kemkes.go.id',
baseUrl: 'https://asiksupport-stg.dto.kemkes.go.id',
contentBaseURL: 'https://asiksupport-stg.dto.kemkes.go.id',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'jasons-tutorials.gitbook.io',
baseUrl: 'https://jasons-tutorials.gitbook.io',
contentBaseURL: 'https://jasons-tutorials.gitbook.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'faq.deltaemulator.com',
baseUrl: 'https://faq.deltaemulator.com',
contentBaseURL: 'https://faq.deltaemulator.com',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.dify.ai',
baseUrl: 'https://docs.dify.ai',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://docs.dify.ai',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'seeddao.gitbook.io',
baseUrl: 'https://seeddao.gitbook.io',
contentBaseURL: 'https://seeddao.gitbook.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'faq.altstore.io',
baseUrl: 'https://faq.altstore.io',
contentBaseURL: 'https://faq.altstore.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'support.audacityteam.org',
baseUrl: 'https://support.audacityteam.org',
contentBaseURL: 'https://support.audacityteam.org',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.gmgn.ai',
baseUrl: 'https://docs.gmgn.ai',
contentBaseURL: 'https://docs.gmgn.ai',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.spicychat.ai',
baseUrl: 'https://docs.spicychat.ai',
contentBaseURL: 'https://docs.spicychat.ai',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.portainer.io',
baseUrl: 'https://docs.portainer.io',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://docs.portainer.io',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.chirptoken.io',
baseUrl: 'https://docs.chirptoken.io',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://docs.chirptoken.io',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.dexscreener.com',
baseUrl: 'https://docs.dexscreener.com',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://docs.dexscreener.com',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.pancakeswap.finance',
baseUrl: 'https://docs.pancakeswap.finance',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://docs.pancakeswap.finance',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'book.character.ai',
baseUrl: 'https://book.character.ai',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.tradeonnova.io',
baseUrl: 'https://docs.tradeonnova.io',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://book.character.ai',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'azcoiner.gitbook.io',
baseUrl: 'https://azcoiner.gitbook.io',
contentBaseURL: 'https://azcoiner.gitbook.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.midas.app',
baseUrl: 'https://docs.midas.app',
contentBaseURL: 'https://docs.midas.app',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.keeper.io',
baseUrl: 'https://docs.keeper.io',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://docs.keeper.io',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'adiblar.gitbook.io',
baseUrl: 'https://adiblar.gitbook.io',
contentBaseURL: 'https://adiblar.gitbook.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.gradient.network',
baseUrl: 'https://docs.gradient.network',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'mygate-network.gitbook.io',
baseUrl: 'https://mygate-network.gitbook.io',
contentBaseURL: 'https://docs.gradient.network',
tests: [{ name: 'Home', url: '/' }],
},
// {
// name: 'mygate-network.gitbook.io',
// contentBaseURL: 'https://mygate-network.gitbook.io',
// tests: [{ name: 'Home', url: '/' }],
// },
{
name: 'treasurenft.gitbook.io',
baseUrl: 'https://treasurenft.gitbook.io',
contentBaseURL: 'https://treasurenft.gitbook.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'browndust2.gitbook.io',
baseUrl: 'https://browndust2.gitbook.io',
contentBaseURL: 'https://browndust2.gitbook.io',
tests: [{ name: 'Home', url: '/', screenshot: { waitForTOCScrolling: false } }],
},
{
name: 'junookyo.gitbook.io',
baseUrl: 'https://junookyo.gitbook.io',
contentBaseURL: 'https://junookyo.gitbook.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'meshnet.nordvpn.com',
baseUrl: 'https://meshnet.nordvpn.com',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://meshnet.nordvpn.com',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'manual.bubble.io',
baseUrl: 'https://manual.bubble.io',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://manual.bubble.io',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.tickettool.xyz',
baseUrl: 'https://docs.tickettool.xyz',
contentBaseURL: 'https://docs.tickettool.xyz',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'wiki.redmodding.org',
baseUrl: 'https://wiki.redmodding.org',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.cherry-ai.com',
baseUrl: 'https://docs.cherry-ai.com',
contentBaseURL: 'https://wiki.redmodding.org',
tests: [{ name: 'Home', url: '/' }],
},
// {
// name: 'docs.cherry-ai.com',
// contentBaseURL: 'https://docs.cherry-ai.com',
// tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
// },
{
name: 'docs.snyk.io',
baseUrl: 'https://docs.snyk.io',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://docs.snyk.io',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.realapp.link',
baseUrl: 'https://docs.realapp.link',
contentBaseURL: 'https://docs.realapp.link',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.plaza.finance',
baseUrl: 'https://docs.plaza.finance',
contentBaseURL: 'https://docs.plaza.finance',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.publicai.io',
baseUrl: 'https://docs.publicai.io',
contentBaseURL: 'https://docs.publicai.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'hyperliquid.gitbook.io',
baseUrl: 'https://hyperliquid.gitbook.io',
contentBaseURL: 'https://hyperliquid.gitbook.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.umbraco.com',
baseUrl: 'https://docs.umbraco.com',
tests: [{ name: 'Home', url: '/welcome', screenshot: { waitForTOCScrolling: false } }],
contentBaseURL: 'https://docs.umbraco.com',
tests: [
{
name: 'Home',
url: '/welcome',
run: waitForCookiesDialog,
screenshot: { waitForTOCScrolling: false },
},
],
},
{
name: 'sosovalue-white-paper.gitbook.io',
baseUrl: 'https://sosovalue-white-paper.gitbook.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.revrobotics.com',
baseUrl: 'https://docs.revrobotics.com',
contentBaseURL: 'https://sosovalue-white-paper.gitbook.io',
tests: [{ name: 'Home', url: '/' }],
},
// {
// name: 'docs.revrobotics.com',
// contentBaseURL: 'https://docs.revrobotics.com',
// tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
// },
{
name: 'chartschool.stockcharts.com',
baseUrl: 'https://chartschool.stockcharts.com',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://chartschool.stockcharts.com',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.soniclabs.com',
baseUrl: 'https://docs.soniclabs.com',
contentBaseURL: 'https://docs.soniclabs.com',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.meshchain.ai',
baseUrl: 'https://docs.meshchain.ai',
contentBaseURL: 'https://docs.meshchain.ai',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.thousandeyes.com',
baseUrl: 'https://docs.thousandeyes.com',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://docs.thousandeyes.com',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'docs.raydium.io',
baseUrl: 'https://docs.raydium.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.solv.finance',
baseUrl: 'https://docs.solv.finance',
contentBaseURL: 'https://docs.raydium.io',
tests: [{ name: 'Home', url: '/' }],
},
{
name: 'docs.fluentbit.io',
baseUrl: 'https://docs.fluentbit.io',
tests: [{ name: 'Home', url: '/' }],
contentBaseURL: 'https://docs.fluentbit.io',
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
},
{
name: 'run-ai-docs.nvidia.com',
contentBaseURL: 'https://run-ai-docs.nvidia.com',
skip: process.env.ARGOS_BUILD_NAME !== 'customers-v2',
tests: [
{ name: 'Home', url: '/' },
{ name: 'OG Image', url: '/~gitbook/ogimage/h17zQIFwy3MaafVNmItO', mode: 'image' },
],
},
];
File diff suppressed because it is too large Load Diff
+170
View File
@@ -0,0 +1,170 @@
import { argosScreenshot } from '@argos-ci/playwright';
import { expect, test } from '@playwright/test';
import { getContentTestURL } from '../tests/utils';
import { waitForIcons } from './util';
test.describe('PDF export', () => {
test('export all pages as PDF (e2e)', async ({ page }) => {
// Set the header to disable the Vercel toolbar
// But only on the main document as it'd cause CORS issues on other resources
await page.route('**/*', async (route, request) => {
if (request.resourceType() === 'document') {
await route.continue({
headers: {
...request.headers(),
'x-vercel-skip-toolbar': '1',
},
});
} else {
await route.continue();
}
});
await page.goto(
getContentTestURL(
'https://gitbook-open-e2e-sites.gitbook.io/gitbook-doc/~gitbook/pdf?limit=10'
)
);
const printBtn = page.getByTestId('print-button');
await expect(printBtn).toBeVisible();
await argosScreenshot(page, 'pdf - all pages', {
viewports: ['macbook-13'],
argosCSS: `
/* Hide Intercom */
.intercom-lightweight-app {
display: none !important;
}
`,
threshold: undefined,
fullPage: true,
beforeScreenshot: async ({ runStabilization }) => {
await runStabilization();
await waitForIcons(page);
},
});
});
test('export all pages as PDF (GitBook docs)', async ({ page }) => {
// Set the header to disable the Vercel toolbar
// But only on the main document as it'd cause CORS issues on other resources
await page.route('**/*', async (route, request) => {
if (request.resourceType() === 'document') {
await route.continue({
headers: {
...request.headers(),
'x-vercel-skip-toolbar': '1',
},
});
} else {
await route.continue();
}
});
await page.goto(getContentTestURL('https://gitbook.com/docs/~gitbook/pdf?limit=10'));
const printBtn = page.getByTestId('print-button');
await expect(printBtn).toBeVisible();
await argosScreenshot(page, 'pdf - all pages', {
viewports: ['macbook-13'],
argosCSS: `
/* Hide Intercom */
.intercom-lightweight-app {
display: none !important;
}
`,
threshold: undefined,
fullPage: true,
beforeScreenshot: async ({ runStabilization }) => {
await runStabilization();
await waitForIcons(page);
},
});
});
test('export a single page as PDF (e2e)', async ({ page }) => {
// Set the header to disable the Vercel toolbar
// But only on the main document as it'd cause CORS issues on other resources
await page.route('**/*', async (route, request) => {
if (request.resourceType() === 'document') {
await route.continue({
headers: {
...request.headers(),
'x-vercel-skip-toolbar': '1',
},
});
} else {
await route.continue();
}
});
await page.goto(
getContentTestURL(
'https://gitbook-open-e2e-sites.gitbook.io/gitbook-doc/~gitbook/pdf?page=Bw7LjWwgTjV8nIV4s7rs&only=yes&limit=2'
)
);
const printBtn = page.getByTestId('print-button');
await expect(printBtn).toBeVisible();
await argosScreenshot(page, 'pdf - all pages', {
viewports: ['macbook-13'],
argosCSS: `
/* Hide Intercom */
.intercom-lightweight-app {
display: none !important;
}
`,
threshold: undefined,
fullPage: true,
beforeScreenshot: async ({ runStabilization }) => {
await runStabilization();
await waitForIcons(page);
},
});
});
test('export a single page as PDF (GitBook docs)', async ({ page }) => {
// Set the header to disable the Vercel toolbar
// But only on the main document as it'd cause CORS issues on other resources
await page.route('**/*', async (route, request) => {
if (request.resourceType() === 'document') {
await route.continue({
headers: {
...request.headers(),
'x-vercel-skip-toolbar': '1',
},
});
} else {
await route.continue();
}
});
await page.goto(
getContentTestURL(
'https://gitbook.com/docs/~gitbook/pdf?page=DfnNkU49mvLe2ythHAyx&only=yes&limit=2'
)
);
const printBtn = page.getByTestId('print-button');
await expect(printBtn).toBeVisible();
await argosScreenshot(page, 'pdf - all pages', {
viewports: ['macbook-13'],
argosCSS: `
/* Hide Intercom */
.intercom-lightweight-app {
display: none !important;
}
`,
threshold: undefined,
fullPage: true,
beforeScreenshot: async ({ runStabilization }) => {
await runStabilization();
await waitForIcons(page);
},
});
});
});
+172 -45
View File
@@ -1,37 +1,47 @@
import { argosScreenshot } from '@argos-ci/playwright';
import {
CustomizationAIMode,
CustomizationBackground,
CustomizationCorners,
CustomizationFont,
CustomizationHeaderItem,
CustomizationDefaultFont,
CustomizationDefaultMonospaceFont,
CustomizationDepth,
type CustomizationHeaderItem,
CustomizationHeaderPreset,
CustomizationIconsStyle,
CustomizationLinksStyle,
CustomizationLocale,
CustomizationSearchStyle,
CustomizationSidebarBackgroundStyle,
CustomizationSidebarListStyle,
CustomizationTheme,
CustomizationThemedColor,
CustomizationThemeMode,
SiteCustomizationSettings,
type CustomizationThemedColor,
type SiteCustomizationSettings,
SiteExternalLinksTarget,
} from '@gitbook/api';
import { test, expect, Page, BrowserContext } from '@playwright/test';
import { type BrowserContext, type Page, type Response, expect, test } from '@playwright/test';
import deepMerge from 'deepmerge';
import rison from 'rison';
import { DeepPartial } from 'ts-essentials';
import type { DeepPartial } from 'ts-essentials';
import { getContentTestURL } from '../tests/utils';
import { getContentTestURL, getTestURL } from '../tests/utils';
export interface Test {
name: string;
/**
* URL to visit for testing.
*/
url: string;
url: string | (() => string | Promise<string>);
cookies?: Parameters<BrowserContext['addCookies']>[0];
/**
* Test to run
*/
run?: (page: Page) => Promise<unknown>;
run?: (page: Page, response: Response | null) => Promise<unknown>;
/**
* Mode for the test.
*/
mode?: 'page' | 'image';
/**
* Whether the test should be fullscreened during testing.
*/
@@ -59,11 +69,12 @@ export interface Test {
only?: boolean;
}
export interface TestsCase {
export type TestsCase = {
name: string;
baseUrl: string;
skip?: boolean;
tests: Array<Test>;
}
contentBaseURL?: string;
};
export const allLocales: CustomizationLocale[] = [
CustomizationLocale.Fr,
@@ -108,6 +119,11 @@ export const allSidebarBackgroundStyles: CustomizationSidebarBackgroundStyle[] =
CustomizationSidebarBackgroundStyle.Filled,
];
export const allSearchStyles: CustomizationSearchStyle[] = [
CustomizationSearchStyle.Prominent,
CustomizationSearchStyle.Subtle,
];
// Common customization settings
export const headerLinks: CustomizationHeaderItem[] = [
@@ -126,11 +142,13 @@ export const headerLinks: CustomizationHeaderItem[] = [
];
export async function waitForCookiesDialog(page: Page) {
const dialog = page.getByRole('dialog', { name: 'Cookies' });
const accept = dialog.getByRole('button', { name: 'Accept' });
const reject = dialog.getByRole('button', { name: 'Reject' });
await expect(accept).toBeVisible();
await expect(reject).toBeVisible();
const dialog = page.getByTestId('cookies-dialog');
await expect(dialog).toBeVisible();
}
export async function waitForNotFound(_page: Page, response: Response | null) {
expect(response).not.toBeNull();
expect(response?.status()).toBe(404);
}
/**
@@ -138,28 +156,63 @@ export async function waitForCookiesDialog(page: Page) {
*/
export function runTestCases(testCases: TestsCase[]) {
for (const testCase of testCases) {
if (testCase.skip) {
continue;
}
test.describe(testCase.name, () => {
for (const testEntry of testCase.tests) {
const { mode = 'page' } = testEntry;
const testFn = testEntry.only ? test.only : test;
testFn(testEntry.name, async ({ page, baseURL, context }) => {
const contentUrl = new URL(testEntry.url, testCase.baseUrl);
const url = getContentTestURL(contentUrl.toString(), baseURL);
testFn(testEntry.name, async ({ page, context }) => {
const testEntryPathname =
typeof testEntry.url === 'function' ? await testEntry.url() : testEntry.url;
const url = testCase.contentBaseURL
? getContentTestURL(
new URL(testEntryPathname, testCase.contentBaseURL).toString()
)
: getTestURL(testEntryPathname);
if (testEntry.cookies) {
await context.addCookies(
testEntry.cookies.map((cookie) => ({
...cookie,
domain: new URL(url).host,
path: '/',
})),
}))
);
}
await page.goto(url);
// Set the header to disable the Vercel toolbar
// But only on the main document as it'd cause CORS issues on other resources
await page.route('**/*', async (route, request) => {
if (request.resourceType() === 'document') {
await route.continue({
headers: {
...request.headers(),
'x-vercel-skip-toolbar': '1',
},
});
} else {
await route.continue();
}
});
const response = await page.goto(url);
if (testEntry.run) {
await testEntry.run(page);
await testEntry.run(page, response);
}
const screenshotOptions = testEntry.screenshot;
if (screenshotOptions !== false) {
await argosScreenshot(page, `${testCase.name} - ${testEntry.name}`, {
const screenshotName = `${testCase.name} - ${testEntry.name}`;
if (mode === 'image') {
await argosScreenshot(page, screenshotName, {
viewports: ['macbook-13'],
threshold: screenshotOptions?.threshold ?? undefined,
fullPage: true,
});
} else {
await argosScreenshot(page, screenshotName, {
viewports: ['macbook-16', 'macbook-13', 'ipad-2', 'iphone-x'],
argosCSS: `
/* Hide Intercom */
@@ -169,14 +222,16 @@ export function runTestCases(testCases: TestsCase[]) {
`,
threshold: screenshotOptions?.threshold ?? undefined,
fullPage: testEntry.fullPage ?? false,
beforeScreenshot: async () => {
await waitForIcons(page);
beforeScreenshot: async ({ runStabilization }) => {
await runStabilization();
if (screenshotOptions?.waitForTOCScrolling !== false) {
await waitForTOCScrolling(page);
}
await waitForIcons(page);
},
});
}
}
});
}
});
@@ -222,18 +277,29 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
styling: {
theme: newTheme,
primaryColor: { light: '#346DDB', dark: '#346DDB' },
infoColor: { light: '#787878', dark: '#787878' },
warningColor: { light: '#FE9A00', dark: '#FE9A00' },
dangerColor: { light: '#FB2C36', dark: '#FB2C36' },
successColor: { light: '#00C950', dark: '#00C950' },
corners: CustomizationCorners.Rounded,
font: CustomizationFont.Inter,
depth: CustomizationDepth.Subtle,
font: CustomizationDefaultFont.Inter,
monospaceFont: CustomizationDefaultMonospaceFont.IBMPlexMono,
background: CustomizationBackground.Plain,
icons: CustomizationIconsStyle.Regular,
links: CustomizationLinksStyle.Default,
sidebar: {
background: CustomizationSidebarBackgroundStyle.Default,
list: CustomizationSidebarListStyle.Default,
},
search: CustomizationSearchStyle.Subtle,
},
internationalization: {
locale: CustomizationLocale.En,
},
insights: {
trackingCookie: true,
},
favicon: {},
header: {
preset: CustomizationHeaderPreset.Default,
@@ -252,8 +318,11 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
feedback: {
enabled: false,
},
aiSearch: {
enabled: true,
ai: {
mode: CustomizationAIMode.None,
},
externalLinks: {
target: SiteExternalLinksTarget.Self,
},
advancedCustomization: {
enabled: true,
@@ -264,6 +333,10 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
pagination: {
enabled: true,
},
pageActions: {
externalAI: true,
markdown: true,
},
trademark: {
enabled: true,
},
@@ -284,30 +357,84 @@ export function getCustomizationURL(partial: DeepPartial<SiteCustomizationSettin
/**
* Wait for all icons present on the page to be loaded.
*/
async function waitForIcons(page: Page) {
await page.waitForFunction(async () => {
function loadImage(src: string) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = (error) => reject(new Error(`Failed to load image: ${src}`));
img.src = src;
});
export async function waitForIcons(page: Page) {
await page.waitForFunction(() => {
const urlStates: Record<
string,
{ state: 'pending'; uri: null } | { state: 'loaded'; uri: string }
> = (window as any).__ICONS_STATES__ || {};
(window as any).__ICONS_STATES__ = urlStates;
const fetchSvgAsDataUri = async (url: string): Promise<string> => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch SVG: ${response.status}`);
}
const svgText = await response.text();
const encoded = encodeURIComponent(svgText).replace(/'/g, '%27').replace(/"/g, '%22');
return `data:image/svg+xml;charset=utf-8,${encoded}`;
};
const loadUrl = (url: string) => {
// Mark the URL as pending.
urlStates[url] = { state: 'pending', uri: null };
fetchSvgAsDataUri(url).then((uri) => {
urlStates[url] = { state: 'loaded', uri };
});
};
const icons = Array.from(document.querySelectorAll('svg.gb-icon'));
await Promise.all(
icons.map(async (icon) => {
const results = icons.map((icon) => {
if (!(icon instanceof SVGElement)) {
throw new Error('Icon is not an SVGElement');
}
// Ignore icons that are not visible.
if (!icon.checkVisibility()) {
return true;
}
const state = icon.getAttribute('data-argos-state');
if (state === 'pending') {
return false;
}
if (state === 'loaded') {
return true;
}
// url("https://ka-p.fontawesome.com/releases/v6.6.0/svgs/light/moon.svg?v=2&token=a463935e93")
const maskImage = window.getComputedStyle(icon).getPropertyValue('mask-image');
const urlMatch = maskImage.match(/url\("([^"]+)"\)/);
const url = urlMatch ? urlMatch[1] : null;
const url = urlMatch?.[1];
// If URL is invalid we throw an error.
if (!url) {
throw new Error('No mask-image');
}
await loadImage(url);
}),
);
// If the URL is already queued for loading, we return the state.
if (urlStates[url]) {
if (urlStates[url].state === 'loaded') {
icon.setAttribute('data-argos-state', 'pending');
icon.style.maskImage = `url("${urlStates[url].uri}")`;
requestAnimationFrame(() => {
icon.setAttribute('data-argos-state', 'loaded');
});
return false;
}
return false;
}
loadUrl(url);
return false;
});
return results.every((x) => x);
});
}
@@ -321,7 +448,7 @@ async function waitForTOCScrolling(page: Page) {
await expect(toc).toBeVisible();
await page.evaluate(() => {
const tocScrollContainer = document.querySelector(
'[data-testid="table-of-contents"] [data-testid="toc-scroll-container"]',
'[data-testid="table-of-contents"] [data-testid="toc-scroll-container"]'
);
if (!tocScrollContainer) {
throw new Error('TOC scroll container not found');
-83
View File
@@ -1,83 +0,0 @@
const { withSentryConfig } = require('@sentry/nextjs');
module.exports = withSentryConfig(
{
env: {
BUILD_VERSION: (process.env.GITHUB_SHA ?? '').slice(0, 7),
SENTRY_RELEASE: process.env.SENTRY_RELEASE ?? '',
SENTRY_DSN: process.env.SENTRY_DSN ?? '',
SENTRY_ENVIRONMENT: process.env.SENTRY_ENVIRONMENT ?? 'development',
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
GITBOOK_ICONS_URL: process.env.GITBOOK_ICONS_URL,
GITBOOK_ICONS_TOKEN: process.env.GITBOOK_ICONS_TOKEN,
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY,
},
webpack(config, { dev, webpack }) {
config.resolve.fallback = {
...config.resolve.fallback,
// Required for `swagger2openapi` to work:
fs: false,
path: false,
http: false,
};
// Tree shake debug code for Sentry
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/tree-shaking/#tree-shaking-with-nextjs
if (!dev) {
config.plugins.push(
new webpack.DefinePlugin({
__SENTRY_DEBUG__: false,
// We always init Sentry with enableTracing: false for now, so this is useless
__SENTRY_TRACING__: false,
__RRWEB_EXCLUDE_IFRAME__: true,
__RRWEB_EXCLUDE_SHADOW_DOM__: true,
__SENTRY_EXCLUDE_REPLAY_WORKER__: true,
}),
);
}
return config;
},
async headers() {
return [
// Cache all static assets for 1 year
{
source: '/~gitbook/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
];
},
assetPrefix: process.env.GITBOOK_ASSETS_PREFIX,
poweredByHeader: false,
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '*.gitbook.io',
},
],
},
},
{
release: process.env.SENTRY_RELEASE,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
tunnelRoute: '/~gitbook/monitoring',
disableLogger: true,
},
);
+79
View File
@@ -0,0 +1,79 @@
// @ts-check
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
experimental: {
// This is needed to throw "forbidden" when the api token expired during revalidation
authInterrupts: true,
useCache: true,
// Content is fully static, we can cache it in the session memory cache for a long time
staleTimes: {
dynamic: 3600, // 1 hour
static: 3600, // 1 hour
},
// Since content is fully static, we don't want to fetch on hover again
optimisticClientCache: false,
},
env: {
BUILD_VERSION: (process.env.GITHUB_SHA ?? '').slice(0, 7),
// GitBook envs
GITBOOK_API_URL: process.env.GITBOOK_API_URL,
GITBOOK_APP_URL: process.env.GITBOOK_APP_URL,
GITBOOK_INTEGRATIONS_HOST: process.env.GITBOOK_INTEGRATIONS_HOST,
GITBOOK_IMAGE_RESIZE_URL: process.env.GITBOOK_IMAGE_RESIZE_URL,
GITBOOK_ICONS_URL: process.env.GITBOOK_ICONS_URL,
GITBOOK_ICONS_TOKEN: process.env.GITBOOK_ICONS_TOKEN,
GITBOOK_URL: process.env.GITBOOK_URL,
GITBOOK_API_TOKEN: process.env.GITBOOK_API_TOKEN,
GITBOOK_ASSETS_PREFIX: process.env.GITBOOK_ASSETS_PREFIX,
GITBOOK_SECRET: process.env.GITBOOK_SECRET,
GITBOOK_IMAGE_RESIZE_SIGNING_KEY: process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY,
GITBOOK_IMAGE_RESIZE_MODE: process.env.GITBOOK_IMAGE_RESIZE_MODE,
GITBOOK_FONTS_URL: process.env.GITBOOK_FONTS_URL,
GITBOOK_RUNTIME: process.env.GITBOOK_RUNTIME,
// Next.js envs
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY,
// Used to detect if the app is running in V2 mode
GITBOOK_V2: 'true',
},
assetPrefix: process.env.GITBOOK_ASSETS_PREFIX,
poweredByHeader: false,
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '*.gitbook.io',
},
],
},
async headers() {
return [
{
source: '/~gitbook/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
{
key: 'Access-Control-Allow-Origin',
value: '*',
},
],
},
];
},
};
export default nextConfig;
+29
View File
@@ -0,0 +1,29 @@
import type { OpenNextConfig } from '@opennextjs/cloudflare';
export default {
default: {
override: {
wrapper: 'cloudflare-node',
converter: 'edge',
proxyExternalRequest: 'fetch',
queue: () => import('./openNext/queue/middleware').then((m) => m.default),
incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default),
tagCache: () => import('./openNext/tagCache/middleware').then((m) => m.default),
},
},
middleware: {
external: true,
override: {
wrapper: 'cloudflare-edge',
converter: 'edge',
proxyExternalRequest: 'fetch',
queue: () => import('./openNext/queue/middleware').then((m) => m.default),
incrementalCache: () => import('./openNext/incrementalCache').then((m) => m.default),
tagCache: () => import('./openNext/tagCache/middleware').then((m) => m.default),
},
},
dangerous: {
enableCacheInterception: true,
},
edgeExternals: ['node:crypto'],
} satisfies OpenNextConfig;
@@ -0,0 +1,39 @@
import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js';
import { DurableObject } from 'cloudflare:workers';
//Only needed to run locally, in prod we'll use the one from do.js
export { DOShardedTagCache } from '../../.open-next/.build/durable-objects/sharded-tag-cache.js';
// Only needed to run locally, in prod we'll use the one from do.js
export class R2WriteBuffer extends DurableObject {
writePromise;
async write(cacheKey, value) {
// We are already writing to this key
if (this.writePromise) {
return;
}
this.writePromise = this.env.NEXT_INC_CACHE_R2_BUCKET.put(cacheKey, value);
this.ctx.waitUntil(
this.writePromise.finally(() => {
this.writePromise = undefined;
})
);
}
}
export default {
async fetch(request, env, ctx) {
return runWithCloudflareRequestContext(request, env, ctx, async () => {
// We can't move the handler import to the top level, otherwise the runtime will not be properly initialized
const { handler } = await import(
'../../.open-next/server-functions/default/handler.mjs'
);
// - `Request`s are handled by the Next server
return handler(request, env, ctx);
});
},
};
@@ -0,0 +1,177 @@
{
"main": "default.js",
"name": "gitbook-open-v2-server",
"compatibility_date": "2025-04-14",
"compatibility_flags": [
"nodejs_compat",
"allow_importable_env",
"global_fetch_strictly_public"
],
"observability": {
"enabled": true
},
"vars": {
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
},
"env": {
"dev": {
"vars": {
"STAGE": "dev",
"OPEN_NEXT_REQUEST_ID_HEADER": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-preview"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-server-dev"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["R2WriteBuffer", "DOShardedTagCache"]
}
]
},
"preview": {
"vars": {
"STAGE": "preview",
// Just as a test for the preview environment to check that everything works
"NEXT_PRIVATE_DEBUG_CACHE": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-preview"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-server-preview"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-preview"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-preview"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-preview"
}
]
}
},
"staging": {
"vars": {
"OPEN_NEXT_REQUEST_ID_HEADER": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-staging"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-server-staging"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-staging"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-staging"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-staging"
}
]
},
"tail_consumers": [
{
"service": "gitbook-x-staging-tail"
}
]
},
"production": {
"vars": {
// This is a bit misleading, but it means that we can have 500 concurrent revalidations
// This means that we'll have up to 100 durable objects instance running at the same time
"MAX_REVALIDATE_CONCURRENCY": "100",
"OPEN_NEXT_REQUEST_ID_HEADER": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-production"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-server-production"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-production"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-production"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-production"
}
]
},
"tail_consumers": [
{
"service": "gitbook-x-prod-tail"
}
]
}
}
}
@@ -0,0 +1,38 @@
// This worker only purposes it to host the different DO that we will need in the other workers.
import { DurableObject } from 'cloudflare:workers';
// `use cache` could cause multiple writes to the same key to happen concurrently, there is a limit of 1 write per key/second
// so we need to buffer writes to the R2 bucket to avoid hitting this limit.
export class R2WriteBuffer extends DurableObject {
writePromise;
async write(cacheKey, value) {
// We are already writing to this key
if (this.writePromise) {
return;
}
this.writePromise = this.env.NEXT_INC_CACHE_R2_BUCKET.put(cacheKey, value);
this.ctx.waitUntil(
this.writePromise.finally(() => {
this.writePromise = undefined;
})
);
}
}
export { DOQueueHandler } from '../../.open-next/.build/durable-objects/queue.js';
export { DOShardedTagCache } from '../../.open-next/.build/durable-objects/sharded-tag-cache.js';
export default {
async fetch() {
// This worker does not handle any requests, it only provides Durable Objects
return new Response('This worker is not meant to handle requests directly', {
status: 400,
headers: {
'Content-Type': 'text/plain',
},
});
},
};
@@ -0,0 +1,149 @@
{
"main": "do.js",
"name": "gitbook-open-v2-do",
"compatibility_date": "2025-04-14",
"compatibility_flags": [
"nodejs_compat",
"allow_importable_env",
"global_fetch_strictly_public"
],
"observability": {
"enabled": true
},
"env": {
"preview": {
"vars": {
"STAGE": "preview",
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-preview"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-preview"
}
],
"durable_objects": {
"bindings": [
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache"
},
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"]
}
]
},
"staging": {
"vars": {
"STAGE": "staging",
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-staging"
}
],
"tail_consumers": [
{
"service": "gitbook-x-staging-tail"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-staging"
}
],
"durable_objects": {
"bindings": [
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache"
},
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"]
}
]
},
"production": {
"vars": {
// R2 is strongly consistent, so we can disable SQLite
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true",
// We don't want to pollute the memory with broken cache entries
// Most of the time, those are fake requests.
"NEXT_CACHE_DO_QUEUE_MAX_RETRIES": "1",
"STAGE": "production"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-production"
}
],
"tail_consumers": [
{
"service": "gitbook-x-prod-tail"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-production"
}
],
"durable_objects": {
"bindings": [
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache"
},
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache", "R2WriteBuffer"]
}
]
}
}
}
@@ -0,0 +1,42 @@
import { WorkerEntrypoint } from 'cloudflare:workers';
import { runWithCloudflareRequestContext } from '../../.open-next/cloudflare/init.js';
import { handler as middlewareHandler } from '../../.open-next/middleware/handler.mjs';
export { DOQueueHandler } from '../../.open-next/.build/durable-objects/queue.js';
export { DOShardedTagCache } from '../../.open-next/.build/durable-objects/sharded-tag-cache.js';
export default class extends WorkerEntrypoint {
async fetch(request) {
return runWithCloudflareRequestContext(request, this.env, this.ctx, async () => {
// - `Request`s are handled by the Next server
const reqOrResp = await middlewareHandler(request, this.env, this.ctx);
if (reqOrResp instanceof Response) {
return reqOrResp;
}
if (this.env.STAGE !== 'preview') {
// https://developers.cloudflare.com/workers/configuration/versions-and-deployments/gradual-deployments/#version-affinity
reqOrResp.headers.set(
'Cloudflare-Workers-Version-Overrides',
`gitbook-open-v2-${this.env.STAGE}="${this.env.WORKER_VERSION_ID}"`
);
return this.env.DEFAULT_WORKER?.fetch(reqOrResp, {
cf: {
cacheEverything: false,
},
});
}
// If we are in preview mode, we need to send the request to the preview URL
const modifiedUrl = new URL(reqOrResp.url);
modifiedUrl.hostname = this.env.PREVIEW_HOSTNAME;
const nextRequest = new Request(modifiedUrl, reqOrResp);
return fetch(nextRequest, {
cf: {
cacheEverything: false,
},
});
});
}
}
@@ -0,0 +1,222 @@
{
"main": "middleware.js",
"name": "gitbook-open-v2",
"compatibility_date": "2025-04-14",
"compatibility_flags": [
"nodejs_compat",
"allow_importable_env",
"global_fetch_strictly_public"
],
"assets": {
"directory": "../../.open-next/assets",
"binding": "ASSETS"
},
"observability": {
"enabled": true
},
"vars": {
"NEXT_CACHE_DO_QUEUE_DISABLE_SQLITE": "true"
},
"env": {
"dev": {
"vars": {
"STAGE": "dev",
"NEXT_PRIVATE_DEBUG_CACHE": "true",
// When deployed locally, we don't have access to the tag cache here,
// we should just bypass the cache to go to the server directly
"SHOULD_BYPASS_CACHE": "true",
"OPEN_NEXT_REQUEST_ID_HEADER": "true"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-preview"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-dev"
},
{
"binding": "DEFAULT_WORKER",
"service": "gitbook-open-v2-server-dev"
}
]
},
"preview": {
"vars": {
"STAGE": "preview",
"PREVIEW_HOSTNAME": "TO_REPLACE",
"WORKER_VERSION_ID": "TO_REPLACE"
},
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-preview"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-preview"
},
{
"binding": "DEFAULT_WORKER",
"service": "gitbook-open-v2-server-preview"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-preview"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-preview"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-preview"
}
]
}
},
"staging": {
"vars": {
"STAGE": "staging",
"WORKER_VERSION_ID": "TO_REPLACE",
"OPEN_NEXT_REQUEST_ID_HEADER": "true"
},
"routes": [
{
"pattern": "open-2c.gitbook-staging.com/*",
"zone_name": "gitbook-staging.com"
},
{
"pattern": "static-2c.gitbook-staging.com/*",
"zone_name": "gitbook-staging.com"
}
],
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-staging"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-staging"
},
{
"binding": "DEFAULT_WORKER",
"service": "gitbook-open-v2-server-staging"
}
],
"tail_consumers": [
{
"service": "gitbook-x-staging-tail"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-staging"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-staging"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-staging"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"]
}
]
},
"production": {
"vars": {
// This is a bit misleading, but it means that we can have 500 concurrent revalidations
// This means that we'll have up to 100 durable objects instance running at the same time
"MAX_REVALIDATE_CONCURRENCY": "100",
// Temporary variable to find the issue once deployed
// TODO: remove this once the issue is fixed
"DEBUG_CLOUDFLARE": "true",
"WORKER_VERSION_ID": "TO_REPLACE",
"STAGE": "production",
"OPEN_NEXT_REQUEST_ID_HEADER": "true"
},
"routes": [
{
"pattern": "open-2c.gitbook.com/*",
"zone_name": "gitbook.com"
},
{
"pattern": "static-2c.gitbook.com/*",
"zone_name": "gitbook.com"
}
],
"r2_buckets": [
{
"binding": "NEXT_INC_CACHE_R2_BUCKET",
"bucket_name": "gitbook-open-v2-cache-production"
}
],
"services": [
{
"binding": "WORKER_SELF_REFERENCE",
"service": "gitbook-open-v2-production"
},
{
"binding": "DEFAULT_WORKER",
"service": "gitbook-open-v2-server-production"
}
],
"tail_consumers": [
{
"service": "gitbook-x-prod-tail"
}
],
"durable_objects": {
"bindings": [
{
"name": "WRITE_BUFFER",
"class_name": "R2WriteBuffer",
"script_name": "gitbook-open-v2-do-production"
},
{
"name": "NEXT_TAG_CACHE_DO_SHARDED",
"class_name": "DOShardedTagCache",
"script_name": "gitbook-open-v2-do-production"
},
{
"name": "NEXT_CACHE_DO_QUEUE",
"class_name": "DOQueueHandler",
"script_name": "gitbook-open-v2-do-production"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["DOQueueHandler", "DOShardedTagCache"]
}
]
}
}
}
@@ -0,0 +1,26 @@
// In this script, we use the args from the cli to update the PREVIEW_URL vars in the wrangler config file for the middleware
import fs from 'node:fs';
import path from 'node:path';
const wranglerConfigPath = path.join(__dirname, '../middlewareWrangler.jsonc');
const file = fs.readFileSync(wranglerConfigPath, 'utf-8');
const args = process.argv.slice(2);
// The versionId is in the format xxx-xxx-xxx-xxx, we need the first part to reconstruct the preview URL
const versionId = args[0];
// The preview URL is in the format https://<versionId>-gitbook-open-v2-server-preview.gitbook.workers.dev
const previewHostname = `${versionId.split('-')[0]}-gitbook-open-v2-server-preview.gitbook.workers.dev`;
let updatedFile = file.replace(
/"PREVIEW_HOSTNAME": "TO_REPLACE"/,
`"PREVIEW_HOSTNAME": "${previewHostname}"`
);
updatedFile = updatedFile.replaceAll(
/"WORKER_VERSION_ID": "TO_REPLACE"/g,
`"WORKER_VERSION_ID": "${versionId}"`
);
fs.writeFileSync(wranglerConfigPath, updatedFile);
@@ -0,0 +1,210 @@
import { createHash } from 'node:crypto';
import type {
CacheEntryType,
CacheValue,
IncrementalCache,
WithLastModified,
} from '@opennextjs/aws/types/overrides.js';
import { getCloudflareContext } from '@opennextjs/cloudflare';
import type { DurableObjectNamespace, Rpc } from '@cloudflare/workers-types';
export const BINDING_NAME = 'NEXT_INC_CACHE_R2_BUCKET';
export const DEFAULT_PREFIX = 'incremental-cache';
export type KeyOptions = {
cacheType?: CacheEntryType;
};
/**
*
* It is very similar to the `R2IncrementalCache` in the `@opennextjs/cloudflare` package, but it allow us to trace
* the cache operations. It also integrates both R2 and Cache API in a single class.
* Having our own, will allow us to customize it in the future if needed.
*/
class GitbookIncrementalCache implements IncrementalCache {
name = 'GitbookIncrementalCache';
protected localCache: Cache | undefined;
async get<CacheType extends CacheEntryType = 'cache'>(
key: string,
cacheType?: CacheType
): Promise<WithLastModified<CacheValue<CacheType>> | null> {
const cacheKey = this.getR2Key(key, cacheType);
const r2 = getCloudflareContext().env[BINDING_NAME];
const localCache = await this.getCacheInstance();
if (!r2) throw new Error('No R2 bucket');
if (process.env.SHOULD_BYPASS_CACHE === 'true') {
// We are in a local middleware environment, we should bypass the cache
// and go directly to the server.
return null;
}
try {
// Check local cache first if available
const localCacheEntry = await localCache.match(this.getCacheUrlKey(cacheKey));
if (localCacheEntry) {
const result = (await localCacheEntry.json()) as WithLastModified<
CacheValue<CacheType>
>;
return this.returnNullOn404(result);
}
const r2Object = await r2.get(cacheKey);
if (!r2Object) return null;
return this.returnNullOn404({
value: await r2Object.json(),
lastModified: r2Object.uploaded.getTime(),
});
} catch (e) {
console.error('Failed to get from cache', e);
return null;
}
}
//TODO: This is a workaround to handle 404 responses in the cache.
// It should be handled by OpenNext cache interception directly. This should be removed once OpenNext cache interception is fixed.
returnNullOn404<CacheType extends CacheEntryType = 'cache'>(
cacheEntry: WithLastModified<CacheValue<CacheType>> | null
): WithLastModified<CacheValue<CacheType>> | null {
if (!cacheEntry?.value) return null;
if ('meta' in cacheEntry.value && cacheEntry.value.meta?.status === 404) {
return null;
}
return cacheEntry;
}
async set<CacheType extends CacheEntryType = 'cache'>(
key: string,
value: CacheValue<CacheType>,
cacheType?: CacheType
): Promise<void> {
const cacheKey = this.getR2Key(key, cacheType);
const localCache = await this.getCacheInstance();
try {
await this.writeToR2(cacheKey, JSON.stringify(value));
//TODO: Check if there is any places where we don't have tags
// Ideally we should always have tags, but in case we don't, we need to decide how to handle it
// For now we default to a build ID tag, which allow us to invalidate the cache in case something is wrong in this deployment
const tags = this.getTagsFromCacheEntry(value) ?? [
`build_id/${process.env.NEXT_BUILD_ID}`,
];
// We consider R2 as the source of truth, so we update the local cache
// only after a successful R2 write
await localCache.put(
this.getCacheUrlKey(cacheKey),
new Response(
JSON.stringify({
value,
// Note: `Date.now()` returns the time of the last IO rather than the actual time.
// See https://developers.cloudflare.com/workers/reference/security-model/
lastModified: Date.now(),
}),
{
headers: {
// Cache-Control default to 30 minutes, will be overridden by `revalidate`
// In theory we should always get the `revalidate` value
'cache-control': `max-age=${value.revalidate ?? 60 * 30}`,
'cache-tag': tags.join(','),
},
}
)
);
} catch (e) {
console.error('Failed to set to cache', e);
}
}
async delete(key: string): Promise<void> {
const cacheKey = this.getR2Key(key);
const r2 = getCloudflareContext().env[BINDING_NAME];
const localCache = await this.getCacheInstance();
if (!r2) throw new Error('No R2 bucket');
try {
await r2.delete(cacheKey);
// Here again R2 is the source of truth, so we delete from local cache first
await localCache.delete(this.getCacheUrlKey(cacheKey));
} catch (e) {
console.error('Failed to delete from cache', e);
}
}
async writeToR2(key: string, value: string): Promise<void> {
try {
const env = getCloudflareContext().env as {
WRITE_BUFFER: DurableObjectNamespace<
Rpc.DurableObjectBranded & {
write: (key: string, value: string) => Promise<void>;
}
>;
};
const id = env.WRITE_BUFFER.idFromName(key);
// A stub is a client used to invoke methods on the Durable Object
const stub = env.WRITE_BUFFER.get(id);
await stub.write(key, value);
} catch {
// We fallback to writing directly to R2
// it can fail locally because the limit is 1Mb per args
// It is 32Mb in production, so we should be fine
const r2 = getCloudflareContext().env[BINDING_NAME];
r2?.put(key, value);
}
}
async getCacheInstance(): Promise<Cache> {
if (this.localCache) return this.localCache;
this.localCache = await caches.open('incremental-cache');
return this.localCache;
}
// Utility function to generate keys for R2/Cache API
getR2Key(initialKey: string, cacheType: CacheEntryType = 'cache'): string {
let key = initialKey;
if (cacheType === 'composable') {
// For composable cache, we need to discard the build ID from the key
const [_buildId, ...restOfTheKey] = JSON.parse(initialKey);
key = JSON.stringify([...restOfTheKey]);
}
const hash = createHash('sha256').update(key).digest('hex');
return `${DEFAULT_PREFIX}/${cacheType === 'cache' ? process.env?.NEXT_BUILD_ID : 'dataCache'}/${hash}.${cacheType}`.replace(
/\/+/g,
'/'
);
}
getCacheUrlKey(cacheKey: string): string {
return `http://cache.local/${cacheKey}`;
}
getTagsFromCacheEntry<CacheType extends CacheEntryType>(
entry: CacheValue<CacheType>
): string[] | undefined {
if ('tags' in entry && entry.tags) {
return entry.tags;
}
if ('meta' in entry && entry.meta && 'headers' in entry.meta && entry.meta.headers) {
const rawTags = entry.meta.headers['x-next-cache-tags'];
if (typeof rawTags === 'string') {
return rawTags.split(',');
}
}
if ('value' in entry) {
return entry.tags;
}
}
}
export default new GitbookIncrementalCache();
@@ -0,0 +1,11 @@
import type { Queue } from '@opennextjs/aws/types/overrides.js';
import { getCloudflareContext } from '@opennextjs/cloudflare';
import doQueue from '@opennextjs/cloudflare/overrides/queue/do-queue';
export default {
name: 'GitbookISRQueue',
send: async (msg) => {
const { ctx } = getCloudflareContext();
ctx.waitUntil(doQueue.send(msg));
},
} satisfies Queue;
+11
View File
@@ -0,0 +1,11 @@
import { getLogger } from '@/lib/logger';
import type { Queue } from '@opennextjs/aws/types/overrides.js';
export default {
name: 'GitbookISRQueue',
send: async (msg) => {
const logger = getLogger().subLogger('GitbookISRQueue');
// We should never reach this point in the server. If that's the case, we should log it.
logger.warn('send called on server side, this should not happen.', msg);
},
} satisfies Queue;
@@ -0,0 +1,59 @@
import { createLogger, getLogger } from '@/lib/logger';
import type { NextModeTagCache } from '@opennextjs/aws/types/overrides.js';
import doShardedTagCache from '@opennextjs/cloudflare/overrides/tag-cache/do-sharded-tag-cache';
import { softTagFilter } from '@opennextjs/cloudflare/overrides/tag-cache/tag-cache-filter';
const originalTagCache = doShardedTagCache({
baseShardSize: 12,
regionalCache: true,
regionalCacheTtlSec: 60 * 5 /* 5 minutes */,
// Because we invalidate the Cache API on update, we can safely set this to true
regionalCacheDangerouslyPersistMissingTags: true,
shardReplication: {
numberOfSoftReplicas: 2,
numberOfHardReplicas: 1,
regionalReplication: {
defaultRegion: 'enam',
},
},
});
export default {
name: 'GitbookTagCache',
mode: 'nextMode',
// We don't really use this one, as of now it is only used for soft tags
getLastRevalidated: async (tags: string[]) => {
const tagsToCheck = tags.filter(softTagFilter);
if (tagsToCheck.length === 0) {
return 0; // If no tags to check, return 0
}
return await originalTagCache.getLastRevalidated(tagsToCheck);
},
hasBeenRevalidated: async (tags: string[], lastModified?: number) => {
try {
const tagsToCheck = tags.filter(softTagFilter);
if (tagsToCheck.length === 0) {
return false; // If no tags to check, return false
}
return await originalTagCache.hasBeenRevalidated(tagsToCheck, lastModified);
} catch (e) {
createLogger('gitbookTagCache', {}).error(
`hasBeenRevalidated - Error checking tags ${tags.join(', ')}`,
e
);
return false; // In case of error, return false
}
},
writeTags: async (tags: string[]) => {
const tagsToWrite = tags.filter(softTagFilter);
if (tagsToWrite.length === 0) {
const logger = getLogger().subLogger('gitbookTagCache');
logger.warn('writeTags - No valid tags to write');
return; // If no tags to write, exit early
}
await originalTagCache.writeTags(tagsToWrite);
},
} satisfies NextModeTagCache;

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