mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 17:43:24 +00:00
Setup package and deployment for v2 (#2791)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'gitbook-v2': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Setup structure and deployment for new version
|
||||||
+149
-11
@@ -18,7 +18,8 @@ jobs:
|
|||||||
checks: write
|
checks: write
|
||||||
statuses: write
|
statuses: write
|
||||||
outputs:
|
outputs:
|
||||||
deployment_url: ${{ steps.deploy.outputs.deployment-url }}
|
deployment-url: ${{ steps.deploy.outputs.deployment-url }}
|
||||||
|
deployment-alias-url: ${{ steps.deploy.outputs.deployment-alias-url }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -38,7 +39,7 @@ jobs:
|
|||||||
echo "SENTRY_ENVIRONMENT=preview" >> $GITHUB_ENV
|
echo "SENTRY_ENVIRONMENT=preview" >> $GITHUB_ENV
|
||||||
if: 1 && !startsWith(github.ref, 'refs/heads/main')
|
if: 1 && !startsWith(github.ref, 'refs/heads/main')
|
||||||
- name: Build Next.js with next-on-pages
|
- name: Build Next.js with next-on-pages
|
||||||
run: bun run build:cloudflare
|
run: bun run turbo build:cloudflare
|
||||||
env:
|
env:
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ secrets.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY }}
|
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: ${{ secrets.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY }}
|
||||||
@@ -74,26 +75,163 @@ jobs:
|
|||||||
-H "Accept: application/vnd.github.v3+json" \
|
-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"}' \
|
-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 }}
|
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: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Setup Bun
|
||||||
|
uses: ./.github/composite/setup-bun
|
||||||
- name: Find GitHub Comment
|
- name: Find GitHub Comment
|
||||||
uses: peter-evans/find-comment@v3
|
uses: peter-evans/find-comment@v3
|
||||||
id: fc
|
id: fc
|
||||||
if: 1 && !startsWith(github.ref, 'refs/heads/main')
|
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
comment-author: 'github-actions[bot]'
|
comment-author: 'github-actions[bot]'
|
||||||
body-includes: GitBook Preview
|
body-includes: 'Summary of the deployments'
|
||||||
|
|
||||||
- name: Create or update GitHub comment
|
- name: Create or update GitHub comment
|
||||||
uses: peter-evans/create-or-update-comment@v4
|
uses: peter-evans/create-or-update-comment@v4
|
||||||
if: 1 && !startsWith(github.ref, 'refs/heads/main')
|
|
||||||
with:
|
with:
|
||||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
body: |
|
body: |
|
||||||
**GitBook Preview**
|
Summary of the deployments:
|
||||||
Latest commit: [${{ steps.deploy.outputs.deployment-url }}](${{ steps.deploy.outputs.deployment-url }})
|
|
||||||
PR: [${{ steps.deploy.outputs.deployment-alias-url }}](${{ steps.deploy.outputs.deployment-alias-url }})
|
### 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
|
edit-mode: replace
|
||||||
visual-testing:
|
visual-testing:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -111,7 +249,7 @@ jobs:
|
|||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
run: bun e2e
|
run: bun e2e
|
||||||
env:
|
env:
|
||||||
BASE_URL: ${{needs.deploy.outputs.deployment_url}}
|
BASE_URL: ${{needs.deploy.outputs.deployment-url}}
|
||||||
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
@@ -135,7 +273,7 @@ jobs:
|
|||||||
- name: Run pagespeed tests
|
- name: Run pagespeed tests
|
||||||
run: bun ./packages/gitbook/tests/pagespeed-testing.ts $DEPLOYMENT_URL
|
run: bun ./packages/gitbook/tests/pagespeed-testing.ts $DEPLOYMENT_URL
|
||||||
env:
|
env:
|
||||||
DEPLOYMENT_URL: ${{needs.deploy.outputs.deployment_url}}
|
DEPLOYMENT_URL: ${{needs.deploy.outputs.deployment-url}}
|
||||||
PAGESPEED_API_KEY: ${{ secrets.PAGESPEED_API_KEY }}
|
PAGESPEED_API_KEY: ${{ secrets.PAGESPEED_API_KEY }}
|
||||||
format:
|
format:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.next
|
.next
|
||||||
.vercel
|
.vercel
|
||||||
|
.open-next
|
||||||
|
|
||||||
# Generated
|
# Generated
|
||||||
packages/emoji-codepoints/index.ts
|
packages/emoji-codepoints/index.ts
|
||||||
|
|||||||
+5
-3
@@ -2,9 +2,10 @@
|
|||||||
"name": "gitbook",
|
"name": "gitbook",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@changesets/cli": "^2.27.7",
|
"@changesets/cli": "^2.27.12",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"turbo": "^2.3.4"
|
"turbo": "^2.3.4",
|
||||||
|
"vercel": "^39.3.0"
|
||||||
},
|
},
|
||||||
"packageManager": "bun@1.1.18",
|
"packageManager": "bun@1.1.18",
|
||||||
"overrides": {
|
"overrides": {
|
||||||
@@ -15,8 +16,9 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "turbo run dev --ui=stream",
|
"dev": "turbo run dev --ui=stream",
|
||||||
|
"dev:v2": "turbo run dev:v2 --ui=stream",
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"build:cloudflare": "turbo run build:cloudflare",
|
"build:v2": "turbo run build:v2",
|
||||||
"clean-deps": "rm -rf node_modules && rm -rf packages/*/node_modules",
|
"clean-deps": "rm -rf node_modules && rm -rf packages/*/node_modules",
|
||||||
"lint": "turbo run lint",
|
"lint": "turbo run lint",
|
||||||
"lint:fix": "turbo run lint -- --fix",
|
"lint:fix": "turbo run lint -- --fix",
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# cloudflare
|
||||||
|
.open-next
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# 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`
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
/// <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.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// @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;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
// 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;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "gitbook-v2",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"next": "canary",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"@gitbook/api": "^0.89.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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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} />;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function getSiteCacheTag(siteId: string) {
|
||||||
|
return `sites/${siteId}`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* 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';
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"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"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
main = ".open-next/worker.js"
|
||||||
|
name = "gitbook-v2"
|
||||||
|
compatibility_date = "2024-09-23"
|
||||||
|
compatibility_flags = ["nodejs_compat"]
|
||||||
|
assets = { directory = ".open-next/assets", binding = "ASSETS" }
|
||||||
+16
@@ -17,6 +17,16 @@
|
|||||||
"dependsOn": ["^build", "generate"],
|
"dependsOn": ["^build", "generate"],
|
||||||
"outputs": [".next/**", "!.next/cache/**", "dist"]
|
"outputs": [".next/**", "!.next/cache/**", "dist"]
|
||||||
},
|
},
|
||||||
|
// Build the package for v2
|
||||||
|
"build:v2": {
|
||||||
|
"dependsOn": ["^build:v2", "^build", "generate"],
|
||||||
|
"outputs": [".next/**", "!.next/cache/**", "dist"]
|
||||||
|
},
|
||||||
|
// Build the package for v2 (cloudflare)
|
||||||
|
"build:v2:cloudflare": {
|
||||||
|
"dependsOn": ["^build:v2:cloudflare", "^build:v2", "generate"],
|
||||||
|
"outputs": [".next/**", "!.next/cache/**", "dist", ".open-next/**"]
|
||||||
|
},
|
||||||
// Build the package for Cloudflare Pages
|
// Build the package for Cloudflare Pages
|
||||||
"build:cloudflare": {
|
"build:cloudflare": {
|
||||||
"dependsOn": ["^build", "generate"],
|
"dependsOn": ["^build", "generate"],
|
||||||
@@ -45,6 +55,12 @@
|
|||||||
"cache": false,
|
"cache": false,
|
||||||
"dependsOn": ["generate"]
|
"dependsOn": ["generate"]
|
||||||
},
|
},
|
||||||
|
// Start the package in development mode for v2
|
||||||
|
"dev:v2": {
|
||||||
|
"persistent": true,
|
||||||
|
"cache": false,
|
||||||
|
"dependsOn": ["generate"]
|
||||||
|
},
|
||||||
// Script run when the package is deployed
|
// Script run when the package is deployed
|
||||||
"release": {
|
"release": {
|
||||||
"dependsOn": ["^release", "build"],
|
"dependsOn": ["^release", "build"],
|
||||||
|
|||||||
Reference in New Issue
Block a user