mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-10-12 07:58:49 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc49a3d899 | |||
| db880d647f | |||
| 6f888387b4 | |||
| 0dd9a65cae | |||
| f39ad015b4 | |||
| a1438cb678 | |||
| 97a5dd3138 | |||
| 6f2cab2f07 | |||
| be6329a64a | |||
| 967b65300d | |||
| 581ceb2b3f | |||
| 9ad5951afc | |||
| 58c4cb2f9e | |||
| ac3643001c | |||
| dd8c53da9a | |||
| e149e618fb | |||
| 39d2828af9 | |||
| 321e07d9fd | |||
| a36a939aef | |||
| c75151e2dc | |||
| d06bedbbe1 |
@@ -19,6 +19,12 @@ inputs:
|
|||||||
deploy:
|
deploy:
|
||||||
description: 'Deploy as main version for all traffic instead of uploading versions'
|
description: 'Deploy as main version for all traffic instead of uploading versions'
|
||||||
required: true
|
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:
|
outputs:
|
||||||
deployment-url:
|
deployment-url:
|
||||||
description: "Deployment URL"
|
description: "Deployment URL"
|
||||||
@@ -65,7 +71,7 @@ runs:
|
|||||||
workingDirectory: ./
|
workingDirectory: ./
|
||||||
wranglerVersion: '3.112.0'
|
wranglerVersion: '3.112.0'
|
||||||
environment: ${{ inputs.environment }}
|
environment: ${{ inputs.environment }}
|
||||||
command: ${{ fromJSON(inputs.deploy) == true && 'deploy' || 'versions upload' }} --config ./packages/gitbook-v2/wrangler.jsonc
|
command: ${{ inputs.deploy == 'true' && 'deploy' || format('versions upload --tag {0} --message "{1}"', inputs.commitTag, inputs.commitMessage) }} --config ./packages/gitbook-v2/wrangler.jsonc
|
||||||
- name: Outputs
|
- name: Outputs
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ jobs:
|
|||||||
run: bun run turbo gitbook#build:cloudflare
|
run: bun run turbo gitbook#build:cloudflare
|
||||||
env:
|
env:
|
||||||
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ secrets.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY }}
|
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ secrets.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY }}
|
||||||
|
GITBOOK_RUNTIME: cloudflare
|
||||||
- id: deploy
|
- id: deploy
|
||||||
name: Deploy to Cloudflare
|
name: Deploy to Cloudflare
|
||||||
uses: cloudflare/wrangler-action@v3.14.0
|
uses: cloudflare/wrangler-action@v3.14.0
|
||||||
@@ -95,6 +96,8 @@ jobs:
|
|||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
opItem: op://gitbook-open/2c-preview
|
opItem: op://gitbook-open/2c-preview
|
||||||
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
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: Outputs
|
- name: Outputs
|
||||||
run: |
|
run: |
|
||||||
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
|
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ jobs:
|
|||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
opItem: op://gitbook-open/2c-production
|
opItem: op://gitbook-open/2c-production
|
||||||
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||||
|
commitTag: main
|
||||||
|
commitMessage: ${{ github.sha }}
|
||||||
- name: Outputs
|
- name: Outputs
|
||||||
run: |
|
run: |
|
||||||
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
|
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
|
||||||
@@ -48,6 +48,8 @@ jobs:
|
|||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
opItem: op://gitbook-open/2c-staging
|
opItem: op://gitbook-open/2c-staging
|
||||||
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
opServiceAccount: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
|
||||||
|
commitTag: main
|
||||||
|
commitMessage: ${{ github.sha }}
|
||||||
- name: Outputs
|
- name: Outputs
|
||||||
run: |
|
run: |
|
||||||
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
|
echo "URL: ${{ steps.deploy.outputs.deployment-url }}"
|
||||||
@@ -34,7 +34,9 @@ export async function verifyImageSignature(
|
|||||||
const generated = await generator(input);
|
const generated = await generator(input);
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noConsole: we want to log the signature comparison
|
// biome-ignore lint/suspicious/noConsole: we want to log the signature comparison
|
||||||
console.log(`comparing image signature "${generated}" (expected) === "${signature}" (actual)`);
|
console.log(
|
||||||
|
`comparing image signature for "${input.url}" on identifier "${input.imagesContextId}": "${generated}" (expected) === "${signature}" (actual)`
|
||||||
|
);
|
||||||
return generated === signature;
|
return generated === signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +71,8 @@ const generateSignatureV2: SignFn = async (input) => {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(':');
|
.join(':');
|
||||||
|
|
||||||
return fnv1a(all, { utf8Buffer: fnv1aUtf8Buffer }).toString(16);
|
const signature = fnv1a(all, { utf8Buffer: fnv1aUtf8Buffer }).toString(16);
|
||||||
|
return signature;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reused buffer for FNV-1a hashing in the v1 algorithm
|
// Reused buffer for FNV-1a hashing in the v1 algorithm
|
||||||
|
|||||||
@@ -167,6 +167,23 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
|
|||||||
// (customization override, theme, etc)
|
// (customization override, theme, etc)
|
||||||
let routeType: 'dynamic' | 'static' = 'static';
|
let routeType: 'dynamic' | 'static' = 'static';
|
||||||
|
|
||||||
|
// We pick only stable data from the siteURL data to prevent re-rendering of
|
||||||
|
// the root layout when changing pages..
|
||||||
|
const stableSiteURLData: SiteURLData = {
|
||||||
|
site: siteURLData.site,
|
||||||
|
siteSection: siteURLData.siteSection,
|
||||||
|
siteSpace: siteURLData.siteSpace,
|
||||||
|
siteBasePath: siteURLData.siteBasePath,
|
||||||
|
basePath: siteURLData.basePath,
|
||||||
|
space: siteURLData.space,
|
||||||
|
organization: siteURLData.organization,
|
||||||
|
changeRequest: siteURLData.changeRequest,
|
||||||
|
revision: siteURLData.revision,
|
||||||
|
shareKey: siteURLData.shareKey,
|
||||||
|
apiToken: siteURLData.apiToken,
|
||||||
|
imagesContextId: imagesContextId,
|
||||||
|
};
|
||||||
|
|
||||||
const requestHeaders = new Headers(request.headers);
|
const requestHeaders = new Headers(request.headers);
|
||||||
requestHeaders.set(MiddlewareHeaders.RouteType, routeType);
|
requestHeaders.set(MiddlewareHeaders.RouteType, routeType);
|
||||||
requestHeaders.set(MiddlewareHeaders.URLMode, mode);
|
requestHeaders.set(MiddlewareHeaders.URLMode, mode);
|
||||||
@@ -174,7 +191,7 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
|
|||||||
MiddlewareHeaders.SiteURL,
|
MiddlewareHeaders.SiteURL,
|
||||||
`${siteCanonicalURL.origin}${siteURLData.basePath}`
|
`${siteCanonicalURL.origin}${siteURLData.basePath}`
|
||||||
);
|
);
|
||||||
requestHeaders.set(MiddlewareHeaders.SiteURLData, JSON.stringify(siteURLData));
|
requestHeaders.set(MiddlewareHeaders.SiteURLData, JSON.stringify(stableSiteURLData));
|
||||||
|
|
||||||
// Preview of customization/theme
|
// Preview of customization/theme
|
||||||
const customization = siteRequestURL.searchParams.get('customization');
|
const customization = siteRequestURL.searchParams.get('customization');
|
||||||
@@ -204,23 +221,6 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
|
|||||||
);
|
);
|
||||||
routeType = routeTypeFromPathname ?? routeType;
|
routeType = routeTypeFromPathname ?? routeType;
|
||||||
|
|
||||||
// We pick only stable data from the siteURL data to prevent re-rendering of
|
|
||||||
// the root layout when changing pages..
|
|
||||||
const stableSiteURLData: SiteURLData = {
|
|
||||||
site: siteURLData.site,
|
|
||||||
siteSection: siteURLData.siteSection,
|
|
||||||
siteSpace: siteURLData.siteSpace,
|
|
||||||
siteBasePath: siteURLData.siteBasePath,
|
|
||||||
basePath: siteURLData.basePath,
|
|
||||||
space: siteURLData.space,
|
|
||||||
organization: siteURLData.organization,
|
|
||||||
changeRequest: siteURLData.changeRequest,
|
|
||||||
revision: siteURLData.revision,
|
|
||||||
shareKey: siteURLData.shareKey,
|
|
||||||
apiToken: siteURLData.apiToken,
|
|
||||||
imagesContextId: imagesContextId,
|
|
||||||
};
|
|
||||||
|
|
||||||
const route = [
|
const route = [
|
||||||
'sites',
|
'sites',
|
||||||
routeType,
|
routeType,
|
||||||
|
|||||||
@@ -92,11 +92,6 @@ const testCases: TestsCase[] = [
|
|||||||
contentBaseURL: 'https://book.character.ai',
|
contentBaseURL: 'https://book.character.ai',
|
||||||
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
|
tests: [{ name: 'Home', url: '/', run: waitForCookiesDialog }],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'docs.tradeonnova.io',
|
|
||||||
contentBaseURL: 'https://docs.tradeonnova.io',
|
|
||||||
tests: [{ name: 'Home', url: '/' }],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'azcoiner.gitbook.io',
|
name: 'azcoiner.gitbook.io',
|
||||||
contentBaseURL: 'https://azcoiner.gitbook.io',
|
contentBaseURL: 'https://azcoiner.gitbook.io',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ module.exports = {
|
|||||||
GITBOOK_ICONS_URL: process.env.GITBOOK_ICONS_URL,
|
GITBOOK_ICONS_URL: process.env.GITBOOK_ICONS_URL,
|
||||||
GITBOOK_ICONS_TOKEN: process.env.GITBOOK_ICONS_TOKEN,
|
GITBOOK_ICONS_TOKEN: process.env.GITBOOK_ICONS_TOKEN,
|
||||||
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY,
|
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY,
|
||||||
|
GITBOOK_RUNTIME: process.env.GITBOOK_RUNTIME,
|
||||||
},
|
},
|
||||||
|
|
||||||
webpack(config) {
|
webpack(config) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ImageResponse } from 'next/og';
|
|||||||
|
|
||||||
import { getEmojiForCode } from '@/lib/emojis';
|
import { getEmojiForCode } from '@/lib/emojis';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
import { getCacheTag } from '@gitbook/cache-tags';
|
||||||
import type { GitBookSiteContext } from '@v2/lib/context';
|
import type { GitBookSiteContext } from '@v2/lib/context';
|
||||||
import { getResizedImageURL } from '@v2/lib/images';
|
import { getResizedImageURL } from '@v2/lib/images';
|
||||||
|
|
||||||
@@ -73,6 +74,14 @@ export async function serveIcon(context: GitBookSiteContext, req: Request) {
|
|||||||
{
|
{
|
||||||
width: size.width,
|
width: size.width,
|
||||||
height: size.height,
|
height: size.height,
|
||||||
|
headers: {
|
||||||
|
'cache-tag': [
|
||||||
|
getCacheTag({
|
||||||
|
tag: 'site',
|
||||||
|
site: context.site.id,
|
||||||
|
}),
|
||||||
|
].join(','),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import { type PageParams, fetchPageData } from '@/components/SitePage';
|
|||||||
import { getFontSourcesToPreload } from '@/fonts/custom';
|
import { getFontSourcesToPreload } from '@/fonts/custom';
|
||||||
import { getAssetURL } from '@/lib/assets';
|
import { getAssetURL } from '@/lib/assets';
|
||||||
import { filterOutNullable } from '@/lib/typescript';
|
import { filterOutNullable } from '@/lib/typescript';
|
||||||
|
import { getCacheTag } from '@gitbook/cache-tags';
|
||||||
import type { GitBookSiteContext } from '@v2/lib/context';
|
import type { GitBookSiteContext } from '@v2/lib/context';
|
||||||
|
import { getCloudflareContext } from '@v2/lib/data/cloudflare';
|
||||||
import { getResizedImageURL } from '@v2/lib/images';
|
import { getResizedImageURL } from '@v2/lib/images';
|
||||||
|
|
||||||
const googleFontsMap: { [fontName in CustomizationDefaultFont]: string } = {
|
const googleFontsMap: { [fontName in CustomizationDefaultFont]: string } = {
|
||||||
@@ -169,8 +171,12 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
|
|||||||
{String.fromCodePoint(Number.parseInt(`0x${customization.favicon.emoji}`))}
|
{String.fromCodePoint(Number.parseInt(`0x${customization.favicon.emoji}`))}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
const src = linker.toAbsoluteURL(
|
const src = await readSelfImage(
|
||||||
linker.toPathInSpace(`~gitbook/icon?size=medium&theme=${customization.themes.default}`)
|
linker.toAbsoluteURL(
|
||||||
|
linker.toPathInSpace(
|
||||||
|
`~gitbook/icon?size=medium&theme=${customization.themes.default}`
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
return <img src={src} alt="Icon" width={40} height={40} tw="mr-4" />;
|
return <img src={src} alt="Icon" width={40} height={40} tw="mr-4" />;
|
||||||
})();
|
})();
|
||||||
@@ -192,7 +198,11 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Grid */}
|
{/* Grid */}
|
||||||
<img tw="absolute inset-0 w-[100vw] h-[100vh]" src={gridAsset} alt="Grid" />
|
<img
|
||||||
|
tw="absolute inset-0 w-[100vw] h-[100vh]"
|
||||||
|
src={await readStaticImage(gridAsset)}
|
||||||
|
alt="Grid"
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
{customization.header.logo ? (
|
{customization.header.logo ? (
|
||||||
@@ -228,6 +238,18 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
|
|||||||
width: 1200,
|
width: 1200,
|
||||||
height: 630,
|
height: 630,
|
||||||
fonts: fonts.length ? fonts : undefined,
|
fonts: fonts.length ? fonts : undefined,
|
||||||
|
headers: {
|
||||||
|
'cache-tag': [
|
||||||
|
getCacheTag({
|
||||||
|
tag: 'site',
|
||||||
|
site: baseContext.site.id,
|
||||||
|
}),
|
||||||
|
getCacheTag({
|
||||||
|
tag: 'space',
|
||||||
|
space: baseContext.space.id,
|
||||||
|
}),
|
||||||
|
].join(','),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -285,3 +307,55 @@ async function loadCustomFont(input: { url: string; weight: 400 | 700 }) {
|
|||||||
weight,
|
weight,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a resource from the function itself.
|
||||||
|
* To avoid error with worker to worker requests in the same zone, we use the `WORKER_SELF_REFERENCE` binding.
|
||||||
|
*/
|
||||||
|
async function fetchSelf(url: string) {
|
||||||
|
const cloudflare = getCloudflareContext();
|
||||||
|
if (cloudflare?.env.WORKER_SELF_REFERENCE) {
|
||||||
|
return await cloudflare.env.WORKER_SELF_REFERENCE.fetch(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await fetch(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an image from a response as a base64 encoded string.
|
||||||
|
*/
|
||||||
|
async function readImage(response: Response) {
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (!contentType || !contentType.startsWith('image/')) {
|
||||||
|
throw new Error(`Invalid content type: ${contentType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayBuffer = await response.arrayBuffer();
|
||||||
|
const base64 = Buffer.from(arrayBuffer).toString('base64');
|
||||||
|
return `data:${contentType};base64,${base64}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const staticImagesCache = new Map<string, string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a static image and cache it in memory.
|
||||||
|
*/
|
||||||
|
async function readStaticImage(url: string) {
|
||||||
|
const cached = staticImagesCache.get(url);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const image = await readSelfImage(url);
|
||||||
|
staticImagesCache.set(url, image);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an image from GitBook itself.
|
||||||
|
*/
|
||||||
|
async function readSelfImage(url: string) {
|
||||||
|
const response = await fetchSelf(url);
|
||||||
|
const image = await readImage(response);
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user