mirror of
https://github.com/facebook/docusaurus.git
synced 2025-12-26 01:33:02 +00:00
Compare commits
154 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bc5c90dc7 | ||
|
|
ee9dfd5d0b | ||
|
|
7f5d6122d2 | ||
|
|
47a98a1d6e | ||
|
|
75a529bb8f | ||
|
|
acd96cb3f0 | ||
|
|
0799e20b67 | ||
|
|
d4a66aa2ed | ||
|
|
0f8cda2f65 | ||
|
|
59bce2b21c | ||
|
|
21b7b7fd02 | ||
|
|
5e77169b35 | ||
|
|
616dec13b3 | ||
|
|
455358880d | ||
|
|
6efe49abaf | ||
|
|
ebb0d0e3bb | ||
|
|
eccc778249 | ||
|
|
c6c0f636a8 | ||
|
|
c32ed21431 | ||
|
|
c6a86ff717 | ||
|
|
d379344e6a | ||
|
|
5c7ba4e9d6 | ||
|
|
a72be12acc | ||
|
|
d6cbf6f9e8 | ||
|
|
f13adecec0 | ||
|
|
89633b4d33 | ||
|
|
bbec801e3f | ||
|
|
366b4a1b26 | ||
|
|
66dbc7da39 | ||
|
|
37530aaafb | ||
|
|
7880f26a07 | ||
|
|
b61745a9e2 | ||
|
|
05acc90c01 | ||
|
|
963159b3c1 | ||
|
|
acc66c14b0 | ||
|
|
a24b8ad5ed | ||
|
|
9c85f8689a | ||
|
|
6a38ccdfb0 | ||
|
|
c81409b5a3 | ||
|
|
f8bedbd0a0 | ||
|
|
7651d42e11 | ||
|
|
a4742594a9 | ||
|
|
74542245b3 | ||
|
|
6b3ed1ee65 | ||
|
|
0372ecd1e9 | ||
|
|
e133e8d6d2 | ||
|
|
5e846f6496 | ||
|
|
bca9ce746f | ||
|
|
c8fc3311f1 | ||
|
|
d9d29046ec | ||
|
|
5423a779c1 | ||
|
|
2a10b5453f | ||
|
|
ccf03d6e38 | ||
|
|
8b44659c50 | ||
|
|
258769d1c8 | ||
|
|
19ea360fd5 | ||
|
|
43665c5f08 | ||
|
|
7b33dc109b | ||
|
|
505ff8d228 | ||
|
|
13ec1eaa3e | ||
|
|
a7c0aab8be | ||
|
|
ac630f8279 | ||
|
|
598af3b8e8 | ||
|
|
8f32716def | ||
|
|
f89fbae282 | ||
|
|
b4cc50a423 | ||
|
|
c3e5db1dc3 | ||
|
|
e41fa2e191 | ||
|
|
016b80b55d | ||
|
|
aff609a848 | ||
|
|
c24d7dc86a | ||
|
|
422f5d91ef | ||
|
|
70f6312c0d | ||
|
|
50ca86aa04 | ||
|
|
4dc05762ee | ||
|
|
0bc6b6e394 | ||
|
|
9c689880ed | ||
|
|
a9bab411ad | ||
|
|
80a4518ffb | ||
|
|
af3d5caf86 | ||
|
|
24d01bb839 | ||
|
|
1c484e1d59 | ||
|
|
e8f72697a5 | ||
|
|
3b7778a57a | ||
|
|
7d4e430cf9 | ||
|
|
3dacdf33c9 | ||
|
|
72c48b5806 | ||
|
|
6021faf97f | ||
|
|
bfdcc73e17 | ||
|
|
ece9432b3a | ||
|
|
305910758b | ||
|
|
c131034bc2 | ||
|
|
34b355b550 | ||
|
|
87d878471e | ||
|
|
a4c33bfea5 | ||
|
|
27945b3b34 | ||
|
|
b1391349f5 | ||
|
|
942094af3c | ||
|
|
749b45e629 | ||
|
|
2febb76fae | ||
|
|
12bcad9837 | ||
|
|
c81808459d | ||
|
|
e83747f323 | ||
|
|
2655db686e | ||
|
|
0239c18b78 | ||
|
|
a3324ff65e | ||
|
|
2adbc0d6a0 | ||
|
|
0306d18240 | ||
|
|
3733dfee52 | ||
|
|
1808945c1f | ||
|
|
e0524a5c84 | ||
|
|
4c3daab18b | ||
|
|
fad80c4421 | ||
|
|
da08536816 | ||
|
|
d9d7e855c2 | ||
|
|
b0df38d317 | ||
|
|
0f7cf285a3 | ||
|
|
385102df9f | ||
|
|
1592094206 | ||
|
|
e82cd48842 | ||
|
|
e14caf1f78 | ||
|
|
96c38d5fdd | ||
|
|
ef71ddf937 | ||
|
|
1cbc0118b0 | ||
|
|
068d4c63a9 | ||
|
|
a94fdbe21c | ||
|
|
ee332e951d | ||
|
|
3ed2ea2ecf | ||
|
|
6cb955987e | ||
|
|
a392c33b8e | ||
|
|
b54103be5d | ||
|
|
f811e2dbf4 | ||
|
|
315e3f617e | ||
|
|
a36ac15947 | ||
|
|
60df4936aa | ||
|
|
d3ab0e63a0 | ||
|
|
ef23f546fd | ||
|
|
e17a26ab45 | ||
|
|
18b47fdfc1 | ||
|
|
dacfc17fb4 | ||
|
|
ffa7525ff9 | ||
|
|
1c454a9430 | ||
|
|
dd6396dd39 | ||
|
|
ad56077c70 | ||
|
|
2b15b85645 | ||
|
|
264774a550 | ||
|
|
68aa3c876b | ||
|
|
76518fd206 | ||
|
|
7e87ea320c | ||
|
|
b126e643d1 | ||
|
|
8a5a0c110e | ||
|
|
8958c11c58 | ||
|
|
ba18a8b8b9 | ||
|
|
5d858f5e66 |
|
|
@ -21,6 +21,7 @@
|
||||||
],
|
],
|
||||||
"ignorePaths": [
|
"ignorePaths": [
|
||||||
"CHANGELOG.md",
|
"CHANGELOG.md",
|
||||||
|
"CHANGELOG-v*.md",
|
||||||
"patches",
|
"patches",
|
||||||
"packages/docusaurus-theme-translations/locales",
|
"packages/docusaurus-theme-translations/locales",
|
||||||
"packages/docusaurus-plugin-ideal-image/src/theme/IdealImageLegacy",
|
"packages/docusaurus-plugin-ideal-image/src/theme/IdealImageLegacy",
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ module.exports = {
|
||||||
],
|
],
|
||||||
'no-useless-escape': WARNING,
|
'no-useless-escape': WARNING,
|
||||||
'no-void': [ERROR, {allowAsStatement: true}],
|
'no-void': [ERROR, {allowAsStatement: true}],
|
||||||
'prefer-destructuring': WARNING,
|
'prefer-destructuring': OFF,
|
||||||
'prefer-named-capture-group': WARNING,
|
'prefer-named-capture-group': WARNING,
|
||||||
'prefer-template': WARNING,
|
'prefer-template': WARNING,
|
||||||
yoda: WARNING,
|
yoda: WARNING,
|
||||||
|
|
@ -304,7 +304,7 @@ module.exports = {
|
||||||
'jest/prefer-expect-resolves': WARNING,
|
'jest/prefer-expect-resolves': WARNING,
|
||||||
'jest/prefer-lowercase-title': [WARNING, {ignore: ['describe']}],
|
'jest/prefer-lowercase-title': [WARNING, {ignore: ['describe']}],
|
||||||
'jest/prefer-spy-on': WARNING,
|
'jest/prefer-spy-on': WARNING,
|
||||||
'jest/prefer-to-be': WARNING,
|
'jest/prefer-to-be': OFF,
|
||||||
'jest/prefer-to-have-length': WARNING,
|
'jest/prefer-to-have-length': WARNING,
|
||||||
'jest/require-top-level-describe': ERROR,
|
'jest/require-top-level-describe': ERROR,
|
||||||
'jest/valid-title': [
|
'jest/valid-title': [
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,10 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository code
|
- name: Check out repository code
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
@ -32,3 +32,5 @@ jobs:
|
||||||
run: yarn || yarn || yarn
|
run: yarn || yarn || yarn
|
||||||
- name: Build blog-only
|
- name: Build blog-only
|
||||||
run: yarn workspace website build:blogOnly
|
run: yarn workspace website build:blogOnly
|
||||||
|
env:
|
||||||
|
DOCUSAURUS_PERF_LOGGER: 'true'
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
@ -37,26 +37,27 @@ jobs:
|
||||||
- name: Build Hash Router
|
- name: Build Hash Router
|
||||||
run: yarn build:website:fast
|
run: yarn build:website:fast
|
||||||
env:
|
env:
|
||||||
|
DOCUSAURUS_PERF_LOGGER: 'true'
|
||||||
DOCUSAURUS_ROUTER: 'hash'
|
DOCUSAURUS_ROUTER: 'hash'
|
||||||
# Note: hash router + baseUrl do not play well together
|
# Note: hash router + baseUrl do not play well together
|
||||||
# This would host at https://facebook.github.io/docusaurus/#/docusaurus/
|
# This would host at https://facebook.github.io/docusaurus/#/docusaurus/
|
||||||
# BASE_URL: '/docusaurus/' # hash router +
|
# BASE_URL: '/docusaurus/' # hash router +
|
||||||
|
|
||||||
- name: Upload Website artifact
|
- name: Upload Website artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
with:
|
with:
|
||||||
name: website-hash-router-archive
|
name: website-hash-router-archive
|
||||||
path: website/build
|
path: website/build
|
||||||
|
|
||||||
#- name: Upload Website Pages artifact
|
#- name: Upload Website Pages artifact
|
||||||
# uses: actions/upload-pages-artifact@v3
|
# uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
|
||||||
# with:
|
# with:
|
||||||
# path: website/build
|
# path: website/build
|
||||||
|
|
||||||
# Deploy to https://facebook.github.io/docusaurus/
|
# Deploy to https://facebook.github.io/docusaurus/
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
if: ${{ github.event_name != 'pull_request' && github.ref_name == 'main' }}
|
if: ${{ github.event_name != 'pull_request' && github.ref_name == 'main' }}
|
||||||
uses: peaceiris/actions-gh-pages@v4
|
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
publish_dir: website/build
|
publish_dir: website/build
|
||||||
|
|
@ -80,4 +81,4 @@ jobs:
|
||||||
# steps:
|
# steps:
|
||||||
# - name: Deploy to GitHub Pages
|
# - name: Deploy to GitHub Pages
|
||||||
# id: deployment
|
# id: deployment
|
||||||
# uses: actions/deploy-pages@v4
|
# uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
|
||||||
|
|
|
||||||
|
|
@ -41,14 +41,14 @@ jobs:
|
||||||
DOCUSAURUS_INFRA: ['SLOWER', 'FASTER']
|
DOCUSAURUS_INFRA: ['SLOWER', 'FASTER']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- name: Track build size changes
|
- name: Track build size changes
|
||||||
uses: preactjs/compressed-size-action@946a292cd35bd1088e0d7eb92b69d1a8d5b5d76a # v2
|
uses: preactjs/compressed-size-action@8518045ed95e94e971b83333085e1cb99aa18aa8 # v2.9.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
build-script: build:website:fast
|
build-script: build:website:fast
|
||||||
|
|
@ -62,6 +62,7 @@ jobs:
|
||||||
comment-key: DOCUSAURUS_INFRA_${{ matrix.DOCUSAURUS_INFRA }}
|
comment-key: DOCUSAURUS_INFRA_${{ matrix.DOCUSAURUS_INFRA }}
|
||||||
env:
|
env:
|
||||||
DOCUSAURUS_SLOWER: ${{ matrix.DOCUSAURUS_INFRA == 'SLOWER' && 'true' || 'false' }}
|
DOCUSAURUS_SLOWER: ${{ matrix.DOCUSAURUS_INFRA == 'SLOWER' && 'true' || 'false' }}
|
||||||
|
DOCUSAURUS_PERF_LOGGER: 'true'
|
||||||
|
|
||||||
# Ensures build times stay under reasonable thresholds
|
# Ensures build times stay under reasonable thresholds
|
||||||
build-time:
|
build-time:
|
||||||
|
|
@ -73,9 +74,9 @@ jobs:
|
||||||
DOCUSAURUS_INFRA: ['SLOWER', 'FASTER']
|
DOCUSAURUS_INFRA: ['SLOWER', 'FASTER']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
@ -88,6 +89,7 @@ jobs:
|
||||||
timeout-minutes: ${{ matrix.DOCUSAURUS_INFRA == 'SLOWER' && 3 || 2 }}
|
timeout-minutes: ${{ matrix.DOCUSAURUS_INFRA == 'SLOWER' && 3 || 2 }}
|
||||||
env:
|
env:
|
||||||
DOCUSAURUS_SLOWER: ${{ matrix.DOCUSAURUS_INFRA == 'SLOWER' && 'true' || 'false' }}
|
DOCUSAURUS_SLOWER: ${{ matrix.DOCUSAURUS_INFRA == 'SLOWER' && 'true' || 'false' }}
|
||||||
|
DOCUSAURUS_PERF_LOGGER: 'true'
|
||||||
|
|
||||||
# Ensure build with a warm cache does not increase too much
|
# Ensure build with a warm cache does not increase too much
|
||||||
- name: Build (warm cache)
|
- name: Build (warm cache)
|
||||||
|
|
@ -96,5 +98,6 @@ jobs:
|
||||||
timeout-minutes: ${{ matrix.DOCUSAURUS_INFRA == 'SLOWER' && 1 || 2 }}
|
timeout-minutes: ${{ matrix.DOCUSAURUS_INFRA == 'SLOWER' && 1 || 2 }}
|
||||||
env:
|
env:
|
||||||
DOCUSAURUS_SLOWER: ${{ matrix.DOCUSAURUS_INFRA == 'SLOWER' && 'true' || 'false' }}
|
DOCUSAURUS_SLOWER: ${{ matrix.DOCUSAURUS_INFRA == 'SLOWER' && 'true' || 'false' }}
|
||||||
|
DOCUSAURUS_PERF_LOGGER: 'true'
|
||||||
|
|
||||||
# TODO post a GitHub comment with build with perf warnings?
|
# TODO post a GitHub comment with build with perf warnings?
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,11 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Needed to get the commit number with "git rev-list --count HEAD"
|
fetch-depth: 0 # Needed to get the commit number with "git rev-list --count HEAD"
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,12 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # 3.26.5
|
uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # 4.31.0
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # 3.26.5
|
uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # 4.31.0
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,6 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Dependency Review
|
- name: Dependency Review
|
||||||
uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # 4.7.1
|
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # 4.8.2
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name: Lighthouse Report
|
name: Lighthouse Report
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- docusaurus-v**
|
- docusaurus-v**
|
||||||
|
|
@ -21,10 +21,10 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
@ -37,7 +37,7 @@ jobs:
|
||||||
|
|
||||||
- name: Audit URLs using Lighthouse
|
- name: Audit URLs using Lighthouse
|
||||||
id: lighthouse_audit
|
id: lighthouse_audit
|
||||||
uses: treosh/lighthouse-ci-action@2f8dda6cf4de7d73b29853c3f29e73a01e297bd8 # 12.1.0
|
uses: treosh/lighthouse-ci-action@fcd65974f7c4c2bf0ee9d09b84d2489183c29726 # 12.6.1
|
||||||
with:
|
with:
|
||||||
urls: |
|
urls: |
|
||||||
http://localhost:3000
|
http://localhost:3000
|
||||||
|
|
@ -53,7 +53,7 @@ jobs:
|
||||||
|
|
||||||
- name: Format lighthouse score
|
- name: Format lighthouse score
|
||||||
id: format_lighthouse_score
|
id: format_lighthouse_score
|
||||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # 7.0.1
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # 8.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|
@ -65,7 +65,7 @@ jobs:
|
||||||
|
|
||||||
- name: Add Lighthouse stats as comment
|
- name: Add Lighthouse stats as comment
|
||||||
id: comment_to_pr
|
id: comment_to_pr
|
||||||
uses: marocchino/sticky-pull-request-comment@67d0dec7b07ed060a405f9b2a64b8ab319fdd7db # 2.9.2
|
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # 2.9.4
|
||||||
with:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
number: ${{ github.event.pull_request.number }}
|
number: ${{ github.event.pull_request.number }}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ jobs:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
with:
|
with:
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
ref: ${{ github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
|
|
@ -42,6 +42,6 @@ jobs:
|
||||||
- name: Print Diff
|
- name: Print Diff
|
||||||
run: git diff
|
run: git diff
|
||||||
|
|
||||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
- uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||||
with:
|
with:
|
||||||
commit_message: 'refactor: apply lint autofix'
|
commit_message: 'refactor: apply lint autofix'
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,12 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: ['18.0', '20', '22', '24']
|
node: ['20.0', '20', '22', '24', '25.1']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Use Node.js ${{ matrix.node }}
|
- name: Use Node.js ${{ matrix.node }}
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
@ -72,6 +72,48 @@ jobs:
|
||||||
DOCUSAURUS_PERF_LOGGER: 'true'
|
DOCUSAURUS_PERF_LOGGER: 'true'
|
||||||
working-directory: ../test-website
|
working-directory: ../test-website
|
||||||
|
|
||||||
|
yarn-v1-windows:
|
||||||
|
name: E2E — Yarn v1 Windows
|
||||||
|
timeout-minutes: 30
|
||||||
|
runs-on: windows-8-core
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
|
- name: Use Node.js LTS
|
||||||
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
cache: yarn
|
||||||
|
- name: Installation
|
||||||
|
run: yarn || yarn || yarn
|
||||||
|
- name: Generate test-website project against main branch
|
||||||
|
# Not using test-release.sh => no verdaccio docker image on Windows
|
||||||
|
# run: bash ./admin/scripts/test-release.sh -s
|
||||||
|
run: yarn create-docusaurus test-website-in-workspace classic --typescript
|
||||||
|
- name: Install test-website project with Yarn v1
|
||||||
|
run: yarn || yarn || yarn
|
||||||
|
working-directory: test-website-in-workspace
|
||||||
|
- name: Start test-website project
|
||||||
|
run: yarn start --no-open
|
||||||
|
working-directory: test-website-in-workspace
|
||||||
|
env:
|
||||||
|
E2E_TEST: true
|
||||||
|
- name: Build test-website project
|
||||||
|
# We build 2 locales to ensure a localized site doesn't leak memory
|
||||||
|
# See https://github.com/facebook/docusaurus/pull/10599
|
||||||
|
run: yarn build --locale en --locale fr
|
||||||
|
env:
|
||||||
|
# Our website should build even with limited memory
|
||||||
|
# See https://github.com/facebook/docusaurus/pull/10590
|
||||||
|
NODE_OPTIONS: '--max-old-space-size=300'
|
||||||
|
DOCUSAURUS_PERF_LOGGER: 'true'
|
||||||
|
working-directory: test-website-in-workspace
|
||||||
|
- name: Upload Website artifact
|
||||||
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
|
with:
|
||||||
|
name: website-e2e-windows
|
||||||
|
path: test-website-in-workspace/build
|
||||||
|
|
||||||
yarn-berry:
|
yarn-berry:
|
||||||
name: E2E — Yarn Berry
|
name: E2E — Yarn Berry
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|
@ -82,9 +124,9 @@ jobs:
|
||||||
variant: [-s, -st]
|
variant: [-s, -st]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Use Node.js LTS
|
- name: Use Node.js LTS
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
@ -151,9 +193,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Use Node.js LTS
|
- name: Use Node.js LTS
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
@ -191,9 +233,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Use Node.js LTS
|
- name: Use Node.js LTS
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,9 @@ jobs:
|
||||||
variant: ['js', 'ts']
|
variant: ['js', 'ts']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Set up Node LTS
|
- name: Set up Node LTS
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: lts/*
|
node-version: lts/*
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,14 @@ jobs:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: ['18.0', '20', '22', '24']
|
node: ['20.0', '20', '22', '24', '25.1']
|
||||||
steps:
|
steps:
|
||||||
- name: Support longpaths
|
- name: Support longpaths
|
||||||
run: git config --system core.longpaths true
|
run: git config --system core.longpaths true
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Use Node.js ${{ matrix.node }}
|
- name: Use Node.js ${{ matrix.node }}
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
@ -54,14 +54,20 @@ jobs:
|
||||||
run: yarn workspace website test:swizzle:wrap:ts
|
run: yarn workspace website test:swizzle:wrap:ts
|
||||||
- name: Docusaurus Build
|
- name: Docusaurus Build
|
||||||
run: yarn build:website:fast
|
run: yarn build:website:fast
|
||||||
|
env:
|
||||||
|
DOCUSAURUS_PERF_LOGGER: 'true'
|
||||||
|
|
||||||
- name: TypeCheck website
|
- name: TypeCheck website
|
||||||
# TODO temporary, remove TS skipLibCheck
|
|
||||||
# see https://github.com/facebook/docusaurus/pull/10486
|
# see https://github.com/facebook/docusaurus/pull/10486
|
||||||
run: yarn workspace website typecheck --project tsconfig.skipLibCheck.json
|
run: yarn workspace website typecheck
|
||||||
- name: TypeCheck website - min version - v5.1
|
- name: TypeCheck website - min version - v5.1
|
||||||
run: |
|
run: |
|
||||||
yarn add typescript@5.1.6 --exact -D -W --ignore-scripts
|
yarn add typescript@5.1.6 --exact -D -W --ignore-scripts
|
||||||
|
|
||||||
|
# DocSearch@4/ai@5 doesn't support TS 5.1 (with skipLibCheck=false)
|
||||||
|
jq '.resolutions."@docsearch/react" = "^3.9.0"' package.json > package.json.tmp && mv -Force package.json.tmp package.json
|
||||||
|
yarn add @docsearch/react@^3.9.0 --exact -D -W --ignore-scripts
|
||||||
|
|
||||||
yarn workspace website typecheck
|
yarn workspace website typecheck
|
||||||
- name: TypeCheck website - max version - Latest
|
- name: TypeCheck website - max version - Latest
|
||||||
# For latest TS there are often lib check errors, so we disable it
|
# For latest TS there are often lib check errors, so we disable it
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,12 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: ['18.0', '20', '22', '24']
|
node: ['20.0', '20', '22', '24', '25.1']
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||||
- name: Use Node.js ${{ matrix.node }}
|
- name: Use Node.js ${{ matrix.node }}
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|
@ -55,12 +55,16 @@ jobs:
|
||||||
run: yarn workspace website test:css-order
|
run: yarn workspace website test:css-order
|
||||||
|
|
||||||
- name: TypeCheck website
|
- name: TypeCheck website
|
||||||
# TODO temporary, remove TS skipLibCheck
|
|
||||||
# see https://github.com/facebook/docusaurus/pull/10486
|
# see https://github.com/facebook/docusaurus/pull/10486
|
||||||
run: yarn workspace website typecheck --project tsconfig.skipLibCheck.json
|
run: yarn workspace website typecheck
|
||||||
- name: TypeCheck website - min version - v5.1
|
- name: TypeCheck website - min version - v5.1
|
||||||
run: |
|
run: |
|
||||||
yarn add typescript@5.1.6 --exact -D -W --ignore-scripts
|
yarn add typescript@5.1.6 --exact -D -W --ignore-scripts
|
||||||
|
|
||||||
|
# DocSearch@4/ai@5 doesn't support TS 5.1 (with skipLibCheck=false)
|
||||||
|
jq '.resolutions."@docsearch/react" = "^3.9.0"' package.json > package.json.tmp && mv -f package.json.tmp package.json
|
||||||
|
yarn add @docsearch/react@^3.9.0 --exact -D -W --ignore-scripts
|
||||||
|
|
||||||
yarn workspace website typecheck
|
yarn workspace website typecheck
|
||||||
- name: TypeCheck website - max version - Latest
|
- name: TypeCheck website - max version - Latest
|
||||||
# For latest TS there are often lib check errors, so we disable it
|
# For latest TS there are often lib check errors, so we disable it
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ website/i18n/**/*
|
||||||
.netlify
|
.netlify
|
||||||
|
|
||||||
website/rspack-tracing.json
|
website/rspack-tracing.json
|
||||||
|
website/rspack-tracing.pftrace
|
||||||
website/bundler-cpu-profile.json
|
website/bundler-cpu-profile.json
|
||||||
website/profile.json.gz
|
website/profile.json.gz
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
dist
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
.yarn
|
.yarn
|
||||||
build
|
**/build/**
|
||||||
coverage
|
coverage
|
||||||
.docusaurus
|
.docusaurus
|
||||||
.idea
|
.idea
|
||||||
|
|
@ -11,6 +11,8 @@ coverage
|
||||||
|
|
||||||
jest/vendor
|
jest/vendor
|
||||||
|
|
||||||
|
argos/test-results
|
||||||
|
|
||||||
packages/lqip-loader/lib/
|
packages/lqip-loader/lib/
|
||||||
packages/docusaurus/lib/
|
packages/docusaurus/lib/
|
||||||
packages/docusaurus-*/lib/*
|
packages/docusaurus-*/lib/*
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
7254
CHANGELOG.md
7254
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
|
|
@ -82,7 +82,7 @@ Apart from the `good first issue`, the following labels are also worth looking a
|
||||||
- [`help wanted`](https://github.com/facebook/docusaurus/labels/help%20wanted): if you have specific knowledge in one domain, working on these issues can make your expertise shine.
|
- [`help wanted`](https://github.com/facebook/docusaurus/labels/help%20wanted): if you have specific knowledge in one domain, working on these issues can make your expertise shine.
|
||||||
- [`status: accepting pr`](https://github.com/facebook/docusaurus/labels/status%3A%20accepting%20pr): community contributors can feel free to claim any of these.
|
- [`status: accepting pr`](https://github.com/facebook/docusaurus/labels/status%3A%20accepting%20pr): community contributors can feel free to claim any of these.
|
||||||
|
|
||||||
If you want to work on any of these issues, just drop a message saying "I'd like to work on this", and we will assign the issue to you and update the issue's status as "claimed". **You are expected to send a pull request within seven days** after that, so we can still delegate the issue to someone else if you are unavailable.
|
If you want to work on any of these issues, just drop a message saying "I am working on this". **You do not need to ask for assignment to work on any issue explicitly marked as welcoming external contributions.** However, don't "cookie lick", or squat on an issue without actually sending a PR. You are automatically considered as giving up if you don't **send a PR within seven days after your comment**, and the issue automatically becomes up for grabs again.
|
||||||
|
|
||||||
Alternatively, when opening an issue, you can also click the "self service" checkbox to indicate that you'd like to work on the issue yourself, which will also make us see the issue as "claimed".
|
Alternatively, when opening an issue, you can also click the "self service" checkbox to indicate that you'd like to work on the issue yourself, which will also make us see the issue as "claimed".
|
||||||
|
|
||||||
|
|
@ -214,6 +214,16 @@ After you have signed the CLA, the CLA bot would automatically update the PR sta
|
||||||
|
|
||||||
If it happens that you were unavailable and your PR gets closed, feel free to reopen once it's ready! We are still happy to review it, help you complete it, and eventually merge it.
|
If it happens that you were unavailable and your PR gets closed, feel free to reopen once it's ready! We are still happy to review it, help you complete it, and eventually merge it.
|
||||||
|
|
||||||
|
### AI-assisted PRs
|
||||||
|
|
||||||
|
We welcome the use of AI tools for authoring PRs, and we love to see people pushing the boundaries of AI capabilities. The core team actively uses different AI tools in our development process. However, we are aware that **many people are sending entirely AI-generated PRs as a low-effort way to farm OSS contributions**, so please be mindful of the following etiquette to show your respect for our time and our codebase:
|
||||||
|
|
||||||
|
- **Be transparent**: If a significant portion of your code is AI generated, please indicate that in your PR description.
|
||||||
|
- **Be accountable**: You are responsible for the code you submit, regardless of whether it was generated by AI or written by you. You should be able to explain every line of the code, ensure all tests pass, and address our reviews.
|
||||||
|
- **Be reasonable**: Sometimes we receive 1k LOC PRs that are obviously AI-generated and implement unsolicited features. Please note that significant changes require prior communication and approval from the team in the form of an issue.
|
||||||
|
|
||||||
|
We retain the right to close any PR that we deem as unproductive or low-effort, even when we agree with the spirit of the change.
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
When adding a new breaking change, follow this template in your pull request:
|
When adding a new breaking change, follow this template in your pull request:
|
||||||
|
|
@ -227,6 +237,10 @@ When adding a new breaking change, follow this template in your pull request:
|
||||||
- **Severity (number of people affected x effort)**:
|
- **Severity (number of people affected x effort)**:
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> Breaking changes should be discussed in the issue tracker before being implemented.
|
||||||
|
|
||||||
### What Happens Next?
|
### What Happens Next?
|
||||||
|
|
||||||
The core Docusaurus team will be monitoring pull requests. Do help us by keeping pull requests consistent by following the guidelines above.
|
The core Docusaurus team will be monitoring pull requests. Do help us by keeping pull requests consistent by following the guidelines above.
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ Short on time? Check out our [5-minute tutorial ⏱️](https://tutorial.docusau
|
||||||
|
|
||||||
- **Customizable**
|
- **Customizable**
|
||||||
|
|
||||||
> While Docusaurus ships with the key pages and sections you need to get started, including a home page, a docs section, a [blog](https://docusaurus.io/docs/blog), and additional support pages, it is also [customizable](https://docusaurus.io/docs/creating-pages) as well to ensure you have a site that is [uniquely yours](https://docusaurus.io/docs/styling-layout).
|
> While Docusaurus ships with the key pages and sections you need to get started, including a home page, a docs section, a [blog](https://docusaurus.io/docs/blog), and additional support pages, it is also [customizable](https://docusaurus.io/docs/creating-pages) to ensure you have a site that is [uniquely yours](https://docusaurus.io/docs/styling-layout).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
@ -120,4 +120,4 @@ The Docusaurus documentation (e.g., `.md` files in the `/docs` folder) is [Creat
|
||||||
|
|
||||||
[](https://rocketvalidator.com/)
|
[](https://rocketvalidator.com/)
|
||||||
|
|
||||||
[Rocket Validator](https://rocketvalidator.com/) helps us find HTML markup or accessibility issues.
|
[Rocket Validator](https://rocketvalidator.com/) helps us find [HTML markup and accessibility issues](https://rocketvalidator.com/stats/docusaurus.io).
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "new.docusaurus.io",
|
"name": "new.docusaurus.io",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npx --package netlify-cli netlify dev"
|
"start": "npx --package netlify-cli netlify dev"
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ Not all labels will appear in the changelog—some are designed not to. However,
|
||||||
is:pr is:merged sort:updated-desc -label:"pr: breaking change","pr: new feature","pr: bug fix","pr: performance","pr: polish","pr: documentation","pr: maintenance","pr: internal","pr: dependencies","pr: showcase"
|
is:pr is:merged sort:updated-desc -label:"pr: breaking change","pr: new feature","pr: bug fix","pr: performance","pr: polish","pr: documentation","pr: maintenance","pr: internal","pr: dependencies","pr: showcase"
|
||||||
```
|
```
|
||||||
|
|
||||||
[Check tags of all recently merged Pull-Requests](https://github.com/facebook/docusaurus/pulls?q=is%3Apr+is%3Amerged+sort%3Aupdated-desc+-label%3A%22pr%3A+breaking+change%22%2C%22pr%3A+new+feature%22%2C%22pr%3A+bug+fix%22%2C%22pr%3A+performance%22%2C%22pr%3A+polish%22%2C%22pr%3A+documentation%22%2C%22pr%3A+maintenance%22%2C%22pr%3A+internal%22%2C%22pr%3A+dependencies%22%2C%22pr%3A+showcase%22)
|
[Check tags of all recently merged Pull-Requests](https://github.com/facebook/docusaurus/pulls?q=is%3Apr+is%3Amerged+sort%3Aupdated-desc+-label%3A%22pr%3A+breaking+change%22%2C%22pr%3A+new+feature%22%2C%22pr%3A+bug+fix%22%2C%22pr%3A+performance%22%2C%22pr%3A+polish%22%2C%22pr%3A+documentation%22%2C%22pr%3A+maintenance%22%2C%22pr%3A+internal%22%2C%22pr%3A+dependencies%22%2C%22pr%3A+showcase%22%2C%22pr%3A+ignore%22%2C%22pr%3A+translations%22+)
|
||||||
|
|
||||||
Some general principles about the labeling process:
|
Some general principles about the labeling process:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ git diff --name-only -- '*.json' | sed 's, ,\\&,g' | xargs git checkout --
|
||||||
# The website is generated outside the repo to minimize chances of yarn resolving the wrong version
|
# The website is generated outside the repo to minimize chances of yarn resolving the wrong version
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
|
echo Generating test-website in `pwd`
|
||||||
|
|
||||||
# Build skeleton website with new version
|
# Build skeleton website with new version
|
||||||
npm_config_registry="$CUSTOM_REGISTRY_URL" npx --yes --loglevel silly create-docusaurus@"$NEW_VERSION" test-website classic --javascript $EXTRA_OPTS
|
npm_config_registry="$CUSTOM_REGISTRY_URL" npx --yes --loglevel silly create-docusaurus@"$NEW_VERSION" test-website classic --javascript $EXTRA_OPTS
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "test-bad-package",
|
"name": "test-bad-package",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdx-js/react": "1.0.1",
|
"@mdx-js/react": "1.0.1",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "argos",
|
"name": "argos",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"description": "Argos visual diff tests",
|
"description": "Argos visual diff tests",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|
|
||||||
|
|
@ -134,11 +134,6 @@ function throwOnConsole(page: Page) {
|
||||||
// it's already happening in main branch
|
// it's already happening in main branch
|
||||||
'Failed to load resource: the server responded with a status of 404 (Not Found)',
|
'Failed to load resource: the server responded with a status of 404 (Not Found)',
|
||||||
|
|
||||||
// TODO legit hydration bugs to fix on embeds of /docs/styling-layout
|
|
||||||
// useLocation() returns window.search/hash immediately :s
|
|
||||||
'/docs/configuration?docusaurus-theme=light',
|
|
||||||
'/docs/configuration?docusaurus-theme=dark',
|
|
||||||
|
|
||||||
// Warning because react-live not supporting React automatic JSX runtime
|
// Warning because react-live not supporting React automatic JSX runtime
|
||||||
// See https://github.com/FormidableLabs/react-live/issues/405
|
// See https://github.com/FormidableLabs/react-live/issues/405
|
||||||
'Your app (or one of its dependencies) is using an outdated JSX transform. Update to the modern JSX transform for faster performance',
|
'Your app (or one of its dependencies) is using an outdated JSX transform. Update to the modern JSX transform for faster performance',
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,19 @@ languages_mapping: &languages_mapping
|
||||||
two_letters_code:
|
two_letters_code:
|
||||||
pt-BR: pt-BR
|
pt-BR: pt-BR
|
||||||
|
|
||||||
|
# Crowdin regularly update their MDX parser
|
||||||
|
# Unfortunately, their v2 parser is more "MDX compliant" and thus can't parse
|
||||||
|
# Docusaurus MDX files correctly due to our custom {#headingId} syntax.
|
||||||
|
# Adding this type param permits using their older v1.2 parser.
|
||||||
|
# Note: you can find the version of a file using browser DevTools
|
||||||
|
# The source file icons will have a class such as "file_type_mdx_v1_2"
|
||||||
|
#
|
||||||
|
# TODO fix our headingId syntax
|
||||||
|
# providing an explicit type is annoying and not future-proof
|
||||||
|
# there's a risk that when adding an image in /docs, it will be parsed as mdx
|
||||||
|
# and duplicating source file configs for various extensions is not great either
|
||||||
|
mdx_file_type: &mdx_file_type mdx_v1_2
|
||||||
|
|
||||||
#
|
#
|
||||||
# Files configuration
|
# Files configuration
|
||||||
#
|
#
|
||||||
|
|
@ -27,18 +40,33 @@ files:
|
||||||
- source: /website/i18n/en/**/*
|
- source: /website/i18n/en/**/*
|
||||||
translation: /website/i18n/%two_letters_code%/**/%original_file_name%
|
translation: /website/i18n/%two_letters_code%/**/%original_file_name%
|
||||||
languages_mapping: *languages_mapping
|
languages_mapping: *languages_mapping
|
||||||
|
|
||||||
|
- source: /website/docs/**/*.mdx
|
||||||
|
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-docs/current/**/%original_file_name%
|
||||||
|
languages_mapping: *languages_mapping
|
||||||
|
type: *mdx_file_type
|
||||||
- source: /website/docs/**/*
|
- source: /website/docs/**/*
|
||||||
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-docs/current/**/%original_file_name%
|
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-docs/current/**/%original_file_name%
|
||||||
languages_mapping: *languages_mapping
|
languages_mapping: *languages_mapping
|
||||||
- source: /website/community/**/*
|
ignore: [/**/*.mdx]
|
||||||
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-docs-community/current/**/%original_file_name%
|
|
||||||
|
- source: /website/versioned_docs/**/*.mdx
|
||||||
|
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-docs/**/%original_file_name%
|
||||||
languages_mapping: *languages_mapping
|
languages_mapping: *languages_mapping
|
||||||
|
type: *mdx_file_type
|
||||||
- source: /website/versioned_docs/**/*
|
- source: /website/versioned_docs/**/*
|
||||||
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-docs/**/%original_file_name%
|
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-docs/**/%original_file_name%
|
||||||
languages_mapping: *languages_mapping
|
languages_mapping: *languages_mapping
|
||||||
|
ignore: [/**/*.mdx]
|
||||||
|
|
||||||
|
- source: /website/community/**/*
|
||||||
|
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-docs-community/current/**/%original_file_name%
|
||||||
|
languages_mapping: *languages_mapping
|
||||||
|
|
||||||
- source: /website/blog/**/*
|
- source: /website/blog/**/*
|
||||||
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-blog/**/%original_file_name%
|
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-blog/**/%original_file_name%
|
||||||
languages_mapping: *languages_mapping
|
languages_mapping: *languages_mapping
|
||||||
|
|
||||||
- source: /website/src/pages/**/*
|
- source: /website/src/pages/**/*
|
||||||
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-pages/**/%original_file_name%
|
translation: /website/i18n/%two_letters_code%/docusaurus-plugin-content-pages/**/%original_file_name%
|
||||||
ignore: [/**/*.js, /**/*.jsx, /**/*.ts, /**/*.tsx, /**/*.css]
|
ignore: [/**/*.js, /**/*.jsx, /**/*.ts, /**/*.tsx, /**/*.css]
|
||||||
|
|
|
||||||
|
|
@ -2,40 +2,40 @@
|
||||||
|
|
||||||
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
||||||
|
|
||||||
### Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
### Local Development
|
## Local Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ yarn start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||||
|
|
||||||
### Build
|
## Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ yarn build
|
yarn build
|
||||||
```
|
```
|
||||||
|
|
||||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||||
|
|
||||||
### Deployment
|
## Deployment
|
||||||
|
|
||||||
Using SSH:
|
Using SSH:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ USE_SSH=true yarn deploy
|
USE_SSH=true yarn deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
Not using SSH:
|
Not using SSH:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ GIT_USER=<Your GitHub username> yarn deploy
|
GIT_USER=<Your GitHub username> yarn deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
yangshun:
|
yangshun:
|
||||||
name: Yangshun Tay
|
name: Yangshun Tay
|
||||||
title: Front End Engineer @ Facebook
|
title: Ex-Meta Staff Engineer, Co-founder GreatFrontEnd
|
||||||
url: https://github.com/yangshun
|
url: https://linkedin.com/in/yangshun
|
||||||
image_url: https://github.com/yangshun.png
|
image_url: https://github.com/yangshun.png
|
||||||
page: true
|
page: true
|
||||||
socials:
|
socials:
|
||||||
x: yangshunz
|
x: yangshunz
|
||||||
|
linkedin: yangshun
|
||||||
github: yangshun
|
github: yangshun
|
||||||
|
newsletter: https://www.greatfrontend.com
|
||||||
|
|
||||||
slorber:
|
slorber:
|
||||||
name: Sébastien Lorber
|
name: Sébastien Lorber
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new
|
||||||
|
|
||||||
### What you'll need
|
### What you'll need
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/en/download/) version 18.0 or above:
|
- [Node.js](https://nodejs.org/en/download/) version 20.0 or above:
|
||||||
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
|
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
|
||||||
|
|
||||||
## Generate a new site
|
## Generate a new site
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@ const config: Config = {
|
||||||
tagline: 'Dinosaurs are cool',
|
tagline: 'Dinosaurs are cool',
|
||||||
favicon: 'img/favicon.ico',
|
favicon: 'img/favicon.ico',
|
||||||
|
|
||||||
|
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
|
||||||
|
future: {
|
||||||
|
v4: true, // Improve compatibility with the upcoming Docusaurus v4
|
||||||
|
},
|
||||||
|
|
||||||
// Set the production url of your site here
|
// Set the production url of your site here
|
||||||
url: 'https://your-docusaurus-site.example.com',
|
url: 'https://your-docusaurus-site.example.com',
|
||||||
// Set the /<baseUrl>/ pathname under which your site is served
|
// Set the /<baseUrl>/ pathname under which your site is served
|
||||||
|
|
@ -21,7 +26,6 @@ const config: Config = {
|
||||||
projectName: 'docusaurus', // Usually your repo name.
|
projectName: 'docusaurus', // Usually your repo name.
|
||||||
|
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
onBrokenMarkdownLinks: 'warn',
|
|
||||||
|
|
||||||
// Even if you don't use internationalization, you can use this field to set
|
// Even if you don't use internationalization, you can use this field to set
|
||||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||||
|
|
@ -67,6 +71,9 @@ const config: Config = {
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
// Replace with your project's social card
|
// Replace with your project's social card
|
||||||
image: 'img/docusaurus-social-card.jpg',
|
image: 'img/docusaurus-social-card.jpg',
|
||||||
|
colorMode: {
|
||||||
|
respectPrefersColorScheme: true,
|
||||||
|
},
|
||||||
navbar: {
|
navbar: {
|
||||||
title: 'My Site',
|
title: 'My Site',
|
||||||
logo: {
|
logo: {
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
"dev": "docusaurus start"
|
"dev": "docusaurus start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.7.0",
|
"@docusaurus/core": "3.9.2",
|
||||||
"@docusaurus/preset-classic": "3.7.0",
|
"@docusaurus/preset-classic": "3.9.2",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"prism-react-renderer": "^2.3.0",
|
"prism-react-renderer": "^2.3.0",
|
||||||
|
|
@ -25,9 +25,9 @@
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "3.7.0",
|
"@docusaurus/module-type-aliases": "3.9.2",
|
||||||
"@docusaurus/tsconfig": "3.7.0",
|
"@docusaurus/tsconfig": "3.9.2",
|
||||||
"@docusaurus/types": "3.7.0",
|
"@docusaurus/types": "3.9.2",
|
||||||
"typescript": "~5.6.2"
|
"typescript": "~5.6.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
},
|
},
|
||||||
"description": "Docusaurus example project (classic-typescript template)"
|
"description": "Docusaurus example project (classic-typescript template)"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -2,40 +2,40 @@
|
||||||
|
|
||||||
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
||||||
|
|
||||||
### Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ yarn
|
yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
### Local Development
|
## Local Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ yarn start
|
yarn start
|
||||||
```
|
```
|
||||||
|
|
||||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||||
|
|
||||||
### Build
|
## Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ yarn build
|
yarn build
|
||||||
```
|
```
|
||||||
|
|
||||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||||
|
|
||||||
### Deployment
|
## Deployment
|
||||||
|
|
||||||
Using SSH:
|
Using SSH:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ USE_SSH=true yarn deploy
|
USE_SSH=true yarn deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
Not using SSH:
|
Not using SSH:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ GIT_USER=<Your GitHub username> yarn deploy
|
GIT_USER=<Your GitHub username> yarn deploy
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
yangshun:
|
yangshun:
|
||||||
name: Yangshun Tay
|
name: Yangshun Tay
|
||||||
title: Front End Engineer @ Facebook
|
title: Ex-Meta Staff Engineer, Co-founder GreatFrontEnd
|
||||||
url: https://github.com/yangshun
|
url: https://linkedin.com/in/yangshun
|
||||||
image_url: https://github.com/yangshun.png
|
image_url: https://github.com/yangshun.png
|
||||||
page: true
|
page: true
|
||||||
socials:
|
socials:
|
||||||
x: yangshunz
|
x: yangshunz
|
||||||
|
linkedin: yangshun
|
||||||
github: yangshun
|
github: yangshun
|
||||||
|
newsletter: https://www.greatfrontend.com
|
||||||
|
|
||||||
slorber:
|
slorber:
|
||||||
name: Sébastien Lorber
|
name: Sébastien Lorber
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new
|
||||||
|
|
||||||
### What you'll need
|
### What you'll need
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/en/download/) version 18.0 or above:
|
- [Node.js](https://nodejs.org/en/download/) version 20.0 or above:
|
||||||
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
|
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
|
||||||
|
|
||||||
## Generate a new site
|
## Generate a new site
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,11 @@ const config = {
|
||||||
tagline: 'Dinosaurs are cool',
|
tagline: 'Dinosaurs are cool',
|
||||||
favicon: 'img/favicon.ico',
|
favicon: 'img/favicon.ico',
|
||||||
|
|
||||||
|
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
|
||||||
|
future: {
|
||||||
|
v4: true, // Improve compatibility with the upcoming Docusaurus v4
|
||||||
|
},
|
||||||
|
|
||||||
// Set the production url of your site here
|
// Set the production url of your site here
|
||||||
url: 'https://your-docusaurus-site.example.com',
|
url: 'https://your-docusaurus-site.example.com',
|
||||||
// Set the /<baseUrl>/ pathname under which your site is served
|
// Set the /<baseUrl>/ pathname under which your site is served
|
||||||
|
|
@ -26,7 +31,6 @@ const config = {
|
||||||
projectName: 'docusaurus', // Usually your repo name.
|
projectName: 'docusaurus', // Usually your repo name.
|
||||||
|
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
onBrokenMarkdownLinks: 'warn',
|
|
||||||
|
|
||||||
// Even if you don't use internationalization, you can use this field to set
|
// Even if you don't use internationalization, you can use this field to set
|
||||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||||
|
|
@ -75,6 +79,9 @@ const config = {
|
||||||
({
|
({
|
||||||
// Replace with your project's social card
|
// Replace with your project's social card
|
||||||
image: 'img/docusaurus-social-card.jpg',
|
image: 'img/docusaurus-social-card.jpg',
|
||||||
|
colorMode: {
|
||||||
|
respectPrefersColorScheme: true,
|
||||||
|
},
|
||||||
navbar: {
|
navbar: {
|
||||||
title: 'My Site',
|
title: 'My Site',
|
||||||
logo: {
|
logo: {
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@
|
||||||
"dev": "docusaurus start"
|
"dev": "docusaurus start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.7.0",
|
"@docusaurus/core": "3.9.2",
|
||||||
"@docusaurus/preset-classic": "3.7.0",
|
"@docusaurus/preset-classic": "3.9.2",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"prism-react-renderer": "^2.3.0",
|
"prism-react-renderer": "^2.3.0",
|
||||||
|
|
@ -24,8 +24,8 @@
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "3.7.0",
|
"@docusaurus/module-type-aliases": "3.9.2",
|
||||||
"@docusaurus/types": "3.7.0"
|
"@docusaurus/types": "3.9.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
},
|
},
|
||||||
"description": "Docusaurus example project"
|
"description": "Docusaurus example project"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -32,15 +32,16 @@ const ignorePatterns = [
|
||||||
export default {
|
export default {
|
||||||
rootDir: fileURLToPath(new URL('.', import.meta.url)),
|
rootDir: fileURLToPath(new URL('.', import.meta.url)),
|
||||||
verbose: true,
|
verbose: true,
|
||||||
|
// Default 5s timeout often fails on Windows :s,
|
||||||
|
// see https://github.com/facebook/docusaurus/pull/8259
|
||||||
|
testTimeout: 15000,
|
||||||
setupFiles: ['./jest/setup.js'],
|
setupFiles: ['./jest/setup.js'],
|
||||||
testEnvironmentOptions: {
|
testEnvironmentOptions: {
|
||||||
url: 'https://docusaurus.io/',
|
url: 'https://docusaurus.io/',
|
||||||
},
|
},
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
testPathIgnorePatterns: ignorePatterns,
|
testPathIgnorePatterns: ignorePatterns,
|
||||||
// Default 5s timeout often fails on Windows :s,
|
watchPathIgnorePatterns: ['/\\.docusaurus'],
|
||||||
// see https://github.com/facebook/docusaurus/pull/8259
|
|
||||||
testTimeout: 15000,
|
|
||||||
coveragePathIgnorePatterns: [
|
coveragePathIgnorePatterns: [
|
||||||
...ignorePatterns,
|
...ignorePatterns,
|
||||||
// We also ignore all package entry points
|
// We also ignore all package entry points
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,3 @@ declare module 'to-vfile' {
|
||||||
|
|
||||||
export function read(path: string, encoding?: string): Promise<VFile>;
|
export function read(path: string, encoding?: string): Promise<VFile>;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@testing-utils/git' {
|
|
||||||
const createTempRepo: typeof import('./utils/git').createTempRepo;
|
|
||||||
export {createTempRepo};
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ function normalizePaths<T>(value: T): T {
|
||||||
(val) => val.split(cwdReal).join('<PROJECT_ROOT>'),
|
(val) => val.split(cwdReal).join('<PROJECT_ROOT>'),
|
||||||
(val) => val.split(cwd).join('<PROJECT_ROOT>'),
|
(val) => val.split(cwd).join('<PROJECT_ROOT>'),
|
||||||
|
|
||||||
// Replace home directory with <TEMP_DIR>
|
// Replace temp directory with <TEMP_DIR>
|
||||||
(val) => val.split(tempDirReal).join('<TEMP_DIR>'),
|
(val) => val.split(tempDirReal).join('<TEMP_DIR>'),
|
||||||
(val) => val.split(tempDir).join('<TEMP_DIR>'),
|
(val) => val.split(tempDir).join('<TEMP_DIR>'),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
/**
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fs from 'fs-extra';
|
|
||||||
import os from 'os';
|
|
||||||
import path from 'path';
|
|
||||||
import shell from 'shelljs';
|
|
||||||
|
|
||||||
class Git {
|
|
||||||
constructor(private dir: string) {
|
|
||||||
const res = shell.exec('git init', {cwd: dir, silent: true});
|
|
||||||
if (res.code !== 0) {
|
|
||||||
throw new Error(`git init exited with code ${res.code}.
|
|
||||||
stderr: ${res.stderr}
|
|
||||||
stdout: ${res.stdout}`);
|
|
||||||
}
|
|
||||||
// Doesn't matter currently
|
|
||||||
shell.exec('git config user.email "test@jc-verse.com"', {
|
|
||||||
cwd: dir,
|
|
||||||
silent: true,
|
|
||||||
});
|
|
||||||
shell.exec('git config user.name "Test"', {cwd: dir, silent: true});
|
|
||||||
|
|
||||||
shell.exec('git commit --allow-empty -m "First commit"', {
|
|
||||||
cwd: dir,
|
|
||||||
silent: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
commit(msg: string, date: string, author: string): void {
|
|
||||||
const addRes = shell.exec('git add .', {cwd: this.dir, silent: true});
|
|
||||||
const commitRes = shell.exec(
|
|
||||||
`git commit -m "${msg}" --date "${date}T00:00:00Z" --author "${author}"`,
|
|
||||||
{
|
|
||||||
cwd: this.dir,
|
|
||||||
env: {GIT_COMMITTER_DATE: `${date}T00:00:00Z`},
|
|
||||||
silent: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (addRes.code !== 0) {
|
|
||||||
throw new Error(`git add exited with code ${addRes.code}.
|
|
||||||
stderr: ${addRes.stderr}
|
|
||||||
stdout: ${addRes.stdout}`);
|
|
||||||
}
|
|
||||||
if (commitRes.code !== 0) {
|
|
||||||
throw new Error(`git commit exited with code ${commitRes.code}.
|
|
||||||
stderr: ${commitRes.stderr}
|
|
||||||
stdout: ${commitRes.stdout}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function is sync so the same mock repo can be shared across tests
|
|
||||||
export function createTempRepo(): {repoDir: string; git: Git} {
|
|
||||||
const repoDir = fs.mkdtempSync(path.join(os.tmpdir(), 'git-test-repo'));
|
|
||||||
|
|
||||||
const git = new Git(repoDir);
|
|
||||||
|
|
||||||
return {repoDir, git};
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"useNx": false,
|
"useNx": false,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "create-docusaurus",
|
"name": "create-docusaurus",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"description": "Create Docusaurus apps easily.",
|
"description": "Create Docusaurus apps easily.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -22,10 +22,10 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/logger": "3.7.0",
|
"@docusaurus/logger": "3.9.2",
|
||||||
"@docusaurus/utils": "3.7.0",
|
"@docusaurus/utils": "3.9.2",
|
||||||
"commander": "^5.1.0",
|
"commander": "^5.1.0",
|
||||||
"execa": "5.1.1",
|
"execa": "^5.1.1",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"prompts": "^2.4.2",
|
"prompts": "^2.4.2",
|
||||||
|
|
@ -37,6 +37,6 @@
|
||||||
"@types/supports-color": "^8.1.1"
|
"@types/supports-color": "^8.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,10 @@ async function getSiteName(
|
||||||
return 'A website name is required.';
|
return 'A website name is required.';
|
||||||
}
|
}
|
||||||
const dest = path.resolve(rootDir, siteName);
|
const dest = path.resolve(rootDir, siteName);
|
||||||
if (await fs.pathExists(dest)) {
|
if (siteName === '.' && (await fs.readdir(dest)).length > 0) {
|
||||||
|
return logger.interpolate`Directory not empty at path=${dest}!`;
|
||||||
|
}
|
||||||
|
if (siteName !== '.' && (await fs.pathExists(dest))) {
|
||||||
return logger.interpolate`Directory already exists at path=${dest}!`;
|
return logger.interpolate`Directory already exists at path=${dest}!`;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ const config: Config = {
|
||||||
projectName: 'docusaurus', // Usually your repo name.
|
projectName: 'docusaurus', // Usually your repo name.
|
||||||
|
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
onBrokenMarkdownLinks: 'warn',
|
|
||||||
|
|
||||||
// Even if you don't use internationalization, you can use this field to set
|
// Even if you don't use internationalization, you can use this field to set
|
||||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||||
|
|
@ -72,6 +71,9 @@ const config: Config = {
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
// Replace with your project's social card
|
// Replace with your project's social card
|
||||||
image: 'img/docusaurus-social-card.jpg',
|
image: 'img/docusaurus-social-card.jpg',
|
||||||
|
colorMode: {
|
||||||
|
respectPrefersColorScheme: true,
|
||||||
|
},
|
||||||
navbar: {
|
navbar: {
|
||||||
title: 'My Site',
|
title: 'My Site',
|
||||||
logo: {
|
logo: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "docusaurus-2-classic-typescript-template",
|
"name": "docusaurus-2-classic-typescript-template",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docusaurus": "docusaurus",
|
"docusaurus": "docusaurus",
|
||||||
|
|
@ -15,8 +15,8 @@
|
||||||
"typecheck": "tsc"
|
"typecheck": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.7.0",
|
"@docusaurus/core": "3.9.2",
|
||||||
"@docusaurus/preset-classic": "3.7.0",
|
"@docusaurus/preset-classic": "3.9.2",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"prism-react-renderer": "^2.3.0",
|
"prism-react-renderer": "^2.3.0",
|
||||||
|
|
@ -24,9 +24,9 @@
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "3.7.0",
|
"@docusaurus/module-type-aliases": "3.9.2",
|
||||||
"@docusaurus/tsconfig": "3.7.0",
|
"@docusaurus/tsconfig": "3.9.2",
|
||||||
"@docusaurus/types": "3.7.0",
|
"@docusaurus/types": "3.9.2",
|
||||||
"typescript": "~5.6.2"
|
"typescript": "~5.6.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
|
|
@ -42,6 +42,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ const config = {
|
||||||
projectName: 'docusaurus', // Usually your repo name.
|
projectName: 'docusaurus', // Usually your repo name.
|
||||||
|
|
||||||
onBrokenLinks: 'throw',
|
onBrokenLinks: 'throw',
|
||||||
onBrokenMarkdownLinks: 'warn',
|
|
||||||
|
|
||||||
// Even if you don't use internationalization, you can use this field to set
|
// Even if you don't use internationalization, you can use this field to set
|
||||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||||
|
|
@ -80,6 +79,9 @@ const config = {
|
||||||
({
|
({
|
||||||
// Replace with your project's social card
|
// Replace with your project's social card
|
||||||
image: 'img/docusaurus-social-card.jpg',
|
image: 'img/docusaurus-social-card.jpg',
|
||||||
|
colorMode: {
|
||||||
|
respectPrefersColorScheme: true,
|
||||||
|
},
|
||||||
navbar: {
|
navbar: {
|
||||||
title: 'My Site',
|
title: 'My Site',
|
||||||
logo: {
|
logo: {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "docusaurus-2-classic-template",
|
"name": "docusaurus-2-classic-template",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"docusaurus": "docusaurus",
|
"docusaurus": "docusaurus",
|
||||||
|
|
@ -14,8 +14,8 @@
|
||||||
"write-heading-ids": "docusaurus write-heading-ids"
|
"write-heading-ids": "docusaurus write-heading-ids"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.7.0",
|
"@docusaurus/core": "3.9.2",
|
||||||
"@docusaurus/preset-classic": "3.7.0",
|
"@docusaurus/preset-classic": "3.9.2",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"prism-react-renderer": "^2.3.0",
|
"prism-react-renderer": "^2.3.0",
|
||||||
|
|
@ -23,8 +23,8 @@
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "3.7.0",
|
"@docusaurus/module-type-aliases": "3.9.2",
|
||||||
"@docusaurus/types": "3.7.0"
|
"@docusaurus/types": "3.9.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|
@ -39,6 +39,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new
|
||||||
|
|
||||||
### What you'll need
|
### What you'll need
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/en/download/) version 18.0 or above:
|
- [Node.js](https://nodejs.org/en/download/) version 20.0 or above:
|
||||||
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
|
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
|
||||||
|
|
||||||
## Generate a new site
|
## Generate a new site
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/babel",
|
"name": "@docusaurus/babel",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"description": "Docusaurus package for Babel-related utils.",
|
"description": "Docusaurus package for Babel-related utils.",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
|
|
@ -36,15 +36,14 @@
|
||||||
"@babel/preset-react": "^7.25.9",
|
"@babel/preset-react": "^7.25.9",
|
||||||
"@babel/preset-typescript": "^7.25.9",
|
"@babel/preset-typescript": "^7.25.9",
|
||||||
"@babel/runtime": "^7.25.9",
|
"@babel/runtime": "^7.25.9",
|
||||||
"@babel/runtime-corejs3": "^7.25.9",
|
|
||||||
"@babel/traverse": "^7.25.9",
|
"@babel/traverse": "^7.25.9",
|
||||||
"@docusaurus/logger": "3.7.0",
|
"@docusaurus/logger": "3.9.2",
|
||||||
"@docusaurus/utils": "3.7.0",
|
"@docusaurus/utils": "3.9.2",
|
||||||
"babel-plugin-dynamic-import-node": "^2.3.3",
|
"babel-plugin-dynamic-import-node": "^2.3.3",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/bundler",
|
"name": "@docusaurus/bundler",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"description": "Docusaurus util package to abstract the current bundler.",
|
"description": "Docusaurus util package to abstract the current bundler.",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
|
|
@ -19,24 +19,24 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.25.9",
|
"@babel/core": "^7.25.9",
|
||||||
"@docusaurus/babel": "3.7.0",
|
"@docusaurus/babel": "3.9.2",
|
||||||
"@docusaurus/cssnano-preset": "3.7.0",
|
"@docusaurus/cssnano-preset": "3.9.2",
|
||||||
"@docusaurus/logger": "3.7.0",
|
"@docusaurus/logger": "3.9.2",
|
||||||
"@docusaurus/types": "3.7.0",
|
"@docusaurus/types": "3.9.2",
|
||||||
"@docusaurus/utils": "3.7.0",
|
"@docusaurus/utils": "3.9.2",
|
||||||
"babel-loader": "^9.2.1",
|
"babel-loader": "^9.2.1",
|
||||||
"clean-css": "^5.3.2",
|
"clean-css": "^5.3.3",
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"css-loader": "^6.8.1",
|
"css-loader": "^6.11.0",
|
||||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||||
"cssnano": "^6.1.2",
|
"cssnano": "^6.1.2",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"html-minifier-terser": "^7.2.0",
|
"html-minifier-terser": "^7.2.0",
|
||||||
"mini-css-extract-plugin": "^2.9.1",
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
"null-loader": "^4.0.1",
|
"null-loader": "^4.0.1",
|
||||||
"postcss": "^8.4.26",
|
"postcss": "^8.5.4",
|
||||||
"postcss-loader": "^7.3.3",
|
"postcss-loader": "^7.3.4",
|
||||||
"postcss-preset-env": "^10.1.0",
|
"postcss-preset-env": "^10.2.1",
|
||||||
"terser-webpack-plugin": "^5.3.9",
|
"terser-webpack-plugin": "^5.3.9",
|
||||||
"tslib": "^2.6.0",
|
"tslib": "^2.6.0",
|
||||||
"url-loader": "^4.1.1",
|
"url-loader": "^4.1.1",
|
||||||
|
|
@ -55,6 +55,6 @@
|
||||||
"@total-typescript/shoehorn": "^0.1.2"
|
"@total-typescript/shoehorn": "^0.1.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -129,8 +129,8 @@ export async function registerBundlerTracing({
|
||||||
|
|
||||||
await Rspack.experiments.globalTrace.register(
|
await Rspack.experiments.globalTrace.register(
|
||||||
filter,
|
filter,
|
||||||
'chrome',
|
'perfetto',
|
||||||
'./rspack-tracing.json',
|
'./rspack-tracing.pftrace',
|
||||||
);
|
);
|
||||||
|
|
||||||
console.info(`Rspack tracing registered, filter=${filter}`);
|
console.info(`Rspack tracing registered, filter=${filter}`);
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ async function createSwcJsLoaderFactory(): Promise<
|
||||||
return ({isServer}) => {
|
return ({isServer}) => {
|
||||||
return {
|
return {
|
||||||
loader,
|
loader,
|
||||||
options: getOptions({isServer}),
|
options: getOptions({isServer, bundlerName: 'webpack'}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ async function createRspackSwcJsLoaderFactory(): Promise<
|
||||||
return ({isServer}) => {
|
return ({isServer}) => {
|
||||||
return {
|
return {
|
||||||
loader,
|
loader,
|
||||||
options: getOptions({isServer}),
|
options: getOptions({isServer, bundlerName: 'rspack'}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,10 @@ async function getRspackMinimizers({
|
||||||
}: MinimizersConfig): Promise<WebpackPluginInstance[]> {
|
}: MinimizersConfig): Promise<WebpackPluginInstance[]> {
|
||||||
const rspack = getCurrentBundlerAsRspack({currentBundler});
|
const rspack = getCurrentBundlerAsRspack({currentBundler});
|
||||||
const getBrowserslistQueries = await importGetBrowserslistQueries();
|
const getBrowserslistQueries = await importGetBrowserslistQueries();
|
||||||
const browserslistQueries = getBrowserslistQueries({isServer: false});
|
const browserslistQueries = getBrowserslistQueries({
|
||||||
|
isServer: false,
|
||||||
|
bundlerName: 'rspack',
|
||||||
|
});
|
||||||
const swcJsMinimizerOptions = await importSwcJsMinimizerOptions();
|
const swcJsMinimizerOptions = await importSwcJsMinimizerOptions();
|
||||||
return [
|
return [
|
||||||
// See https://rspack.dev/plugins/rspack/swc-js-minimizer-rspack-plugin
|
// See https://rspack.dev/plugins/rspack/swc-js-minimizer-rspack-plugin
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,10 @@ async function getSwcMinifier(): Promise<HtmlMinifier> {
|
||||||
// TODO maybe it's fine to only keep <!-- --> React comments?
|
// TODO maybe it's fine to only keep <!-- --> React comments?
|
||||||
preserveComments: [],
|
preserveComments: [],
|
||||||
|
|
||||||
|
// Keep <head> tag: important for social image crawlers like LinkedIn
|
||||||
|
// See https://github.com/swc-project/swc/issues/10994
|
||||||
|
tagOmission: 'keep-head-and-body',
|
||||||
|
|
||||||
// Sorting these attributes (class) can lead to React hydration errors
|
// Sorting these attributes (class) can lead to React hydration errors
|
||||||
sortSpaceSeparatedAttributeValues: false,
|
sortSpaceSeparatedAttributeValues: false,
|
||||||
sortAttributes: false,
|
sortAttributes: false,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/cssnano-preset",
|
"name": "@docusaurus/cssnano-preset",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"description": "Advanced cssnano preset for maximum optimization.",
|
"description": "Advanced cssnano preset for maximum optimization.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssnano-preset-advanced": "^6.1.2",
|
"cssnano-preset-advanced": "^6.1.2",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.5.4",
|
||||||
"postcss-sort-media-queries": "^5.2.0",
|
"postcss-sort-media-queries": "^5.2.0",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
|
|
@ -26,6 +26,6 @@
|
||||||
"to-vfile": "^6.1.0"
|
"to-vfile": "^6.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ const preset: typeof advancedBasePreset = function preset(opts) {
|
||||||
const advancedPreset = advancedBasePreset({
|
const advancedPreset = advancedBasePreset({
|
||||||
autoprefixer: {add: false},
|
autoprefixer: {add: false},
|
||||||
discardComments: {removeAll: true},
|
discardComments: {removeAll: true},
|
||||||
|
// See CodeBlock custom line number bug: https://github.com/facebook/docusaurus/pull/11487
|
||||||
|
/* cSpell:ignore Idents */
|
||||||
|
reduceIdents: {counter: false},
|
||||||
/* cSpell:ignore zindex */
|
/* cSpell:ignore zindex */
|
||||||
zindex: false,
|
zindex: false,
|
||||||
...opts,
|
...opts,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/faster",
|
"name": "@docusaurus/faster",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"description": "Docusaurus experimental package exposing new modern dependencies to make the build faster.",
|
"description": "Docusaurus experimental package exposing new modern dependencies to make the build faster.",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
|
|
@ -18,18 +18,19 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/types": "3.7.0",
|
"@docusaurus/types": "3.9.2",
|
||||||
"@rspack/core": "^1.3.10",
|
"@rspack/core": "^1.5.0",
|
||||||
"@swc/core": "^1.7.39",
|
"@swc/core": "^1.7.39",
|
||||||
"@swc/html": "^1.7.39",
|
"@swc/html": "^1.13.5",
|
||||||
"browserslist": "^4.24.2",
|
"browserslist": "^4.24.2",
|
||||||
"lightningcss": "^1.27.0",
|
"lightningcss": "^1.27.0",
|
||||||
|
"semver": "^7.5.4",
|
||||||
"swc-loader": "^0.2.6",
|
"swc-loader": "^0.2.6",
|
||||||
"tslib": "^2.6.0",
|
"tslib": "^2.6.0",
|
||||||
"webpack": "^5.95.0"
|
"webpack": "^5.95.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@docusaurus/types": "*"
|
"@docusaurus/types": "*"
|
||||||
|
|
|
||||||
|
|
@ -9,18 +9,22 @@ import Rspack from '@rspack/core';
|
||||||
import * as lightningcss from 'lightningcss';
|
import * as lightningcss from 'lightningcss';
|
||||||
import browserslist from 'browserslist';
|
import browserslist from 'browserslist';
|
||||||
import {minify as swcHtmlMinifier} from '@swc/html';
|
import {minify as swcHtmlMinifier} from '@swc/html';
|
||||||
|
import semver from 'semver';
|
||||||
import type {JsMinifyOptions, Options as SwcOptions} from '@swc/core';
|
import type {JsMinifyOptions, Options as SwcOptions} from '@swc/core';
|
||||||
|
import type {CurrentBundler} from '@docusaurus/types';
|
||||||
|
|
||||||
export const swcLoader = require.resolve('swc-loader');
|
export const swcLoader = require.resolve('swc-loader');
|
||||||
|
|
||||||
export const getSwcLoaderOptions = ({
|
export const getSwcLoaderOptions = ({
|
||||||
isServer,
|
isServer,
|
||||||
|
bundlerName,
|
||||||
}: {
|
}: {
|
||||||
isServer: boolean;
|
isServer: boolean;
|
||||||
|
bundlerName: CurrentBundler['name'];
|
||||||
}): SwcOptions => {
|
}): SwcOptions => {
|
||||||
return {
|
return {
|
||||||
env: {
|
env: {
|
||||||
targets: getBrowserslistQueries({isServer}),
|
targets: getBrowserslistQueries({isServer, bundlerName}),
|
||||||
},
|
},
|
||||||
jsc: {
|
jsc: {
|
||||||
parser: {
|
parser: {
|
||||||
|
|
@ -63,20 +67,53 @@ export function getSwcJsMinimizerOptions(): JsMinifyOptions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO this is not accurate
|
||||||
|
// for Rspack we should read from the built-in browserslist data
|
||||||
|
// see https://github.com/facebook/docusaurus/pull/11496
|
||||||
|
function getLastBrowserslistKnownNodeVersion(
|
||||||
|
bundlerName: CurrentBundler['name'],
|
||||||
|
): string {
|
||||||
|
if (bundlerName === 'rspack') {
|
||||||
|
// TODO hardcoded value until Rspack exposes its Browserslist data
|
||||||
|
// see https://github.com/facebook/docusaurus/pull/11496
|
||||||
|
return '22.0.0';
|
||||||
|
}
|
||||||
|
// browserslist('last 1 node versions')[0]!.replace('node ', '')
|
||||||
|
return browserslist.nodeVersions.at(-1)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMinVersion(v1: string, v2: string): string {
|
||||||
|
return semver.lt(v1, v2) ? v1 : v2;
|
||||||
|
}
|
||||||
|
|
||||||
// We need this because of Rspack built-in LightningCSS integration
|
// We need this because of Rspack built-in LightningCSS integration
|
||||||
// See https://github.com/orgs/browserslist/discussions/846
|
// See https://github.com/orgs/browserslist/discussions/846
|
||||||
export function getBrowserslistQueries({
|
export function getBrowserslistQueries({
|
||||||
isServer,
|
isServer,
|
||||||
|
bundlerName,
|
||||||
}: {
|
}: {
|
||||||
isServer: boolean;
|
isServer: boolean;
|
||||||
|
bundlerName: CurrentBundler['name'];
|
||||||
}): string[] {
|
}): string[] {
|
||||||
if (isServer) {
|
if (isServer) {
|
||||||
return [`node ${process.versions.node}`];
|
// Escape hatch env variable
|
||||||
|
if (process.env.DOCUSAURUS_SERVER_NODE_TARGET) {
|
||||||
|
return [`node ${process.env.DOCUSAURUS_SERVER_NODE_TARGET}`];
|
||||||
|
}
|
||||||
|
// For server builds, we want to use the current Node version as target
|
||||||
|
// But we can't pass a target that Browserslist doesn't know about yet
|
||||||
|
const nodeTarget = getMinVersion(
|
||||||
|
process.versions.node,
|
||||||
|
getLastBrowserslistKnownNodeVersion(bundlerName),
|
||||||
|
);
|
||||||
|
|
||||||
|
return [`node ${nodeTarget}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
const queries = browserslist.loadConfig({path: process.cwd()}) ?? [
|
const queries = browserslist.loadConfig({path: process.cwd()}) ?? [
|
||||||
...browserslist.defaults,
|
...browserslist.defaults,
|
||||||
];
|
];
|
||||||
|
|
||||||
return queries;
|
return queries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/logger",
|
"name": "@docusaurus/logger",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"description": "An encapsulated logger for semantically formatting console messages.",
|
"description": "An encapsulated logger for semantically formatting console messages.",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/supports-color": "^8.1.1"
|
"@types/supports-color": "^8.1.1"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/mdx-loader",
|
"name": "@docusaurus/mdx-loader",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"description": "Docusaurus Loader for MDX",
|
"description": "Docusaurus Loader for MDX",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
|
@ -18,9 +18,9 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/logger": "3.7.0",
|
"@docusaurus/logger": "3.9.2",
|
||||||
"@docusaurus/utils": "3.7.0",
|
"@docusaurus/utils": "3.9.2",
|
||||||
"@docusaurus/utils-validation": "3.7.0",
|
"@docusaurus/utils-validation": "3.9.2",
|
||||||
"@mdx-js/mdx": "^3.0.0",
|
"@mdx-js/mdx": "^3.0.0",
|
||||||
"@slorber/remark-comment": "^1.0.0",
|
"@slorber/remark-comment": "^1.0.0",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
"webpack": "^5.88.1"
|
"webpack": "^5.88.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/types": "3.7.0",
|
"@docusaurus/types": "3.9.2",
|
||||||
"@types/escape-html": "^1.0.2",
|
"@types/escape-html": "^1.0.2",
|
||||||
"@types/mdast": "^4.0.2",
|
"@types/mdast": "^4.0.2",
|
||||||
"@types/stringify-object": "^3.3.1",
|
"@types/stringify-object": "^3.3.1",
|
||||||
|
|
@ -62,6 +62,6 @@
|
||||||
"react-dom": "^18.0.0 || ^19.0.0"
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ import type {WebpackCompilerName} from '@docusaurus/utils';
|
||||||
import type {MDXFrontMatter} from './frontMatter';
|
import type {MDXFrontMatter} from './frontMatter';
|
||||||
import type {Options} from './options';
|
import type {Options} from './options';
|
||||||
import type {AdmonitionOptions} from './remark/admonitions';
|
import type {AdmonitionOptions} from './remark/admonitions';
|
||||||
|
import type {PluginOptions as ResolveMarkdownLinksOptions} from './remark/resolveMarkdownLinks';
|
||||||
|
import type {PluginOptions as TransformLinksOptions} from './remark/transformLinks';
|
||||||
|
import type {PluginOptions as TransformImageOptions} from './remark/transformImage';
|
||||||
import type {ProcessorOptions} from '@mdx-js/mdx';
|
import type {ProcessorOptions} from '@mdx-js/mdx';
|
||||||
|
|
||||||
// TODO as of April 2023, no way to import/re-export this ESM type easily :/
|
// TODO as of April 2023, no way to import/re-export this ESM type easily :/
|
||||||
|
|
@ -92,7 +95,7 @@ async function createProcessorFactory() {
|
||||||
headings,
|
headings,
|
||||||
{anchorsMaintainCase: options.markdownConfig.anchors.maintainCase},
|
{anchorsMaintainCase: options.markdownConfig.anchors.maintainCase},
|
||||||
],
|
],
|
||||||
emoji,
|
...(options.markdownConfig.emoji ? [emoji] : []),
|
||||||
toc,
|
toc,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -121,13 +124,19 @@ async function createProcessorFactory() {
|
||||||
{
|
{
|
||||||
staticDirs: options.staticDirs,
|
staticDirs: options.staticDirs,
|
||||||
siteDir: options.siteDir,
|
siteDir: options.siteDir,
|
||||||
},
|
onBrokenMarkdownImages:
|
||||||
|
options.markdownConfig.hooks.onBrokenMarkdownImages,
|
||||||
|
} satisfies TransformImageOptions,
|
||||||
],
|
],
|
||||||
// TODO merge this with transformLinks?
|
// TODO merge this with transformLinks?
|
||||||
options.resolveMarkdownLink
|
options.resolveMarkdownLink
|
||||||
? [
|
? [
|
||||||
resolveMarkdownLinks,
|
resolveMarkdownLinks,
|
||||||
{resolveMarkdownLink: options.resolveMarkdownLink},
|
{
|
||||||
|
resolveMarkdownLink: options.resolveMarkdownLink,
|
||||||
|
onBrokenMarkdownLinks:
|
||||||
|
options.markdownConfig.hooks.onBrokenMarkdownLinks,
|
||||||
|
} satisfies ResolveMarkdownLinksOptions,
|
||||||
]
|
]
|
||||||
: undefined,
|
: undefined,
|
||||||
[
|
[
|
||||||
|
|
@ -135,7 +144,9 @@ async function createProcessorFactory() {
|
||||||
{
|
{
|
||||||
staticDirs: options.staticDirs,
|
staticDirs: options.staticDirs,
|
||||||
siteDir: options.siteDir,
|
siteDir: options.siteDir,
|
||||||
},
|
onBrokenMarkdownLinks:
|
||||||
|
options.markdownConfig.hooks.onBrokenMarkdownLinks,
|
||||||
|
} satisfies TransformLinksOptions,
|
||||||
],
|
],
|
||||||
gfm,
|
gfm,
|
||||||
options.markdownConfig.mdx1Compat.comments ? comment : null,
|
options.markdownConfig.mdx1Compat.comments ? comment : null,
|
||||||
|
|
|
||||||
|
|
@ -5,22 +5,47 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {jest} from '@jest/globals';
|
||||||
|
import * as path from 'path';
|
||||||
import plugin from '..';
|
import plugin from '..';
|
||||||
import type {PluginOptions} from '../index';
|
import type {PluginOptions} from '../index';
|
||||||
|
|
||||||
async function process(content: string) {
|
const siteDir = __dirname;
|
||||||
const {remark} = await import('remark');
|
|
||||||
|
|
||||||
const options: PluginOptions = {
|
const DefaultTestOptions: PluginOptions = {
|
||||||
resolveMarkdownLink: ({linkPathname}) => `/RESOLVED---${linkPathname}`,
|
resolveMarkdownLink: ({linkPathname}) => `/RESOLVED---${linkPathname}`,
|
||||||
|
onBrokenMarkdownLinks: 'throw',
|
||||||
|
};
|
||||||
|
|
||||||
|
async function process(content: string, optionsInput?: Partial<PluginOptions>) {
|
||||||
|
const options = {
|
||||||
|
...DefaultTestOptions,
|
||||||
|
...optionsInput,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await remark().use(plugin, options).process(content);
|
const {remark} = await import('remark');
|
||||||
|
|
||||||
|
const result = await remark()
|
||||||
|
.use(plugin, options)
|
||||||
|
.process({
|
||||||
|
value: content,
|
||||||
|
path: path.posix.join(siteDir, 'docs', 'myFile.mdx'),
|
||||||
|
});
|
||||||
|
|
||||||
return result.value;
|
return result.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('resolveMarkdownLinks remark plugin', () => {
|
describe('resolveMarkdownLinks remark plugin', () => {
|
||||||
|
it('accepts non-md link', async () => {
|
||||||
|
/* language=markdown */
|
||||||
|
const content = `[link1](link1)`;
|
||||||
|
const result = await process(content);
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"[link1](link1)
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
it('resolves Markdown and MDX links', async () => {
|
it('resolves Markdown and MDX links', async () => {
|
||||||
/* language=markdown */
|
/* language=markdown */
|
||||||
const content = `[link1](link1.mdx)
|
const content = `[link1](link1.mdx)
|
||||||
|
|
@ -157,4 +182,212 @@ this is a code block
|
||||||
"
|
"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('onBrokenMarkdownLinks', () => {
|
||||||
|
const warnMock = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
beforeEach(() => {
|
||||||
|
warnMock.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function processResolutionErrors(
|
||||||
|
content: string,
|
||||||
|
onBrokenMarkdownLinks: PluginOptions['onBrokenMarkdownLinks'] = 'throw',
|
||||||
|
) {
|
||||||
|
return process(content, {
|
||||||
|
resolveMarkdownLink: () => null,
|
||||||
|
onBrokenMarkdownLinks,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('throws', () => {
|
||||||
|
it('for unresolvable mdx link', async () => {
|
||||||
|
/* language=markdown */
|
||||||
|
const content = `[link1](link1.mdx)`;
|
||||||
|
|
||||||
|
await expect(() => processResolutionErrors(content)).rejects
|
||||||
|
.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Markdown link with URL \`link1.mdx\` in source file "packages/docusaurus-mdx-loader/src/remark/resolveMarkdownLinks/__tests__/docs/myFile.mdx" (1:1) couldn't be resolved.
|
||||||
|
Make sure it references a local Markdown file that exists within the current plugin.
|
||||||
|
To ignore this error, use the \`siteConfig.markdown.hooks.onBrokenMarkdownLinks\` option, or apply the \`pathname://\` protocol to the broken link URLs."
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('for unresolvable md link', async () => {
|
||||||
|
/* language=markdown */
|
||||||
|
const content = `[link1](link1.md)`;
|
||||||
|
|
||||||
|
await expect(() => processResolutionErrors(content)).rejects
|
||||||
|
.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Markdown link with URL \`link1.md\` in source file "packages/docusaurus-mdx-loader/src/remark/resolveMarkdownLinks/__tests__/docs/myFile.mdx" (1:1) couldn't be resolved.
|
||||||
|
Make sure it references a local Markdown file that exists within the current plugin.
|
||||||
|
To ignore this error, use the \`siteConfig.markdown.hooks.onBrokenMarkdownLinks\` option, or apply the \`pathname://\` protocol to the broken link URLs."
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('warns', () => {
|
||||||
|
it('for unresolvable md and mdx link', async () => {
|
||||||
|
/* language=markdown */
|
||||||
|
const content = `
|
||||||
|
[link1](link1.mdx)
|
||||||
|
|
||||||
|
[link2](link2)
|
||||||
|
|
||||||
|
[link3](dir/link3.md)
|
||||||
|
|
||||||
|
[link 4](/link/4)
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await processResolutionErrors(content, 'warn');
|
||||||
|
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"[link1](link1.mdx)
|
||||||
|
|
||||||
|
[link2](link2)
|
||||||
|
|
||||||
|
[link3](dir/link3.md)
|
||||||
|
|
||||||
|
[link 4](/link/4)
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(warnMock).toHaveBeenCalledTimes(2);
|
||||||
|
expect(warnMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"[WARNING] Markdown link with URL \`link1.mdx\` in source file "packages/docusaurus-mdx-loader/src/remark/resolveMarkdownLinks/__tests__/docs/myFile.mdx" (2:1) couldn't be resolved.
|
||||||
|
Make sure it references a local Markdown file that exists within the current plugin.",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"[WARNING] Markdown link with URL \`dir/link3.md\` in source file "packages/docusaurus-mdx-loader/src/remark/resolveMarkdownLinks/__tests__/docs/myFile.mdx" (6:1) couldn't be resolved.
|
||||||
|
Make sure it references a local Markdown file that exists within the current plugin.",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('for unresolvable md and mdx link - with recovery', async () => {
|
||||||
|
/* language=markdown */
|
||||||
|
const content = `
|
||||||
|
[link1](link1.mdx)
|
||||||
|
|
||||||
|
[link2](link2)
|
||||||
|
|
||||||
|
[link3](dir/link3.md?query#hash)
|
||||||
|
|
||||||
|
[link 4](/link/4)
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await processResolutionErrors(content, (params) => {
|
||||||
|
console.warn(`onBrokenMarkdownLinks called with`, params);
|
||||||
|
// We can alter the AST Node
|
||||||
|
params.node.title = 'fixed link title';
|
||||||
|
params.node.url = 'ignored, less important than returned value';
|
||||||
|
// Or return a new URL
|
||||||
|
return `/recovered-link`;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"[link1](/recovered-link "fixed link title")
|
||||||
|
|
||||||
|
[link2](link2)
|
||||||
|
|
||||||
|
[link3](/recovered-link "fixed link title")
|
||||||
|
|
||||||
|
[link 4](/link/4)
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(warnMock).toHaveBeenCalledTimes(2);
|
||||||
|
expect(warnMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"onBrokenMarkdownLinks called with",
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 7,
|
||||||
|
"line": 2,
|
||||||
|
"offset": 7,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 2,
|
||||||
|
"line": 2,
|
||||||
|
"offset": 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "text",
|
||||||
|
"value": "link1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 19,
|
||||||
|
"line": 2,
|
||||||
|
"offset": 19,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 2,
|
||||||
|
"offset": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": "fixed link title",
|
||||||
|
"type": "link",
|
||||||
|
"url": "/recovered-link",
|
||||||
|
},
|
||||||
|
"sourceFilePath": "packages/docusaurus-mdx-loader/src/remark/resolveMarkdownLinks/__tests__/docs/myFile.mdx",
|
||||||
|
"url": "link1.mdx",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"onBrokenMarkdownLinks called with",
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 7,
|
||||||
|
"line": 6,
|
||||||
|
"offset": 43,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 2,
|
||||||
|
"line": 6,
|
||||||
|
"offset": 38,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "text",
|
||||||
|
"value": "link3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 33,
|
||||||
|
"line": 6,
|
||||||
|
"offset": 69,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 6,
|
||||||
|
"offset": 37,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": "fixed link title",
|
||||||
|
"type": "link",
|
||||||
|
"url": "/recovered-link",
|
||||||
|
},
|
||||||
|
"sourceFilePath": "packages/docusaurus-mdx-loader/src/remark/resolveMarkdownLinks/__tests__/docs/myFile.mdx",
|
||||||
|
"url": "dir/link3.md?query#hash",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,18 @@
|
||||||
import {
|
import {
|
||||||
parseLocalURLPath,
|
parseLocalURLPath,
|
||||||
serializeURLPath,
|
serializeURLPath,
|
||||||
|
toMessageRelativeFilePath,
|
||||||
type URLPath,
|
type URLPath,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
|
import logger from '@docusaurus/logger';
|
||||||
|
|
||||||
|
import {formatNodePositionExtraMessage} from '../utils';
|
||||||
import type {Plugin, Transformer} from 'unified';
|
import type {Plugin, Transformer} from 'unified';
|
||||||
import type {Definition, Link, Root} from 'mdast';
|
import type {Definition, Link, Root} from 'mdast';
|
||||||
|
import type {
|
||||||
|
MarkdownConfig,
|
||||||
|
OnBrokenMarkdownLinksFunction,
|
||||||
|
} from '@docusaurus/types';
|
||||||
|
|
||||||
type ResolveMarkdownLinkParams = {
|
type ResolveMarkdownLinkParams = {
|
||||||
/**
|
/**
|
||||||
|
|
@ -32,6 +39,33 @@ export type ResolveMarkdownLink = (
|
||||||
|
|
||||||
export interface PluginOptions {
|
export interface PluginOptions {
|
||||||
resolveMarkdownLink: ResolveMarkdownLink;
|
resolveMarkdownLink: ResolveMarkdownLink;
|
||||||
|
onBrokenMarkdownLinks: MarkdownConfig['hooks']['onBrokenMarkdownLinks'];
|
||||||
|
}
|
||||||
|
|
||||||
|
function asFunction(
|
||||||
|
onBrokenMarkdownLinks: PluginOptions['onBrokenMarkdownLinks'],
|
||||||
|
): OnBrokenMarkdownLinksFunction {
|
||||||
|
if (typeof onBrokenMarkdownLinks === 'string') {
|
||||||
|
const extraHelp =
|
||||||
|
onBrokenMarkdownLinks === 'throw'
|
||||||
|
? logger.interpolate`\nTo ignore this error, use the code=${'siteConfig.markdown.hooks.onBrokenMarkdownLinks'} option, or apply the code=${'pathname://'} protocol to the broken link URLs.`
|
||||||
|
: '';
|
||||||
|
return ({sourceFilePath, url: linkUrl, node}) => {
|
||||||
|
const relativePath = toMessageRelativeFilePath(sourceFilePath);
|
||||||
|
logger.report(
|
||||||
|
onBrokenMarkdownLinks,
|
||||||
|
)`Markdown link with URL code=${linkUrl} in source file path=${relativePath}${formatNodePositionExtraMessage(
|
||||||
|
node,
|
||||||
|
)} couldn't be resolved.
|
||||||
|
Make sure it references a local Markdown file that exists within the current plugin.${extraHelp}`;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return (params) =>
|
||||||
|
onBrokenMarkdownLinks({
|
||||||
|
...params,
|
||||||
|
sourceFilePath: toMessageRelativeFilePath(params.sourceFilePath),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const HAS_MARKDOWN_EXTENSION = /\.mdx?$/i;
|
const HAS_MARKDOWN_EXTENSION = /\.mdx?$/i;
|
||||||
|
|
@ -57,10 +91,15 @@ function parseMarkdownLinkURLPath(link: string): URLPath | null {
|
||||||
* This is exposed as "data.contentTitle" to the processed vfile
|
* This is exposed as "data.contentTitle" to the processed vfile
|
||||||
* Also gives the ability to strip that content title (used for the blog plugin)
|
* Also gives the ability to strip that content title (used for the blog plugin)
|
||||||
*/
|
*/
|
||||||
|
// TODO merge this plugin with "transformLinks"
|
||||||
|
// in general we'd want to avoid traversing multiple times the same AST
|
||||||
const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
||||||
options,
|
options,
|
||||||
): Transformer<Root> {
|
): Transformer<Root> {
|
||||||
const {resolveMarkdownLink} = options;
|
const {resolveMarkdownLink} = options;
|
||||||
|
|
||||||
|
const onBrokenMarkdownLinks = asFunction(options.onBrokenMarkdownLinks);
|
||||||
|
|
||||||
return async (root, file) => {
|
return async (root, file) => {
|
||||||
const {visit} = await import('unist-util-visit');
|
const {visit} = await import('unist-util-visit');
|
||||||
|
|
||||||
|
|
@ -71,18 +110,26 @@ const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sourceFilePath = file.path;
|
||||||
|
|
||||||
const permalink = resolveMarkdownLink({
|
const permalink = resolveMarkdownLink({
|
||||||
sourceFilePath: file.path,
|
sourceFilePath,
|
||||||
linkPathname: linkURLPath.pathname,
|
linkPathname: linkURLPath.pathname,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (permalink) {
|
if (permalink) {
|
||||||
// This reapplies the link ?qs#hash part to the resolved pathname
|
// This reapplies the link ?qs#hash part to the resolved pathname
|
||||||
const resolvedUrl = serializeURLPath({
|
link.url = serializeURLPath({
|
||||||
...linkURLPath,
|
...linkURLPath,
|
||||||
pathname: permalink,
|
pathname: permalink,
|
||||||
});
|
});
|
||||||
link.url = resolvedUrl;
|
} else {
|
||||||
|
link.url =
|
||||||
|
onBrokenMarkdownLinks({
|
||||||
|
url: link.url,
|
||||||
|
sourceFilePath,
|
||||||
|
node: link,
|
||||||
|
}) ?? link.url;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||

|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||

|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||

|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
![img]()
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||

|
|
||||||
|
|
@ -1,16 +1,10 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`transformImage plugin does not choke on invalid image 1`] = `
|
exports[`transformImage plugin does not choke on invalid image 1`] = `
|
||||||
"<img alt="invalid image" src={require("!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./static/invalid.png").default} />
|
"<img alt="invalid image" src={require("!<PROJECT_ROOT>/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js!./../static/invalid.png").default} />
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`transformImage plugin fail if image does not exist 1`] = `"Image packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static/img/doesNotExist.png or packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/static2/img/doesNotExist.png used in packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/fail.md not found."`;
|
|
||||||
|
|
||||||
exports[`transformImage plugin fail if image relative path does not exist 1`] = `"Image packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/notFound.png used in packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/fail2.md not found."`;
|
|
||||||
|
|
||||||
exports[`transformImage plugin fail if image url is absent 1`] = `"Markdown image URL is mandatory in "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/noUrl.md" file"`;
|
|
||||||
|
|
||||||
exports[`transformImage plugin pathname protocol 1`] = `
|
exports[`transformImage plugin pathname protocol 1`] = `
|
||||||
"
|
"
|
||||||
"
|
"
|
||||||
|
|
|
||||||
|
|
@ -6,65 +6,361 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {jest} from '@jest/globals';
|
import {jest} from '@jest/globals';
|
||||||
import path from 'path';
|
import * as path from 'path';
|
||||||
import vfile from 'to-vfile';
|
import vfile from 'to-vfile';
|
||||||
import plugin, {type PluginOptions} from '../index';
|
import plugin, {type PluginOptions} from '../index';
|
||||||
|
|
||||||
const processFixture = async (
|
const siteDir = path.join(__dirname, '__fixtures__');
|
||||||
name: string,
|
|
||||||
options: Partial<PluginOptions>,
|
|
||||||
) => {
|
|
||||||
const {remark} = await import('remark');
|
|
||||||
const {default: mdx} = await import('remark-mdx');
|
|
||||||
const filePath = path.join(__dirname, `__fixtures__/${name}.md`);
|
|
||||||
const file = await vfile.read(filePath);
|
|
||||||
|
|
||||||
const result = await remark()
|
|
||||||
.use(mdx)
|
|
||||||
.use(plugin, {siteDir: __dirname, staticDirs: [], ...options})
|
|
||||||
.process(file);
|
|
||||||
|
|
||||||
return result.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const staticDirs = [
|
const staticDirs = [
|
||||||
path.join(__dirname, '__fixtures__/static'),
|
path.join(__dirname, '__fixtures__/static'),
|
||||||
path.join(__dirname, '__fixtures__/static2'),
|
path.join(__dirname, '__fixtures__/static2'),
|
||||||
];
|
];
|
||||||
|
|
||||||
const siteDir = path.join(__dirname, '__fixtures__');
|
const getProcessor = async (options?: Partial<PluginOptions>) => {
|
||||||
|
const {remark} = await import('remark');
|
||||||
|
const {default: mdx} = await import('remark-mdx');
|
||||||
|
return remark()
|
||||||
|
.use(mdx)
|
||||||
|
.use(plugin, {
|
||||||
|
siteDir,
|
||||||
|
staticDirs,
|
||||||
|
onBrokenMarkdownImages: 'throw',
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const processFixture = async (
|
||||||
|
name: string,
|
||||||
|
options?: Partial<PluginOptions>,
|
||||||
|
) => {
|
||||||
|
const filePath = path.join(__dirname, `__fixtures__/${name}.md`);
|
||||||
|
const file = await vfile.read(filePath);
|
||||||
|
const processor = await getProcessor(options);
|
||||||
|
const result = await processor.process(file);
|
||||||
|
return result.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const processContent = async (
|
||||||
|
content: string,
|
||||||
|
options?: Partial<PluginOptions>,
|
||||||
|
) => {
|
||||||
|
const processor = await getProcessor(options);
|
||||||
|
const result = await processor.process({
|
||||||
|
value: content,
|
||||||
|
path: path.posix.join(siteDir, 'docs', 'myFile.mdx'),
|
||||||
|
});
|
||||||
|
return result.value.toString();
|
||||||
|
};
|
||||||
|
|
||||||
describe('transformImage plugin', () => {
|
describe('transformImage plugin', () => {
|
||||||
it('fail if image does not exist', async () => {
|
|
||||||
await expect(
|
|
||||||
processFixture('fail', {staticDirs}),
|
|
||||||
).rejects.toThrowErrorMatchingSnapshot();
|
|
||||||
});
|
|
||||||
it('fail if image relative path does not exist', async () => {
|
|
||||||
await expect(
|
|
||||||
processFixture('fail2', {staticDirs}),
|
|
||||||
).rejects.toThrowErrorMatchingSnapshot();
|
|
||||||
});
|
|
||||||
it('fail if image url is absent', async () => {
|
|
||||||
await expect(
|
|
||||||
processFixture('noUrl', {staticDirs}),
|
|
||||||
).rejects.toThrowErrorMatchingSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('transform md images to <img />', async () => {
|
it('transform md images to <img />', async () => {
|
||||||
const result = await processFixture('img', {staticDirs, siteDir});
|
// TODO split that large fixture into many smaller test cases?
|
||||||
|
const result = await processFixture('img');
|
||||||
expect(result).toMatchSnapshot();
|
expect(result).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pathname protocol', async () => {
|
it('pathname protocol', async () => {
|
||||||
const result = await processFixture('pathname', {staticDirs});
|
const result = await processContent(
|
||||||
|
``,
|
||||||
|
);
|
||||||
expect(result).toMatchSnapshot();
|
expect(result).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not choke on invalid image', async () => {
|
it('does not choke on invalid image', async () => {
|
||||||
const errorMock = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
const errorMock = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
const result = await processFixture('invalid-img', {staticDirs});
|
const result = await processContent(``);
|
||||||
expect(result).toMatchSnapshot();
|
expect(result).toMatchSnapshot();
|
||||||
expect(errorMock).toHaveBeenCalledTimes(1);
|
expect(errorMock).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('onBrokenMarkdownImages', () => {
|
||||||
|
const fixtures = {
|
||||||
|
doesNotExistAbsolute: ``,
|
||||||
|
doesNotExistRelative: ``,
|
||||||
|
doesNotExistSiteAlias: ``,
|
||||||
|
urlEmpty: `![img]()`,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('throws', () => {
|
||||||
|
it('if image absolute path does not exist', async () => {
|
||||||
|
await expect(processContent(fixtures.doesNotExistAbsolute)).rejects
|
||||||
|
.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Markdown image with URL \`/img/doesNotExist.png\` in source file "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx" (1:1) couldn't be resolved to an existing local image file.
|
||||||
|
To ignore this error, use the \`siteConfig.markdown.hooks.onBrokenMarkdownImages\` option, or apply the \`pathname://\` protocol to the broken image URLs."
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if image relative path does not exist', async () => {
|
||||||
|
await expect(processContent(fixtures.doesNotExistRelative)).rejects
|
||||||
|
.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Markdown image with URL \`./doesNotExist.png\` in source file "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx" (1:1) couldn't be resolved to an existing local image file.
|
||||||
|
To ignore this error, use the \`siteConfig.markdown.hooks.onBrokenMarkdownImages\` option, or apply the \`pathname://\` protocol to the broken image URLs."
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if image @site path does not exist', async () => {
|
||||||
|
await expect(processContent(fixtures.doesNotExistSiteAlias)).rejects
|
||||||
|
.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Markdown image with URL \`@site/doesNotExist.png\` in source file "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx" (1:1) couldn't be resolved to an existing local image file.
|
||||||
|
To ignore this error, use the \`siteConfig.markdown.hooks.onBrokenMarkdownImages\` option, or apply the \`pathname://\` protocol to the broken image URLs."
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if image url empty', async () => {
|
||||||
|
await expect(processContent(fixtures.urlEmpty)).rejects
|
||||||
|
.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Markdown image with empty URL found in source file "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx" (1:1).
|
||||||
|
To ignore this error, use the \`siteConfig.markdown.hooks.onBrokenMarkdownImages\` option, or apply the \`pathname://\` protocol to the broken image URLs."
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('warns', () => {
|
||||||
|
function processWarn(content: string) {
|
||||||
|
return processContent(content, {onBrokenMarkdownImages: 'warn'});
|
||||||
|
}
|
||||||
|
|
||||||
|
const warnMock = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
beforeEach(() => {
|
||||||
|
warnMock.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if image absolute path does not exist', async () => {
|
||||||
|
const result = await processWarn(fixtures.doesNotExistAbsolute);
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(warnMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(warnMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"[WARNING] Markdown image with URL \`/img/doesNotExist.png\` in source file "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx" (1:1) couldn't be resolved to an existing local image file.",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if image relative path does not exist', async () => {
|
||||||
|
const result = await processWarn(fixtures.doesNotExistRelative);
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(warnMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(warnMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"[WARNING] Markdown image with URL \`./doesNotExist.png\` in source file "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx" (1:1) couldn't be resolved to an existing local image file.",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if image @site path does not exist', async () => {
|
||||||
|
const result = await processWarn(fixtures.doesNotExistSiteAlias);
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(warnMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(warnMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"[WARNING] Markdown image with URL \`@site/doesNotExist.png\` in source file "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx" (1:1) couldn't be resolved to an existing local image file.",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if image url empty', async () => {
|
||||||
|
const result = await processWarn(fixtures.urlEmpty);
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"![img]()
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(warnMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(warnMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"[WARNING] Markdown image with empty URL found in source file "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx" (1:1).",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('function form', () => {
|
||||||
|
function processWarn(content: string) {
|
||||||
|
return processContent(content, {
|
||||||
|
onBrokenMarkdownImages: (params) => {
|
||||||
|
console.log('onBrokenMarkdownImages called for ', params);
|
||||||
|
// We can alter the AST Node
|
||||||
|
params.node.alt = 'new 404 alt';
|
||||||
|
params.node.url = 'ignored, less important than returned value';
|
||||||
|
// Or return a new URL
|
||||||
|
return '/404.png';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const logMock = jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
beforeEach(() => {
|
||||||
|
logMock.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if image absolute path does not exist', async () => {
|
||||||
|
const result = await processWarn(fixtures.doesNotExistAbsolute);
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(logMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(logMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"onBrokenMarkdownImages called for ",
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"alt": "new 404 alt",
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 30,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 29,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": null,
|
||||||
|
"type": "image",
|
||||||
|
"url": "/404.png",
|
||||||
|
},
|
||||||
|
"sourceFilePath": "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx",
|
||||||
|
"url": "/img/doesNotExist.png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if image relative path does not exist', async () => {
|
||||||
|
const result = await processWarn(fixtures.doesNotExistRelative);
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(logMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(logMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"onBrokenMarkdownImages called for ",
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"alt": "new 404 alt",
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 27,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 26,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": null,
|
||||||
|
"type": "image",
|
||||||
|
"url": "/404.png",
|
||||||
|
},
|
||||||
|
"sourceFilePath": "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx",
|
||||||
|
"url": "./doesNotExist.png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if image @site path does not exist', async () => {
|
||||||
|
const result = await processWarn(fixtures.doesNotExistSiteAlias);
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(logMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(logMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"onBrokenMarkdownImages called for ",
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"alt": "new 404 alt",
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 31,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 30,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": null,
|
||||||
|
"type": "image",
|
||||||
|
"url": "/404.png",
|
||||||
|
},
|
||||||
|
"sourceFilePath": "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx",
|
||||||
|
"url": "@site/doesNotExist.png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if image url empty', async () => {
|
||||||
|
const result = await processWarn(fixtures.urlEmpty);
|
||||||
|
expect(result).toMatchInlineSnapshot(`
|
||||||
|
"
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
expect(logMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(logMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"onBrokenMarkdownImages called for ",
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"alt": "new 404 alt",
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 9,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 8,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": null,
|
||||||
|
"type": "image",
|
||||||
|
"url": "/404.png",
|
||||||
|
},
|
||||||
|
"sourceFilePath": "packages/docusaurus-mdx-loader/src/remark/transformImage/__tests__/__fixtures__/docs/myFile.mdx",
|
||||||
|
"url": "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import url from 'url';
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import {
|
import {
|
||||||
toMessageRelativeFilePath,
|
toMessageRelativeFilePath,
|
||||||
|
|
@ -15,26 +14,72 @@ import {
|
||||||
findAsyncSequential,
|
findAsyncSequential,
|
||||||
getFileLoaderUtils,
|
getFileLoaderUtils,
|
||||||
parseURLOrPath,
|
parseURLOrPath,
|
||||||
|
parseLocalURLPath,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import escapeHtml from 'escape-html';
|
import escapeHtml from 'escape-html';
|
||||||
import {imageSizeFromFile} from 'image-size/fromFile';
|
import {imageSizeFromFile} from 'image-size/fromFile';
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
import {assetRequireAttributeValue, transformNode} from '../utils';
|
import {
|
||||||
|
assetRequireAttributeValue,
|
||||||
|
formatNodePositionExtraMessage,
|
||||||
|
transformNode,
|
||||||
|
} from '../utils';
|
||||||
import type {Plugin, Transformer} from 'unified';
|
import type {Plugin, Transformer} from 'unified';
|
||||||
import type {MdxJsxTextElement} from 'mdast-util-mdx';
|
import type {MdxJsxTextElement} from 'mdast-util-mdx';
|
||||||
import type {Image, Root} from 'mdast';
|
import type {Image, Root} from 'mdast';
|
||||||
import type {Parent} from 'unist';
|
import type {Parent} from 'unist';
|
||||||
|
import type {
|
||||||
|
MarkdownConfig,
|
||||||
|
OnBrokenMarkdownImagesFunction,
|
||||||
|
} from '@docusaurus/types';
|
||||||
|
|
||||||
type PluginOptions = {
|
export type PluginOptions = {
|
||||||
staticDirs: string[];
|
staticDirs: string[];
|
||||||
siteDir: string;
|
siteDir: string;
|
||||||
|
onBrokenMarkdownImages: MarkdownConfig['hooks']['onBrokenMarkdownImages'];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Context = PluginOptions & {
|
type Context = {
|
||||||
|
staticDirs: PluginOptions['staticDirs'];
|
||||||
|
siteDir: PluginOptions['siteDir'];
|
||||||
|
onBrokenMarkdownImages: OnBrokenMarkdownImagesFunction;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
inlineMarkdownImageFileLoader: string;
|
inlineMarkdownImageFileLoader: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function asFunction(
|
||||||
|
onBrokenMarkdownImages: PluginOptions['onBrokenMarkdownImages'],
|
||||||
|
): OnBrokenMarkdownImagesFunction {
|
||||||
|
if (typeof onBrokenMarkdownImages === 'string') {
|
||||||
|
const extraHelp =
|
||||||
|
onBrokenMarkdownImages === 'throw'
|
||||||
|
? logger.interpolate`\nTo ignore this error, use the code=${'siteConfig.markdown.hooks.onBrokenMarkdownImages'} option, or apply the code=${'pathname://'} protocol to the broken image URLs.`
|
||||||
|
: '';
|
||||||
|
return ({sourceFilePath, url: imageUrl, node}) => {
|
||||||
|
const relativePath = toMessageRelativeFilePath(sourceFilePath);
|
||||||
|
if (imageUrl) {
|
||||||
|
logger.report(
|
||||||
|
onBrokenMarkdownImages,
|
||||||
|
)`Markdown image with URL code=${imageUrl} in source file path=${relativePath}${formatNodePositionExtraMessage(
|
||||||
|
node,
|
||||||
|
)} couldn't be resolved to an existing local image file.${extraHelp}`;
|
||||||
|
} else {
|
||||||
|
logger.report(
|
||||||
|
onBrokenMarkdownImages,
|
||||||
|
)`Markdown image with empty URL found in source file path=${relativePath}${formatNodePositionExtraMessage(
|
||||||
|
node,
|
||||||
|
)}.${extraHelp}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return (params) =>
|
||||||
|
onBrokenMarkdownImages({
|
||||||
|
...params,
|
||||||
|
sourceFilePath: toMessageRelativeFilePath(params.sourceFilePath),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Target = [node: Image, index: number, parent: Parent];
|
type Target = [node: Image, index: number, parent: Parent];
|
||||||
|
|
||||||
async function toImageRequireNode(
|
async function toImageRequireNode(
|
||||||
|
|
@ -51,7 +96,7 @@ async function toImageRequireNode(
|
||||||
);
|
);
|
||||||
relativeImagePath = `./${relativeImagePath}`;
|
relativeImagePath = `./${relativeImagePath}`;
|
||||||
|
|
||||||
const parsedUrl = parseURLOrPath(node.url, 'https://example.com');
|
const parsedUrl = parseURLOrPath(node.url);
|
||||||
const hash = parsedUrl.hash ?? '';
|
const hash = parsedUrl.hash ?? '';
|
||||||
const search = parsedUrl.search ?? '';
|
const search = parsedUrl.search ?? '';
|
||||||
const requireString = `${context.inlineMarkdownImageFileLoader}${
|
const requireString = `${context.inlineMarkdownImageFileLoader}${
|
||||||
|
|
@ -113,64 +158,60 @@ ${(err as Error).message}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureImageFileExist(imagePath: string, sourceFilePath: string) {
|
async function getLocalImageAbsolutePath(
|
||||||
const imageExists = await fs.pathExists(imagePath);
|
originalImagePath: string,
|
||||||
if (!imageExists) {
|
|
||||||
throw new Error(
|
|
||||||
`Image ${toMessageRelativeFilePath(
|
|
||||||
imagePath,
|
|
||||||
)} used in ${toMessageRelativeFilePath(sourceFilePath)} not found.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getImageAbsolutePath(
|
|
||||||
imagePath: string,
|
|
||||||
{siteDir, filePath, staticDirs}: Context,
|
{siteDir, filePath, staticDirs}: Context,
|
||||||
) {
|
) {
|
||||||
if (imagePath.startsWith('@site/')) {
|
if (originalImagePath.startsWith('@site/')) {
|
||||||
const imageFilePath = path.join(siteDir, imagePath.replace('@site/', ''));
|
const imageFilePath = path.join(
|
||||||
await ensureImageFileExist(imageFilePath, filePath);
|
siteDir,
|
||||||
|
originalImagePath.replace('@site/', ''),
|
||||||
|
);
|
||||||
|
if (!(await fs.pathExists(imageFilePath))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return imageFilePath;
|
return imageFilePath;
|
||||||
} else if (path.isAbsolute(imagePath)) {
|
} else if (path.isAbsolute(originalImagePath)) {
|
||||||
// Absolute paths are expected to exist in the static folder.
|
// Absolute paths are expected to exist in the static folder.
|
||||||
const possiblePaths = staticDirs.map((dir) => path.join(dir, imagePath));
|
const possiblePaths = staticDirs.map((dir) =>
|
||||||
|
path.join(dir, originalImagePath),
|
||||||
|
);
|
||||||
const imageFilePath = await findAsyncSequential(
|
const imageFilePath = await findAsyncSequential(
|
||||||
possiblePaths,
|
possiblePaths,
|
||||||
fs.pathExists,
|
fs.pathExists,
|
||||||
);
|
);
|
||||||
if (!imageFilePath) {
|
if (!imageFilePath) {
|
||||||
throw new Error(
|
return null;
|
||||||
`Image ${possiblePaths
|
}
|
||||||
.map((p) => toMessageRelativeFilePath(p))
|
return imageFilePath;
|
||||||
.join(' or ')} used in ${toMessageRelativeFilePath(
|
} else {
|
||||||
filePath,
|
// relative paths are resolved against the source file's folder
|
||||||
)} not found.`,
|
const imageFilePath = path.join(path.dirname(filePath), originalImagePath);
|
||||||
);
|
if (!(await fs.pathExists(imageFilePath))) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return imageFilePath;
|
return imageFilePath;
|
||||||
}
|
}
|
||||||
// relative paths are resolved against the source file's folder
|
|
||||||
const imageFilePath = path.join(path.dirname(filePath), imagePath);
|
|
||||||
await ensureImageFileExist(imageFilePath, filePath);
|
|
||||||
return imageFilePath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processImageNode(target: Target, context: Context) {
|
async function processImageNode(target: Target, context: Context) {
|
||||||
const [node] = target;
|
const [node] = target;
|
||||||
|
|
||||||
if (!node.url) {
|
if (!node.url) {
|
||||||
throw new Error(
|
node.url =
|
||||||
`Markdown image URL is mandatory in "${toMessageRelativeFilePath(
|
context.onBrokenMarkdownImages({
|
||||||
context.filePath,
|
url: node.url,
|
||||||
)}" file`,
|
sourceFilePath: context.filePath,
|
||||||
);
|
node,
|
||||||
|
}) ?? node.url;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedUrl = url.parse(node.url);
|
const localUrlPath = parseLocalURLPath(node.url);
|
||||||
if (parsedUrl.protocol || !parsedUrl.pathname) {
|
if (!localUrlPath) {
|
||||||
// pathname:// is an escape hatch, in case user does not want her images to
|
// pathname:// is an escape hatch, in case the user does not want images to
|
||||||
// be converted to require calls going through webpack loader
|
// be converted to require calls going through webpack loader
|
||||||
if (parsedUrl.protocol === 'pathname:') {
|
if (parseURLOrPath(node.url).protocol === 'pathname:') {
|
||||||
node.url = node.url.replace('pathname://', '');
|
node.url = node.url.replace('pathname://', '');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -179,17 +220,31 @@ async function processImageNode(target: Target, context: Context) {
|
||||||
// We decode it first because Node Url.pathname is always encoded
|
// We decode it first because Node Url.pathname is always encoded
|
||||||
// while the image file-system path are not.
|
// while the image file-system path are not.
|
||||||
// See https://github.com/facebook/docusaurus/discussions/10720
|
// See https://github.com/facebook/docusaurus/discussions/10720
|
||||||
const decodedPathname = decodeURIComponent(parsedUrl.pathname);
|
const decodedPathname = decodeURIComponent(localUrlPath.pathname);
|
||||||
|
|
||||||
// We try to convert image urls without protocol to images with require calls
|
// We try to convert image urls without protocol to images with require calls
|
||||||
// going through webpack ensures that image assets exist at build time
|
// going through webpack ensures that image assets exist at build time
|
||||||
const imagePath = await getImageAbsolutePath(decodedPathname, context);
|
const localImagePath = await getLocalImageAbsolutePath(
|
||||||
await toImageRequireNode(target, imagePath, context);
|
decodedPathname,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
if (localImagePath === null) {
|
||||||
|
node.url =
|
||||||
|
context.onBrokenMarkdownImages({
|
||||||
|
url: node.url,
|
||||||
|
sourceFilePath: context.filePath,
|
||||||
|
node,
|
||||||
|
}) ?? node.url;
|
||||||
|
} else {
|
||||||
|
await toImageRequireNode(target, localImagePath, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
||||||
options,
|
options,
|
||||||
): Transformer<Root> {
|
): Transformer<Root> {
|
||||||
|
const onBrokenMarkdownImages = asFunction(options.onBrokenMarkdownImages);
|
||||||
|
|
||||||
return async (root, vfile) => {
|
return async (root, vfile) => {
|
||||||
const {visit} = await import('unist-util-visit');
|
const {visit} = await import('unist-util-visit');
|
||||||
|
|
||||||
|
|
@ -201,6 +256,7 @@ const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
||||||
filePath: vfile.path!,
|
filePath: vfile.path!,
|
||||||
inlineMarkdownImageFileLoader:
|
inlineMarkdownImageFileLoader:
|
||||||
fileLoaderUtils.loaders.inlineMarkdownImageFileLoader,
|
fileLoaderUtils.loaders.inlineMarkdownImageFileLoader,
|
||||||
|
onBrokenMarkdownImages,
|
||||||
};
|
};
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
[asset]()
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
[nonexistent](@site/foo.pdf)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
[asset](pathname:///asset/unchecked.pdf)
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`transformAsset plugin fail if asset url is absent 1`] = `"Markdown link URL is mandatory in "packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/noUrl.md" file (title: asset, line: 1)."`;
|
exports[`transformLinks plugin transform md links to <a /> 1`] = `
|
||||||
|
|
||||||
exports[`transformAsset plugin fail if asset with site alias does not exist 1`] = `"Asset packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/foo.pdf used in packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/nonexistentSiteAlias.md not found."`;
|
|
||||||
|
|
||||||
exports[`transformAsset plugin pathname protocol 1`] = `
|
|
||||||
"[asset](pathname:///asset/unchecked.pdf)
|
|
||||||
"
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`transformAsset plugin transform md links to <a /> 1`] = `
|
|
||||||
"[asset](https://example.com/asset.pdf)
|
"[asset](https://example.com/asset.pdf)
|
||||||
|
|
||||||
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default} />
|
<a target="_blank" data-noBrokenLinkCheck={true} href={require("!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default} />
|
||||||
|
|
@ -54,6 +45,5 @@ in paragraph <a target="_blank" data-noBrokenLinkCheck={true} href={require("!<P
|
||||||
|
|
||||||
<a target="_blank" data-noBrokenLinkCheck={true} href={require("./data.raw!=!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./data.json").default}>JSON</a>
|
<a target="_blank" data-noBrokenLinkCheck={true} href={require("./data.raw!=!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./data.json").default}>JSON</a>
|
||||||
|
|
||||||
<a target="_blank" data-noBrokenLinkCheck={true} href={require("./static/static-json.raw!=!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/static-json.json").default}>static JSON</a>
|
<a target="_blank" data-noBrokenLinkCheck={true} href={require("./static/static-json.raw!=!<PROJECT_ROOT>/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/static-json.json").default}>static JSON</a>"
|
||||||
"
|
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -5,53 +5,270 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import {jest} from '@jest/globals';
|
||||||
|
import * as path from 'path';
|
||||||
import vfile from 'to-vfile';
|
import vfile from 'to-vfile';
|
||||||
import plugin from '..';
|
import plugin, {type PluginOptions} from '..';
|
||||||
import transformImage, {type PluginOptions} from '../../transformImage';
|
import transformImage from '../../transformImage';
|
||||||
|
|
||||||
const processFixture = async (name: string, options?: PluginOptions) => {
|
const siteDir = path.join(__dirname, `__fixtures__`);
|
||||||
|
|
||||||
|
const staticDirs = [
|
||||||
|
path.join(siteDir, 'static'),
|
||||||
|
path.join(siteDir, 'static2'),
|
||||||
|
];
|
||||||
|
|
||||||
|
const getProcessor = async (options?: Partial<PluginOptions>) => {
|
||||||
const {remark} = await import('remark');
|
const {remark} = await import('remark');
|
||||||
const {default: mdx} = await import('remark-mdx');
|
const {default: mdx} = await import('remark-mdx');
|
||||||
const siteDir = path.join(__dirname, `__fixtures__`);
|
return remark()
|
||||||
const staticDirs = [
|
|
||||||
path.join(siteDir, 'static'),
|
|
||||||
path.join(siteDir, 'static2'),
|
|
||||||
];
|
|
||||||
const file = await vfile.read(path.join(siteDir, `${name}.md`));
|
|
||||||
const result = await remark()
|
|
||||||
.use(mdx)
|
.use(mdx)
|
||||||
.use(transformImage, {...options, siteDir, staticDirs})
|
.use(transformImage, {
|
||||||
.use(plugin, {
|
siteDir,
|
||||||
...options,
|
|
||||||
staticDirs,
|
staticDirs,
|
||||||
siteDir: path.join(__dirname, '__fixtures__'),
|
onBrokenMarkdownImages: 'throw',
|
||||||
})
|
})
|
||||||
.process(file);
|
.use(plugin, {
|
||||||
|
staticDirs,
|
||||||
return result.value;
|
siteDir,
|
||||||
|
onBrokenMarkdownLinks: 'throw',
|
||||||
|
...options,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('transformAsset plugin', () => {
|
const processFixture = async (
|
||||||
it('fail if asset url is absent', async () => {
|
name: string,
|
||||||
await expect(
|
options?: Partial<PluginOptions>,
|
||||||
processFixture('noUrl'),
|
) => {
|
||||||
).rejects.toThrowErrorMatchingSnapshot();
|
const processor = await getProcessor(options);
|
||||||
});
|
const file = await vfile.read(path.join(siteDir, `${name}.md`));
|
||||||
|
const result = await processor.process(file);
|
||||||
|
return result.value.toString().trim();
|
||||||
|
};
|
||||||
|
|
||||||
it('fail if asset with site alias does not exist', async () => {
|
const processContent = async (
|
||||||
await expect(
|
content: string,
|
||||||
processFixture('nonexistentSiteAlias'),
|
options?: Partial<PluginOptions>,
|
||||||
).rejects.toThrowErrorMatchingSnapshot();
|
) => {
|
||||||
|
const processor = await getProcessor(options);
|
||||||
|
const result = await processor.process({
|
||||||
|
value: content,
|
||||||
|
path: path.posix.join(siteDir, 'docs', 'myFile.mdx'),
|
||||||
});
|
});
|
||||||
|
return result.value.toString().trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('transformLinks plugin', () => {
|
||||||
it('transform md links to <a />', async () => {
|
it('transform md links to <a />', async () => {
|
||||||
|
// TODO split fixture in many smaller test cases
|
||||||
const result = await processFixture('asset');
|
const result = await processFixture('asset');
|
||||||
expect(result).toMatchSnapshot();
|
expect(result).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('pathname protocol', async () => {
|
it('pathname protocol', async () => {
|
||||||
const result = await processFixture('pathname');
|
const result = await processContent(`pathname:///unchecked.pdf)`);
|
||||||
expect(result).toMatchSnapshot();
|
expect(result).toMatchInlineSnapshot(`"pathname:///unchecked.pdf)"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts absolute file that does not exist', async () => {
|
||||||
|
const result = await processContent(`[file](/dir/file.zip)`);
|
||||||
|
expect(result).toMatchInlineSnapshot(`"[file](/dir/file.zip)"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts relative file that does not exist', async () => {
|
||||||
|
const result = await processContent(`[file](dir/file.zip)`);
|
||||||
|
expect(result).toMatchInlineSnapshot(`"[file](dir/file.zip)"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onBrokenMarkdownLinks', () => {
|
||||||
|
const fixtures = {
|
||||||
|
urlEmpty: `[empty]()`,
|
||||||
|
fileDoesNotExistSiteAlias: `[file](@site/file.zip)`,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('throws', () => {
|
||||||
|
it('if url is empty', async () => {
|
||||||
|
await expect(processContent(fixtures.urlEmpty)).rejects
|
||||||
|
.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Markdown link with empty URL found in source file "packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/docs/myFile.mdx" (1:1).
|
||||||
|
To ignore this error, use the \`siteConfig.markdown.hooks.onBrokenMarkdownLinks\` option, or apply the \`pathname://\` protocol to the broken link URLs."
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if file with site alias does not exist', async () => {
|
||||||
|
await expect(processContent(fixtures.fileDoesNotExistSiteAlias)).rejects
|
||||||
|
.toThrowErrorMatchingInlineSnapshot(`
|
||||||
|
"Markdown link with URL \`@site/file.zip\` in source file "packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/docs/myFile.mdx" (1:1) couldn't be resolved.
|
||||||
|
Make sure it references a local Markdown file that exists within the current plugin.
|
||||||
|
To ignore this error, use the \`siteConfig.markdown.hooks.onBrokenMarkdownLinks\` option, or apply the \`pathname://\` protocol to the broken link URLs."
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('warns', () => {
|
||||||
|
function processWarn(content: string) {
|
||||||
|
return processContent(content, {onBrokenMarkdownLinks: 'warn'});
|
||||||
|
}
|
||||||
|
|
||||||
|
const warnMock = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||||
|
beforeEach(() => {
|
||||||
|
warnMock.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if url is empty', async () => {
|
||||||
|
const result = await processWarn(fixtures.urlEmpty);
|
||||||
|
expect(result).toMatchInlineSnapshot(`"[empty]()"`);
|
||||||
|
expect(warnMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(warnMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"[WARNING] Markdown link with empty URL found in source file "packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/docs/myFile.mdx" (1:1).",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if file with site alias does not exist', async () => {
|
||||||
|
const result = await processWarn(fixtures.fileDoesNotExistSiteAlias);
|
||||||
|
expect(result).toMatchInlineSnapshot(`"[file](@site/file.zip)"`);
|
||||||
|
expect(warnMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(warnMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"[WARNING] Markdown link with URL \`@site/file.zip\` in source file "packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/docs/myFile.mdx" (1:1) couldn't be resolved.
|
||||||
|
Make sure it references a local Markdown file that exists within the current plugin.",
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('function form', () => {
|
||||||
|
function processWarn(content: string) {
|
||||||
|
return processContent(content, {
|
||||||
|
onBrokenMarkdownLinks: (params) => {
|
||||||
|
console.log('onBrokenMarkdownLinks called with', params);
|
||||||
|
// We can alter the AST Node
|
||||||
|
params.node.title = 'fixed link title';
|
||||||
|
params.node.url = 'ignored, less important than returned value';
|
||||||
|
// Or return a new URL
|
||||||
|
return '/404';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const logMock = jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
beforeEach(() => {
|
||||||
|
logMock.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if url is empty', async () => {
|
||||||
|
const result = await processWarn(fixtures.urlEmpty);
|
||||||
|
expect(result).toMatchInlineSnapshot(
|
||||||
|
`"[empty](/404 "fixed link title")"`,
|
||||||
|
);
|
||||||
|
expect(logMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(logMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"onBrokenMarkdownLinks called with",
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 7,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 6,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 2,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "text",
|
||||||
|
"value": "empty",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 10,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 9,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": "fixed link title",
|
||||||
|
"type": "link",
|
||||||
|
"url": "/404",
|
||||||
|
},
|
||||||
|
"sourceFilePath": "packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/docs/myFile.mdx",
|
||||||
|
"url": "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if file with site alias does not exist', async () => {
|
||||||
|
const result = await processWarn(fixtures.fileDoesNotExistSiteAlias);
|
||||||
|
expect(result).toMatchInlineSnapshot(
|
||||||
|
`"[file](/404 "fixed link title")"`,
|
||||||
|
);
|
||||||
|
expect(logMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(logMock.mock.calls).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"onBrokenMarkdownLinks called with",
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 6,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 5,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 2,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "text",
|
||||||
|
"value": "file",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"position": {
|
||||||
|
"end": {
|
||||||
|
"column": 23,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 22,
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"column": 1,
|
||||||
|
"line": 1,
|
||||||
|
"offset": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": "fixed link title",
|
||||||
|
"type": "link",
|
||||||
|
"url": "/404",
|
||||||
|
},
|
||||||
|
"sourceFilePath": "packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__fixtures__/docs/myFile.mdx",
|
||||||
|
"url": "@site/file.zip",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import url from 'url';
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import {
|
import {
|
||||||
toMessageRelativeFilePath,
|
toMessageRelativeFilePath,
|
||||||
|
|
@ -15,26 +14,75 @@ import {
|
||||||
findAsyncSequential,
|
findAsyncSequential,
|
||||||
getFileLoaderUtils,
|
getFileLoaderUtils,
|
||||||
parseURLOrPath,
|
parseURLOrPath,
|
||||||
|
parseLocalURLPath,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import escapeHtml from 'escape-html';
|
import escapeHtml from 'escape-html';
|
||||||
import {assetRequireAttributeValue, transformNode} from '../utils';
|
import logger from '@docusaurus/logger';
|
||||||
|
import {
|
||||||
|
assetRequireAttributeValue,
|
||||||
|
formatNodePositionExtraMessage,
|
||||||
|
transformNode,
|
||||||
|
} from '../utils';
|
||||||
import type {Plugin, Transformer} from 'unified';
|
import type {Plugin, Transformer} from 'unified';
|
||||||
import type {MdxJsxTextElement} from 'mdast-util-mdx';
|
import type {MdxJsxTextElement} from 'mdast-util-mdx';
|
||||||
import type {Parent} from 'unist';
|
import type {Parent} from 'unist';
|
||||||
import type {Link, Literal, Root} from 'mdast';
|
import type {Link, Root} from 'mdast';
|
||||||
|
import type {
|
||||||
|
MarkdownConfig,
|
||||||
|
OnBrokenMarkdownLinksFunction,
|
||||||
|
} from '@docusaurus/types';
|
||||||
|
|
||||||
type PluginOptions = {
|
export type PluginOptions = {
|
||||||
staticDirs: string[];
|
staticDirs: string[];
|
||||||
siteDir: string;
|
siteDir: string;
|
||||||
|
onBrokenMarkdownLinks: MarkdownConfig['hooks']['onBrokenMarkdownLinks'];
|
||||||
};
|
};
|
||||||
|
|
||||||
type Context = PluginOptions & {
|
type Context = PluginOptions & {
|
||||||
|
staticDirs: string[];
|
||||||
|
siteDir: string;
|
||||||
|
onBrokenMarkdownLinks: OnBrokenMarkdownLinksFunction;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
inlineMarkdownLinkFileLoader: string;
|
inlineMarkdownLinkFileLoader: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Target = [node: Link, index: number, parent: Parent];
|
type Target = [node: Link, index: number, parent: Parent];
|
||||||
|
|
||||||
|
function asFunction(
|
||||||
|
onBrokenMarkdownLinks: PluginOptions['onBrokenMarkdownLinks'],
|
||||||
|
): OnBrokenMarkdownLinksFunction {
|
||||||
|
if (typeof onBrokenMarkdownLinks === 'string') {
|
||||||
|
const extraHelp =
|
||||||
|
onBrokenMarkdownLinks === 'throw'
|
||||||
|
? logger.interpolate`\nTo ignore this error, use the code=${'siteConfig.markdown.hooks.onBrokenMarkdownLinks'} option, or apply the code=${'pathname://'} protocol to the broken link URLs.`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return ({sourceFilePath, url: linkUrl, node}) => {
|
||||||
|
const relativePath = toMessageRelativeFilePath(sourceFilePath);
|
||||||
|
if (linkUrl) {
|
||||||
|
logger.report(
|
||||||
|
onBrokenMarkdownLinks,
|
||||||
|
)`Markdown link with URL code=${linkUrl} in source file path=${relativePath}${formatNodePositionExtraMessage(
|
||||||
|
node,
|
||||||
|
)} couldn't be resolved.
|
||||||
|
Make sure it references a local Markdown file that exists within the current plugin.${extraHelp}`;
|
||||||
|
} else {
|
||||||
|
logger.report(
|
||||||
|
onBrokenMarkdownLinks,
|
||||||
|
)`Markdown link with empty URL found in source file path=${relativePath}${formatNodePositionExtraMessage(
|
||||||
|
node,
|
||||||
|
)}.${extraHelp}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return (params) =>
|
||||||
|
onBrokenMarkdownLinks({
|
||||||
|
...params,
|
||||||
|
sourceFilePath: toMessageRelativeFilePath(params.sourceFilePath),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms the link node to a JSX `<a>` element with a `require()` call.
|
* Transforms the link node to a JSX `<a>` element with a `require()` call.
|
||||||
*/
|
*/
|
||||||
|
|
@ -123,27 +171,15 @@ async function toAssetRequireNode(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureAssetFileExist(assetPath: string, sourceFilePath: string) {
|
async function getLocalFileAbsolutePath(
|
||||||
const assetExists = await fs.pathExists(assetPath);
|
|
||||||
if (!assetExists) {
|
|
||||||
throw new Error(
|
|
||||||
`Asset ${toMessageRelativeFilePath(
|
|
||||||
assetPath,
|
|
||||||
)} used in ${toMessageRelativeFilePath(sourceFilePath)} not found.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAssetAbsolutePath(
|
|
||||||
assetPath: string,
|
assetPath: string,
|
||||||
{siteDir, filePath, staticDirs}: Context,
|
{siteDir, filePath, staticDirs}: Context,
|
||||||
) {
|
) {
|
||||||
if (assetPath.startsWith('@site/')) {
|
if (assetPath.startsWith('@site/')) {
|
||||||
const assetFilePath = path.join(siteDir, assetPath.replace('@site/', ''));
|
const assetFilePath = path.join(siteDir, assetPath.replace('@site/', ''));
|
||||||
// The @site alias is the only way to believe that the user wants an asset.
|
if (await fs.pathExists(assetFilePath)) {
|
||||||
// Everything else can just be a link URL
|
return assetFilePath;
|
||||||
await ensureAssetFileExist(assetFilePath, filePath);
|
}
|
||||||
return assetFilePath;
|
|
||||||
} else if (path.isAbsolute(assetPath)) {
|
} else if (path.isAbsolute(assetPath)) {
|
||||||
const assetFilePath = await findAsyncSequential(
|
const assetFilePath = await findAsyncSequential(
|
||||||
staticDirs.map((dir) => path.join(dir, assetPath)),
|
staticDirs.map((dir) => path.join(dir, assetPath)),
|
||||||
|
|
@ -164,54 +200,71 @@ async function getAssetAbsolutePath(
|
||||||
async function processLinkNode(target: Target, context: Context) {
|
async function processLinkNode(target: Target, context: Context) {
|
||||||
const [node] = target;
|
const [node] = target;
|
||||||
if (!node.url) {
|
if (!node.url) {
|
||||||
// Try to improve error feedback
|
node.url =
|
||||||
// see https://github.com/facebook/docusaurus/issues/3309#issuecomment-690371675
|
context.onBrokenMarkdownLinks({
|
||||||
const title =
|
url: node.url,
|
||||||
node.title ?? (node.children[0] as Literal | undefined)?.value ?? '?';
|
sourceFilePath: context.filePath,
|
||||||
const line = node.position?.start.line ?? '?';
|
node,
|
||||||
throw new Error(
|
}) ?? node.url;
|
||||||
`Markdown link URL is mandatory in "${toMessageRelativeFilePath(
|
return;
|
||||||
context.filePath,
|
|
||||||
)}" file (title: ${title}, line: ${line}).`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedUrl = url.parse(node.url);
|
const localUrlPath = parseLocalURLPath(node.url);
|
||||||
if (parsedUrl.protocol || !parsedUrl.pathname) {
|
if (!localUrlPath) {
|
||||||
// Don't process pathname:// here, it's used by the <Link> component
|
// Don't process pathname:// here, it's used by the <Link> component
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const hasSiteAlias = parsedUrl.pathname.startsWith('@site/');
|
|
||||||
|
const hasSiteAlias = localUrlPath.pathname.startsWith('@site/');
|
||||||
const hasAssetLikeExtension =
|
const hasAssetLikeExtension =
|
||||||
path.extname(parsedUrl.pathname) &&
|
path.extname(localUrlPath.pathname) &&
|
||||||
!parsedUrl.pathname.match(/\.(?:mdx?|html)(?:#|$)/);
|
!localUrlPath.pathname.match(/\.(?:mdx?|html)(?:#|$)/);
|
||||||
if (!hasSiteAlias && !hasAssetLikeExtension) {
|
if (!hasSiteAlias && !hasAssetLikeExtension) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const assetPath = await getAssetAbsolutePath(
|
const localFilePath = await getLocalFileAbsolutePath(
|
||||||
decodeURIComponent(parsedUrl.pathname),
|
decodeURIComponent(localUrlPath.pathname),
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
if (assetPath) {
|
|
||||||
await toAssetRequireNode(target, assetPath, context);
|
if (localFilePath) {
|
||||||
|
await toAssetRequireNode(target, localFilePath, context);
|
||||||
|
} else {
|
||||||
|
// The @site alias is the only way to believe that the user wants an asset.
|
||||||
|
if (hasSiteAlias) {
|
||||||
|
node.url =
|
||||||
|
context.onBrokenMarkdownLinks({
|
||||||
|
url: node.url,
|
||||||
|
sourceFilePath: context.filePath,
|
||||||
|
node,
|
||||||
|
}) ?? node.url;
|
||||||
|
} else {
|
||||||
|
// Even if the url has a dot, and it looks like a file extension
|
||||||
|
// it can be risky to throw and fail fast by default
|
||||||
|
// It's perfectly valid for a route path segment to look like a filename
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
const plugin: Plugin<PluginOptions[], Root> = function plugin(
|
||||||
options,
|
options,
|
||||||
): Transformer<Root> {
|
): Transformer<Root> {
|
||||||
|
const onBrokenMarkdownLinks = asFunction(options.onBrokenMarkdownLinks);
|
||||||
|
|
||||||
return async (root, vfile) => {
|
return async (root, vfile) => {
|
||||||
const {visit} = await import('unist-util-visit');
|
const {visit} = await import('unist-util-visit');
|
||||||
|
|
||||||
const fileLoaderUtils = getFileLoaderUtils(
|
const fileLoaderUtils = getFileLoaderUtils(
|
||||||
vfile.data.compilerName === 'server',
|
vfile.data.compilerName === 'server',
|
||||||
);
|
);
|
||||||
|
|
||||||
const context: Context = {
|
const context: Context = {
|
||||||
...options,
|
...options,
|
||||||
filePath: vfile.path!,
|
filePath: vfile.path!,
|
||||||
inlineMarkdownLinkFileLoader:
|
inlineMarkdownLinkFileLoader:
|
||||||
fileLoaderUtils.loaders.inlineMarkdownLinkFileLoader,
|
fileLoaderUtils.loaders.inlineMarkdownLinkFileLoader,
|
||||||
|
onBrokenMarkdownLinks,
|
||||||
};
|
};
|
||||||
|
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import path from 'path';
|
||||||
import process from 'process';
|
import process from 'process';
|
||||||
import logger from '@docusaurus/logger';
|
import logger from '@docusaurus/logger';
|
||||||
import {posixPath} from '@docusaurus/utils';
|
import {posixPath} from '@docusaurus/utils';
|
||||||
import {transformNode} from '../utils';
|
import {formatNodePositionExtraMessage, transformNode} from '../utils';
|
||||||
import type {Root} from 'mdast';
|
import type {Root} from 'mdast';
|
||||||
import type {Parent} from 'unist';
|
import type {Parent} from 'unist';
|
||||||
import type {Transformer, Processor, Plugin} from 'unified';
|
import type {Transformer, Processor, Plugin} from 'unified';
|
||||||
|
|
@ -39,17 +39,9 @@ function formatDirectiveName(directive: Directives) {
|
||||||
return `${prefix}${directive.name}`;
|
return `${prefix}${directive.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDirectivePosition(directive: Directives): string | undefined {
|
|
||||||
return directive.position?.start
|
|
||||||
? logger.interpolate`number=${directive.position.start.line}:number=${directive.position.start.column}`
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatUnusedDirectiveMessage(directive: Directives) {
|
function formatUnusedDirectiveMessage(directive: Directives) {
|
||||||
const name = formatDirectiveName(directive);
|
const name = formatDirectiveName(directive);
|
||||||
const position = formatDirectivePosition(directive);
|
return `- ${name}${formatNodePositionExtraMessage(directive)}`;
|
||||||
|
|
||||||
return `- ${name} ${position ? `(${position})` : ''}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatUnusedDirectivesMessage({
|
function formatUnusedDirectivesMessage({
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import logger from '@docusaurus/logger';
|
||||||
import type {Node} from 'unist';
|
import type {Node} from 'unist';
|
||||||
import type {MdxJsxAttributeValueExpression} from 'mdast-util-mdx';
|
import type {MdxJsxAttributeValueExpression} from 'mdast-util-mdx';
|
||||||
|
|
||||||
|
|
@ -83,3 +84,16 @@ export function assetRequireAttributeValue(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatNodePosition(node: Node): string | undefined {
|
||||||
|
return node.position?.start
|
||||||
|
? logger.interpolate`number=${node.position.start.line}:number=${node.position.start.column}`
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns " (line:column)" when position info is available
|
||||||
|
// The initial space is useful to append easily to any existing message
|
||||||
|
export function formatNodePositionExtraMessage(node: Node): string {
|
||||||
|
const position = formatNodePosition(node);
|
||||||
|
return `${position ? ` (${position})` : ''}`;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/module-type-aliases",
|
"name": "@docusaurus/module-type-aliases",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"description": "Docusaurus module type aliases.",
|
"description": "Docusaurus module type aliases.",
|
||||||
"types": "./src/index.d.ts",
|
"types": "./src/index.d.ts",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
"directory": "packages/docusaurus-module-type-aliases"
|
"directory": "packages/docusaurus-module-type-aliases"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/types": "3.7.0",
|
"@docusaurus/types": "3.9.2",
|
||||||
"@types/history": "^4.7.11",
|
"@types/history": "^4.7.11",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-router-config": "*",
|
"@types/react-router-config": "*",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-client-redirects",
|
"name": "@docusaurus/plugin-client-redirects",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"description": "Client redirects plugin for Docusaurus.",
|
"description": "Client redirects plugin for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
|
@ -18,24 +18,24 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.7.0",
|
"@docusaurus/core": "3.9.2",
|
||||||
"@docusaurus/logger": "3.7.0",
|
"@docusaurus/logger": "3.9.2",
|
||||||
"@docusaurus/utils": "3.7.0",
|
"@docusaurus/utils": "3.9.2",
|
||||||
"@docusaurus/utils-common": "3.7.0",
|
"@docusaurus/utils-common": "3.9.2",
|
||||||
"@docusaurus/utils-validation": "3.7.0",
|
"@docusaurus/utils-validation": "3.9.2",
|
||||||
"eta": "^2.2.0",
|
"eta": "^2.2.0",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"tslib": "^2.6.0"
|
"tslib": "^2.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/types": "3.7.0"
|
"@docusaurus/types": "3.9.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^18.0.0 || ^19.0.0",
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
"react-dom": "^18.0.0 || ^19.0.0"
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@docusaurus/plugin-content-blog",
|
"name": "@docusaurus/plugin-content-blog",
|
||||||
"version": "3.7.0",
|
"version": "3.9.2",
|
||||||
"description": "Blog plugin for Docusaurus.",
|
"description": "Blog plugin for Docusaurus.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "src/plugin-content-blog.d.ts",
|
"types": "src/plugin-content-blog.d.ts",
|
||||||
|
|
@ -31,14 +31,14 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@docusaurus/core": "3.7.0",
|
"@docusaurus/core": "3.9.2",
|
||||||
"@docusaurus/logger": "3.7.0",
|
"@docusaurus/logger": "3.9.2",
|
||||||
"@docusaurus/mdx-loader": "3.7.0",
|
"@docusaurus/mdx-loader": "3.9.2",
|
||||||
"@docusaurus/theme-common": "3.7.0",
|
"@docusaurus/theme-common": "3.9.2",
|
||||||
"@docusaurus/types": "3.7.0",
|
"@docusaurus/types": "3.9.2",
|
||||||
"@docusaurus/utils": "3.7.0",
|
"@docusaurus/utils": "3.9.2",
|
||||||
"@docusaurus/utils-common": "3.7.0",
|
"@docusaurus/utils-common": "3.9.2",
|
||||||
"@docusaurus/utils-validation": "3.7.0",
|
"@docusaurus/utils-validation": "3.9.2",
|
||||||
"cheerio": "1.0.0-rc.12",
|
"cheerio": "1.0.0-rc.12",
|
||||||
"feed": "^4.2.2",
|
"feed": "^4.2.2",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
"react-dom": "^18.0.0 || ^19.0.0"
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0"
|
"node": ">=20.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@total-typescript/shoehorn": "^0.1.2",
|
"@total-typescript/shoehorn": "^0.1.2",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,637 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`buildAllRoutes works for realistic blog post 2`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogPostPage",
|
||||||
|
"context": {
|
||||||
|
"blogMetadata": "@aliased/data/blogMetadata-default.json",
|
||||||
|
},
|
||||||
|
"exact": true,
|
||||||
|
"metadata": {
|
||||||
|
"lastUpdatedAt": undefined,
|
||||||
|
"sourceFilePath": "blog/post1.md",
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"content": "@site/blog/post1.md",
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/post1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogPostPage",
|
||||||
|
"context": {
|
||||||
|
"blogMetadata": "@aliased/data/blogMetadata-default.json",
|
||||||
|
},
|
||||||
|
"exact": true,
|
||||||
|
"metadata": {
|
||||||
|
"lastUpdatedAt": undefined,
|
||||||
|
"sourceFilePath": "blog/post2.md",
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"content": "@site/blog/post2.md",
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/post2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogPostPage",
|
||||||
|
"context": {
|
||||||
|
"blogMetadata": "@aliased/data/blogMetadata-default.json",
|
||||||
|
},
|
||||||
|
"exact": true,
|
||||||
|
"metadata": {
|
||||||
|
"lastUpdatedAt": undefined,
|
||||||
|
"sourceFilePath": "blog/post3.md",
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"content": "@site/blog/post3.md",
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/post3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogPostPage",
|
||||||
|
"context": {
|
||||||
|
"blogMetadata": "@aliased/data/blogMetadata-default.json",
|
||||||
|
},
|
||||||
|
"exact": true,
|
||||||
|
"metadata": {
|
||||||
|
"lastUpdatedAt": undefined,
|
||||||
|
"sourceFilePath": "blog/post4.md",
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"content": "@site/blog/post4.md",
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/post4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogPostPage",
|
||||||
|
"context": {
|
||||||
|
"blogMetadata": "@aliased/data/blogMetadata-default.json",
|
||||||
|
},
|
||||||
|
"exact": true,
|
||||||
|
"metadata": {
|
||||||
|
"lastUpdatedAt": undefined,
|
||||||
|
"sourceFilePath": "blog/post5.md",
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"content": "@site/blog/post5.md",
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/post5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogPostPage",
|
||||||
|
"context": {
|
||||||
|
"blogMetadata": "@aliased/data/blogMetadata-default.json",
|
||||||
|
},
|
||||||
|
"exact": true,
|
||||||
|
"metadata": {
|
||||||
|
"lastUpdatedAt": undefined,
|
||||||
|
"sourceFilePath": "blog/post6.md",
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"content": "@site/blog/post6.md",
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/post6",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogListPage",
|
||||||
|
"exact": true,
|
||||||
|
"modules": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"__import": true,
|
||||||
|
"path": "@site/blog/post1.md",
|
||||||
|
"query": {
|
||||||
|
"truncated": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"__import": true,
|
||||||
|
"path": "@site/blog/post2.md",
|
||||||
|
"query": {
|
||||||
|
"truncated": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog",
|
||||||
|
"props": {
|
||||||
|
"metadata": {
|
||||||
|
"blogDescription": "Custom blog description",
|
||||||
|
"blogTitle": "Custom blog title",
|
||||||
|
"nextPage": "/blog/page/2",
|
||||||
|
"page": 1,
|
||||||
|
"permalink": "/blog",
|
||||||
|
"postsPerPage": 2,
|
||||||
|
"previousPage": undefined,
|
||||||
|
"totalCount": 5,
|
||||||
|
"totalPages": 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogListPage",
|
||||||
|
"exact": true,
|
||||||
|
"modules": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"__import": true,
|
||||||
|
"path": "@site/blog/post4.md",
|
||||||
|
"query": {
|
||||||
|
"truncated": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"__import": true,
|
||||||
|
"path": "@site/blog/post5.md",
|
||||||
|
"query": {
|
||||||
|
"truncated": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/page/2",
|
||||||
|
"props": {
|
||||||
|
"metadata": {
|
||||||
|
"blogDescription": "Custom blog description",
|
||||||
|
"blogTitle": "Custom blog title",
|
||||||
|
"nextPage": "/blog/page/3",
|
||||||
|
"page": 2,
|
||||||
|
"permalink": "/blog/page/2",
|
||||||
|
"postsPerPage": 2,
|
||||||
|
"previousPage": "/blog",
|
||||||
|
"totalCount": 5,
|
||||||
|
"totalPages": 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogListPage",
|
||||||
|
"exact": true,
|
||||||
|
"modules": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"__import": true,
|
||||||
|
"path": "@site/blog/post6.md",
|
||||||
|
"query": {
|
||||||
|
"truncated": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/page/3",
|
||||||
|
"props": {
|
||||||
|
"metadata": {
|
||||||
|
"blogDescription": "Custom blog description",
|
||||||
|
"blogTitle": "Custom blog title",
|
||||||
|
"nextPage": undefined,
|
||||||
|
"page": 3,
|
||||||
|
"permalink": "/blog/page/3",
|
||||||
|
"postsPerPage": 2,
|
||||||
|
"previousPage": "/blog/page/2",
|
||||||
|
"totalCount": 5,
|
||||||
|
"totalPages": 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogArchivePage",
|
||||||
|
"exact": true,
|
||||||
|
"path": "/blog/archive",
|
||||||
|
"props": {
|
||||||
|
"archive": {
|
||||||
|
"blogPosts": [
|
||||||
|
{
|
||||||
|
"content": "Content for post1",
|
||||||
|
"id": "post1",
|
||||||
|
"metadata": {
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"key": "author1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for post1",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/post1",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/post1.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for post1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": "Content for post2",
|
||||||
|
"id": "post2",
|
||||||
|
"metadata": {
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"key": "author1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for post2",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/post2",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/post2.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for post2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": "Content for post4",
|
||||||
|
"id": "post4",
|
||||||
|
"metadata": {
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"key": "author1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "author2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for post4",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/post4",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/post4.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for post4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": "Content for post5",
|
||||||
|
"id": "post5",
|
||||||
|
"metadata": {
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"key": "author2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "author3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for post5",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/post5",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/post5.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for post5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": "Content for post6",
|
||||||
|
"id": "post6",
|
||||||
|
"metadata": {
|
||||||
|
"authors": [],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for post6",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/post6",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/post6.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for post6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/Blog/Pages/BlogAuthorsListPage",
|
||||||
|
"context": {
|
||||||
|
"blogMetadata": "@aliased/data/blogMetadata-default.json",
|
||||||
|
},
|
||||||
|
"exact": true,
|
||||||
|
"modules": {
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/authors",
|
||||||
|
"props": {
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"count": 3,
|
||||||
|
"key": "author1",
|
||||||
|
"name": "Author 1",
|
||||||
|
"page": {
|
||||||
|
"permalink": "/blog/authors/author1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 2,
|
||||||
|
"key": "author2",
|
||||||
|
"name": "Author 2",
|
||||||
|
"page": null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"key": "author3",
|
||||||
|
"name": "Author 3",
|
||||||
|
"page": {
|
||||||
|
"permalink": "/blog/authors/author3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/Blog/Pages/BlogAuthorsPostsPage",
|
||||||
|
"context": {
|
||||||
|
"blogMetadata": "@aliased/data/blogMetadata-default.json",
|
||||||
|
},
|
||||||
|
"exact": true,
|
||||||
|
"modules": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"__import": true,
|
||||||
|
"path": "@site/blog/post1.md",
|
||||||
|
"query": {
|
||||||
|
"truncated": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"__import": true,
|
||||||
|
"path": "@site/blog/post2.md",
|
||||||
|
"query": {
|
||||||
|
"truncated": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/authors/author1",
|
||||||
|
"props": {
|
||||||
|
"author": {
|
||||||
|
"count": 3,
|
||||||
|
"key": "author1",
|
||||||
|
"name": "Author 1",
|
||||||
|
"page": {
|
||||||
|
"permalink": "/blog/authors/author1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"listMetadata": {
|
||||||
|
"blogDescription": "Custom blog description",
|
||||||
|
"blogTitle": "Custom blog title",
|
||||||
|
"nextPage": "/blog/authors/author1/page/2",
|
||||||
|
"page": 1,
|
||||||
|
"permalink": "/blog/authors/author1",
|
||||||
|
"postsPerPage": 2,
|
||||||
|
"previousPage": undefined,
|
||||||
|
"totalCount": 3,
|
||||||
|
"totalPages": 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/Blog/Pages/BlogAuthorsPostsPage",
|
||||||
|
"context": {
|
||||||
|
"blogMetadata": "@aliased/data/blogMetadata-default.json",
|
||||||
|
},
|
||||||
|
"exact": true,
|
||||||
|
"modules": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"__import": true,
|
||||||
|
"path": "@site/blog/post4.md",
|
||||||
|
"query": {
|
||||||
|
"truncated": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/authors/author1/page/2",
|
||||||
|
"props": {
|
||||||
|
"author": {
|
||||||
|
"count": 3,
|
||||||
|
"key": "author1",
|
||||||
|
"name": "Author 1",
|
||||||
|
"page": {
|
||||||
|
"permalink": "/blog/authors/author1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"listMetadata": {
|
||||||
|
"blogDescription": "Custom blog description",
|
||||||
|
"blogTitle": "Custom blog title",
|
||||||
|
"nextPage": undefined,
|
||||||
|
"page": 2,
|
||||||
|
"permalink": "/blog/authors/author1/page/2",
|
||||||
|
"postsPerPage": 2,
|
||||||
|
"previousPage": "/blog/authors/author1",
|
||||||
|
"totalCount": 3,
|
||||||
|
"totalPages": 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/Blog/Pages/BlogAuthorsPostsPage",
|
||||||
|
"context": {
|
||||||
|
"blogMetadata": "@aliased/data/blogMetadata-default.json",
|
||||||
|
},
|
||||||
|
"exact": true,
|
||||||
|
"modules": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"__import": true,
|
||||||
|
"path": "@site/blog/post5.md",
|
||||||
|
"query": {
|
||||||
|
"truncated": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/authors/author3",
|
||||||
|
"props": {
|
||||||
|
"author": {
|
||||||
|
"count": 1,
|
||||||
|
"key": "author3",
|
||||||
|
"name": "Author 3",
|
||||||
|
"page": {
|
||||||
|
"permalink": "/blog/authors/author3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"listMetadata": {
|
||||||
|
"blogDescription": "Custom blog description",
|
||||||
|
"blogTitle": "Custom blog title",
|
||||||
|
"nextPage": undefined,
|
||||||
|
"page": 1,
|
||||||
|
"permalink": "/blog/authors/author3",
|
||||||
|
"postsPerPage": 2,
|
||||||
|
"previousPage": undefined,
|
||||||
|
"totalCount": 1,
|
||||||
|
"totalPages": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`buildAllRoutes works for realistic blog post 3`] = `
|
||||||
|
{
|
||||||
|
"blog-post-list-prop-default.json": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"permalink": "/blog/post1",
|
||||||
|
"title": "Title for post1",
|
||||||
|
"unlisted": undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"permalink": "/blog/post2",
|
||||||
|
"title": "Title for post2",
|
||||||
|
"unlisted": undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"permalink": "/blog/post3",
|
||||||
|
"title": "Title for post3",
|
||||||
|
"unlisted": true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"permalink": "/blog/post4",
|
||||||
|
"title": "Title for post4",
|
||||||
|
"unlisted": undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"permalink": "/blog/post5",
|
||||||
|
"title": "Title for post5",
|
||||||
|
"unlisted": undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"title": "Custom blog sidebar title",
|
||||||
|
},
|
||||||
|
"blogMetadata-default.json": {
|
||||||
|
"authorsListPath": "/blog/authors",
|
||||||
|
"blogBasePath": "/blog",
|
||||||
|
"blogTitle": "Custom blog title",
|
||||||
|
},
|
||||||
|
"site-blog-post-1-md-235.json": {
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"key": "author1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for post1",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/post1",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/post1.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for post1",
|
||||||
|
},
|
||||||
|
"site-blog-post-2-md-b42.json": {
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"key": "author1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for post2",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/post2",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/post2.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for post2",
|
||||||
|
},
|
||||||
|
"site-blog-post-3-md-3b7.json": {
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"key": "author3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for post3",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/post3",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/post3.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for post3",
|
||||||
|
"unlisted": true,
|
||||||
|
},
|
||||||
|
"site-blog-post-4-md-15a.json": {
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"key": "author1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "author2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for post4",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/post4",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/post4.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for post4",
|
||||||
|
},
|
||||||
|
"site-blog-post-5-md-274.json": {
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"key": "author2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "author3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for post5",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/post5",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/post5.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for post5",
|
||||||
|
},
|
||||||
|
"site-blog-post-6-md-3ca.json": {
|
||||||
|
"authors": [],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for post6",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/post6",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/post6.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for post6",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
@ -24,24 +24,7 @@ exports[`getContentTranslationFiles returns translation files matching snapshot
|
||||||
|
|
||||||
exports[`translateContent falls back when translation is incomplete 1`] = `
|
exports[`translateContent falls back when translation is incomplete 1`] = `
|
||||||
{
|
{
|
||||||
"blogListPaginated": [
|
"blogDescription": "Someone's random blog",
|
||||||
{
|
|
||||||
"items": [
|
|
||||||
"hello",
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"blogDescription": "Someone's random blog",
|
|
||||||
"blogTitle": "My blog",
|
|
||||||
"nextPage": undefined,
|
|
||||||
"page": 1,
|
|
||||||
"permalink": "/",
|
|
||||||
"postsPerPage": 10,
|
|
||||||
"previousPage": undefined,
|
|
||||||
"totalCount": 1,
|
|
||||||
"totalPages": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"blogPosts": [
|
"blogPosts": [
|
||||||
{
|
{
|
||||||
"content": "",
|
"content": "",
|
||||||
|
|
@ -63,29 +46,13 @@ exports[`translateContent falls back when translation is incomplete 1`] = `
|
||||||
"blogSidebarTitle": "All my posts",
|
"blogSidebarTitle": "All my posts",
|
||||||
"blogTags": {},
|
"blogTags": {},
|
||||||
"blogTagsListPath": "/tags",
|
"blogTagsListPath": "/tags",
|
||||||
|
"blogTitle": "My blog",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`translateContent returns translated loaded 1`] = `
|
exports[`translateContent returns translated loaded 1`] = `
|
||||||
{
|
{
|
||||||
"blogListPaginated": [
|
"blogDescription": "Someone's random blog (translated)",
|
||||||
{
|
|
||||||
"items": [
|
|
||||||
"hello",
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"blogDescription": "Someone's random blog (translated)",
|
|
||||||
"blogTitle": "My blog (translated)",
|
|
||||||
"nextPage": undefined,
|
|
||||||
"page": 1,
|
|
||||||
"permalink": "/",
|
|
||||||
"postsPerPage": 10,
|
|
||||||
"previousPage": undefined,
|
|
||||||
"totalCount": 1,
|
|
||||||
"totalPages": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"blogPosts": [
|
"blogPosts": [
|
||||||
{
|
{
|
||||||
"content": "",
|
"content": "",
|
||||||
|
|
@ -107,5 +74,6 @@ exports[`translateContent returns translated loaded 1`] = `
|
||||||
"blogSidebarTitle": "All my posts (translated)",
|
"blogSidebarTitle": "All my posts (translated)",
|
||||||
"blogTags": {},
|
"blogTags": {},
|
||||||
"blogTagsListPath": "/tags",
|
"blogTagsListPath": "/tags",
|
||||||
|
"blogTitle": "My blog (translated)",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,13 @@ describe('normalizeSocials', () => {
|
||||||
twitch: 'gingergeek',
|
twitch: 'gingergeek',
|
||||||
youtube: 'gingergeekuk',
|
youtube: 'gingergeekuk',
|
||||||
mastodon: 'Mastodon',
|
mastodon: 'Mastodon',
|
||||||
|
email: 'seb@example.com',
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(normalizeSocials(socials)).toMatchInlineSnapshot(`
|
expect(normalizeSocials(socials)).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"bluesky": "https://bsky.app/profile/gingergeek.co.uk",
|
"bluesky": "https://bsky.app/profile/gingergeek.co.uk",
|
||||||
|
"email": "mailto:seb@example.com",
|
||||||
"github": "https://github.com/ozakione",
|
"github": "https://github.com/ozakione",
|
||||||
"instagram": "https://www.instagram.com/thisweekinreact",
|
"instagram": "https://www.instagram.com/thisweekinreact",
|
||||||
"linkedin": "https://www.linkedin.com/in/ozakione/",
|
"linkedin": "https://www.linkedin.com/in/ozakione/",
|
||||||
|
|
@ -48,11 +50,13 @@ describe('normalizeSocials', () => {
|
||||||
instaGRam: 'thisweekinreact',
|
instaGRam: 'thisweekinreact',
|
||||||
BLUESKY: 'gingergeek.co.uk',
|
BLUESKY: 'gingergeek.co.uk',
|
||||||
tHrEaDs: 'gingergeekuk',
|
tHrEaDs: 'gingergeekuk',
|
||||||
|
eMAil: 'seb@example.com',
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(normalizeSocials(socials)).toMatchInlineSnapshot(`
|
expect(normalizeSocials(socials)).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"bluesky": "https://bsky.app/profile/gingergeek.co.uk",
|
"bluesky": "https://bsky.app/profile/gingergeek.co.uk",
|
||||||
|
"email": "mailto:seb@example.com",
|
||||||
"github": "https://github.com/ozakione",
|
"github": "https://github.com/ozakione",
|
||||||
"instagram": "https://www.instagram.com/thisweekinreact",
|
"instagram": "https://www.instagram.com/thisweekinreact",
|
||||||
"linkedin": "https://www.linkedin.com/in/ozakione/",
|
"linkedin": "https://www.linkedin.com/in/ozakione/",
|
||||||
|
|
@ -69,6 +73,7 @@ describe('normalizeSocials', () => {
|
||||||
linkedin: 'https://linkedin.com/ozakione',
|
linkedin: 'https://linkedin.com/ozakione',
|
||||||
github: 'https://github.com/ozakione',
|
github: 'https://github.com/ozakione',
|
||||||
stackoverflow: 'https://stackoverflow.com/ozakione',
|
stackoverflow: 'https://stackoverflow.com/ozakione',
|
||||||
|
email: 'mailto:seb@example.com',
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(normalizeSocials(socials)).toEqual(socials);
|
expect(normalizeSocials(socials)).toEqual(socials);
|
||||||
|
|
@ -81,10 +86,12 @@ describe('normalizeSocials', () => {
|
||||||
github: 'https://github.com/ozakione',
|
github: 'https://github.com/ozakione',
|
||||||
stackoverflow: 'https://stackoverflow.com/ozakione',
|
stackoverflow: 'https://stackoverflow.com/ozakione',
|
||||||
mastodon: 'https://hachyderm.io/@hachyderm',
|
mastodon: 'https://hachyderm.io/@hachyderm',
|
||||||
|
email: 'mailto:seb@example.com',
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(normalizeSocials(socials)).toMatchInlineSnapshot(`
|
expect(normalizeSocials(socials)).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
|
"email": "mailto:seb@example.com",
|
||||||
"github": "https://github.com/ozakione",
|
"github": "https://github.com/ozakione",
|
||||||
"linkedin": "https://www.linkedin.com/in/ozakione/",
|
"linkedin": "https://www.linkedin.com/in/ozakione/",
|
||||||
"mastodon": "https://hachyderm.io/@hachyderm",
|
"mastodon": "https://hachyderm.io/@hachyderm",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@
|
||||||
import {jest} from '@jest/globals';
|
import {jest} from '@jest/globals';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import {DEFAULT_PARSE_FRONT_MATTER} from '@docusaurus/utils';
|
import {
|
||||||
|
DEFAULT_PARSE_FRONT_MATTER,
|
||||||
|
DEFAULT_VCS_CONFIG,
|
||||||
|
} from '@docusaurus/utils';
|
||||||
import {fromPartial} from '@total-typescript/shoehorn';
|
import {fromPartial} from '@total-typescript/shoehorn';
|
||||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||||
import tree from 'tree-node-cli';
|
import tree from 'tree-node-cli';
|
||||||
|
|
@ -51,7 +54,7 @@ function getBlogContentPaths(siteDir: string): BlogContentPaths {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testGenerateFeeds(
|
async function testGenerateFeeds(
|
||||||
context: LoadContext,
|
contextInput: LoadContext,
|
||||||
optionsInput: Options,
|
optionsInput: Options,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const options = validateOptions({
|
const options = validateOptions({
|
||||||
|
|
@ -62,6 +65,17 @@ async function testGenerateFeeds(
|
||||||
options: optionsInput,
|
options: optionsInput,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const context: LoadContext = {
|
||||||
|
...contextInput,
|
||||||
|
siteConfig: {
|
||||||
|
...contextInput.siteConfig,
|
||||||
|
future: {
|
||||||
|
...contextInput.siteConfig?.future,
|
||||||
|
experimental_vcs: DEFAULT_VCS_CONFIG,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const contentPaths = getBlogContentPaths(context.siteDir);
|
const contentPaths = getBlogContentPaths(context.siteDir);
|
||||||
const authorsMap = await getAuthorsMap({
|
const authorsMap = await getAuthorsMap({
|
||||||
contentPaths,
|
contentPaths,
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {jest} from '@jest/globals';
|
import {jest} from '@jest/globals';
|
||||||
import path from 'path';
|
import * as path from 'path';
|
||||||
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
import {normalizePluginOptions} from '@docusaurus/utils-validation';
|
||||||
import {
|
import {posixPath, getLocaleConfig, TEST_VCS} from '@docusaurus/utils';
|
||||||
posixPath,
|
|
||||||
getFileCommitDate,
|
|
||||||
LAST_UPDATE_FALLBACK,
|
|
||||||
} from '@docusaurus/utils';
|
|
||||||
import {DEFAULT_FUTURE_CONFIG} from '@docusaurus/core/src/server/configValidation';
|
import {DEFAULT_FUTURE_CONFIG} from '@docusaurus/core/src/server/configValidation';
|
||||||
import pluginContentBlog from '../index';
|
import pluginContentBlog from '../index';
|
||||||
import {validateOptions} from '../options';
|
import {validateOptions} from '../options';
|
||||||
|
|
@ -22,6 +18,7 @@ import type {
|
||||||
I18n,
|
I18n,
|
||||||
Validate,
|
Validate,
|
||||||
MarkdownConfig,
|
MarkdownConfig,
|
||||||
|
I18nLocaleConfig,
|
||||||
} from '@docusaurus/types';
|
} from '@docusaurus/types';
|
||||||
import type {
|
import type {
|
||||||
BlogPost,
|
BlogPost,
|
||||||
|
|
@ -30,6 +27,10 @@ import type {
|
||||||
EditUrlFunction,
|
EditUrlFunction,
|
||||||
} from '@docusaurus/plugin-content-blog';
|
} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
|
async function getFileCreationDate(filePath: string): Promise<Date> {
|
||||||
|
return new Date((await TEST_VCS.getFileCreationInfo(filePath)).timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
const markdown: MarkdownConfig = {
|
const markdown: MarkdownConfig = {
|
||||||
format: 'mdx',
|
format: 'mdx',
|
||||||
mermaid: true,
|
mermaid: true,
|
||||||
|
|
@ -67,7 +68,10 @@ Available blog post titles are:\n- ${blogPosts
|
||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getI18n(locale: string): I18n {
|
function getI18n(
|
||||||
|
locale: string,
|
||||||
|
localeConfigOptions?: Partial<I18nLocaleConfig>,
|
||||||
|
): I18n {
|
||||||
return {
|
return {
|
||||||
currentLocale: locale,
|
currentLocale: locale,
|
||||||
locales: [locale],
|
locales: [locale],
|
||||||
|
|
@ -80,6 +84,8 @@ function getI18n(locale: string): I18n {
|
||||||
htmlLang: locale,
|
htmlLang: locale,
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
path: locale,
|
path: locale,
|
||||||
|
translate: true,
|
||||||
|
...localeConfigOptions,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -94,13 +100,14 @@ const BaseEditUrl = 'https://baseEditUrl.com/edit';
|
||||||
const getPlugin = async (
|
const getPlugin = async (
|
||||||
siteDir: string,
|
siteDir: string,
|
||||||
pluginOptions: Partial<PluginOptions> = {},
|
pluginOptions: Partial<PluginOptions> = {},
|
||||||
i18n: I18n = DefaultI18N,
|
i18nOptions: Partial<I18n> = {},
|
||||||
) => {
|
) => {
|
||||||
|
const i18n = {...DefaultI18N, ...i18nOptions};
|
||||||
const generatedFilesDir: string = path.resolve(siteDir, '.docusaurus');
|
const generatedFilesDir: string = path.resolve(siteDir, '.docusaurus');
|
||||||
const localizationDir = path.join(
|
const localizationDir = path.join(
|
||||||
siteDir,
|
siteDir,
|
||||||
i18n.path,
|
i18n.path,
|
||||||
i18n.localeConfigs[i18n.currentLocale]!.path,
|
getLocaleConfig(i18n).path,
|
||||||
);
|
);
|
||||||
const siteConfig = {
|
const siteConfig = {
|
||||||
title: 'Hello',
|
title: 'Hello',
|
||||||
|
|
@ -153,20 +160,34 @@ const getBlogTags = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('blog plugin', () => {
|
describe('blog plugin', () => {
|
||||||
it('getPathsToWatch returns right files', async () => {
|
describe('getPathsToWatch', () => {
|
||||||
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
async function runTest({translate}: {translate: boolean}) {
|
||||||
const plugin = await getPlugin(siteDir);
|
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
||||||
const pathsToWatch = plugin.getPathsToWatch!();
|
const plugin = await getPlugin(siteDir, {}, getI18n('en', {translate}));
|
||||||
const relativePathsToWatch = pathsToWatch.map((p) =>
|
const pathsToWatch = plugin.getPathsToWatch!();
|
||||||
posixPath(path.relative(siteDir, p)),
|
return pathsToWatch.map((p) => posixPath(path.relative(siteDir, p)));
|
||||||
);
|
}
|
||||||
expect(relativePathsToWatch).toEqual([
|
|
||||||
'i18n/en/docusaurus-plugin-content-blog/authors.yml',
|
it('getPathsToWatch returns right files', async () => {
|
||||||
'i18n/en/docusaurus-plugin-content-blog/tags.yml',
|
const relativePathsToWatch = await runTest({translate: true});
|
||||||
'blog/tags.yml',
|
expect(relativePathsToWatch).toEqual([
|
||||||
'i18n/en/docusaurus-plugin-content-blog/**/*.{md,mdx}',
|
'i18n/en/docusaurus-plugin-content-blog/authors.yml',
|
||||||
'blog/**/*.{md,mdx}',
|
'i18n/en/docusaurus-plugin-content-blog/tags.yml',
|
||||||
]);
|
// 'blog/authors.yml', // TODO weird that it's not here but tags is?
|
||||||
|
'blog/tags.yml',
|
||||||
|
'i18n/en/docusaurus-plugin-content-blog/**/*.{md,mdx}',
|
||||||
|
'blog/**/*.{md,mdx}',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getPathsToWatch returns right files (translate: false)', async () => {
|
||||||
|
const relativePathsToWatch = await runTest({translate: false});
|
||||||
|
expect(relativePathsToWatch).toEqual([
|
||||||
|
'blog/authors.yml',
|
||||||
|
'blog/tags.yml',
|
||||||
|
'blog/**/*.{md,mdx}',
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('builds a simple website', async () => {
|
it('builds a simple website', async () => {
|
||||||
|
|
@ -377,6 +398,54 @@ describe('blog plugin', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('i18n config translate is wired properly', () => {
|
||||||
|
async function runTest({translate}: {translate: boolean}) {
|
||||||
|
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
||||||
|
const blogPosts = await getBlogPosts(
|
||||||
|
siteDir,
|
||||||
|
{},
|
||||||
|
getI18n('en', {translate}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simpler to snapshot
|
||||||
|
return blogPosts.map((post) => post.metadata.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('works with translate: false', async () => {
|
||||||
|
await expect(runTest({translate: false})).resolves.toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
"test links",
|
||||||
|
"MDX Blog Sample with require calls",
|
||||||
|
"Full Blog Sample",
|
||||||
|
"Complex Slug",
|
||||||
|
"Simple Slug",
|
||||||
|
"draft",
|
||||||
|
"unlisted",
|
||||||
|
"some heading",
|
||||||
|
"date-matter",
|
||||||
|
"Happy 1st Birthday Slash!",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with translate: true', async () => {
|
||||||
|
await expect(runTest({translate: true})).resolves.toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
"test links",
|
||||||
|
"MDX Blog Sample with require calls",
|
||||||
|
"Full Blog Sample",
|
||||||
|
"Complex Slug",
|
||||||
|
"Simple Slug",
|
||||||
|
"draft",
|
||||||
|
"unlisted",
|
||||||
|
"some heading",
|
||||||
|
"date-matter",
|
||||||
|
"Happy 1st Birthday Slash! (translated)",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('handles edit URL with editLocalizedBlogs: true', async () => {
|
it('handles edit URL with editLocalizedBlogs: true', async () => {
|
||||||
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
||||||
const blogPosts = await getBlogPosts(siteDir, {editLocalizedFiles: true});
|
const blogPosts = await getBlogPosts(siteDir, {editLocalizedFiles: true});
|
||||||
|
|
@ -390,6 +459,23 @@ describe('blog plugin', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles edit URL with editLocalizedBlogs: true and translate: false', async () => {
|
||||||
|
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
||||||
|
const blogPosts = await getBlogPosts(
|
||||||
|
siteDir,
|
||||||
|
{editLocalizedFiles: true},
|
||||||
|
getI18n('en', {translate: false}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const localizedBlogPost = blogPosts.find(
|
||||||
|
(v) => v.metadata.title === 'Happy 1st Birthday Slash!',
|
||||||
|
)!;
|
||||||
|
|
||||||
|
expect(localizedBlogPost.metadata.editUrl).toBe(
|
||||||
|
`${BaseEditUrl}/blog/2018-12-14-Happy-First-Birthday-Slash.md`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('handles edit URL with editUrl function', async () => {
|
it('handles edit URL with editUrl function', async () => {
|
||||||
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
const siteDir = path.join(__dirname, '__fixtures__', 'website');
|
||||||
|
|
||||||
|
|
@ -474,9 +560,7 @@ describe('blog plugin', () => {
|
||||||
const blogPosts = await getBlogPosts(siteDir);
|
const blogPosts = await getBlogPosts(siteDir);
|
||||||
const noDateSource = path.posix.join('@site', PluginPath, 'no date.md');
|
const noDateSource = path.posix.join('@site', PluginPath, 'no date.md');
|
||||||
const noDateSourceFile = path.posix.join(siteDir, PluginPath, 'no date.md');
|
const noDateSourceFile = path.posix.join(siteDir, PluginPath, 'no date.md');
|
||||||
// We know the file exists and we know we have git
|
const noDateSourceTime = await getFileCreationDate(noDateSourceFile);
|
||||||
const result = await getFileCommitDate(noDateSourceFile, {age: 'oldest'});
|
|
||||||
const noDateSourceTime = result.date;
|
|
||||||
|
|
||||||
expect({
|
expect({
|
||||||
...getByTitle(blogPosts, 'no date').metadata,
|
...getByTitle(blogPosts, 'no date').metadata,
|
||||||
|
|
@ -554,10 +638,7 @@ describe('blog plugin', () => {
|
||||||
},
|
},
|
||||||
DefaultI18N,
|
DefaultI18N,
|
||||||
);
|
);
|
||||||
const {blogPosts, blogTags, blogListPaginated} =
|
const {blogPosts, blogTags} = (await plugin.loadContent!())!;
|
||||||
(await plugin.loadContent!())!;
|
|
||||||
|
|
||||||
expect(blogListPaginated).toHaveLength(3);
|
|
||||||
|
|
||||||
expect(Object.keys(blogTags)).toHaveLength(2);
|
expect(Object.keys(blogTags)).toHaveLength(2);
|
||||||
expect(blogTags).toMatchSnapshot();
|
expect(blogTags).toMatchSnapshot();
|
||||||
|
|
@ -587,29 +668,23 @@ describe('last update', () => {
|
||||||
);
|
);
|
||||||
const {blogPosts} = (await plugin.loadContent!())!;
|
const {blogPosts} = (await plugin.loadContent!())!;
|
||||||
|
|
||||||
|
const TestLastUpdate = await TEST_VCS.getFileLastUpdateInfo('any path');
|
||||||
|
|
||||||
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb');
|
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb');
|
||||||
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe(
|
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe(
|
||||||
LAST_UPDATE_FALLBACK.lastUpdatedAt,
|
lastUpdateFor('2021-01-01'),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(
|
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(TestLastUpdate.author);
|
||||||
LAST_UPDATE_FALLBACK.lastUpdatedBy,
|
|
||||||
);
|
|
||||||
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe(
|
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe(
|
||||||
LAST_UPDATE_FALLBACK.lastUpdatedAt,
|
lastUpdateFor('2021-01-01'),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb');
|
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb');
|
||||||
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(
|
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(TestLastUpdate.timestamp);
|
||||||
lastUpdateFor('2021-01-01'),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(
|
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(TestLastUpdate.author);
|
||||||
LAST_UPDATE_FALLBACK.lastUpdatedBy,
|
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(TestLastUpdate.timestamp);
|
||||||
);
|
|
||||||
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(
|
|
||||||
lastUpdateFor('2021-01-01'),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('time only', async () => {
|
it('time only', async () => {
|
||||||
|
|
@ -623,29 +698,27 @@ describe('last update', () => {
|
||||||
);
|
);
|
||||||
const {blogPosts} = (await plugin.loadContent!())!;
|
const {blogPosts} = (await plugin.loadContent!())!;
|
||||||
|
|
||||||
expect(blogPosts[0]?.metadata.title).toBe('Author');
|
const TestLastUpdate = await TEST_VCS.getFileLastUpdateInfo('any path');
|
||||||
|
|
||||||
|
expect(blogPosts[0]?.metadata.title).toBe('Both');
|
||||||
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBeUndefined();
|
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBeUndefined();
|
||||||
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe(
|
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe(
|
||||||
LAST_UPDATE_FALLBACK.lastUpdatedAt,
|
lastUpdateFor('2021-01-01'),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(blogPosts[1]?.metadata.title).toBe('Nothing');
|
expect(blogPosts[1]?.metadata.title).toBe('Last update date');
|
||||||
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBeUndefined();
|
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBeUndefined();
|
||||||
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe(
|
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe(
|
||||||
LAST_UPDATE_FALLBACK.lastUpdatedAt,
|
lastUpdateFor('2021-01-01'),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(blogPosts[2]?.metadata.title).toBe('Both');
|
expect(blogPosts[2]?.metadata.title).toBe('Author');
|
||||||
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBeUndefined();
|
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBeUndefined();
|
||||||
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(
|
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(TestLastUpdate.timestamp);
|
||||||
lastUpdateFor('2021-01-01'),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(blogPosts[3]?.metadata.title).toBe('Last update date');
|
expect(blogPosts[3]?.metadata.title).toBe('Nothing');
|
||||||
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBeUndefined();
|
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBeUndefined();
|
||||||
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(
|
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(TestLastUpdate.timestamp);
|
||||||
lastUpdateFor('2021-01-01'),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('author only', async () => {
|
it('author only', async () => {
|
||||||
|
|
@ -659,20 +732,18 @@ describe('last update', () => {
|
||||||
);
|
);
|
||||||
const {blogPosts} = (await plugin.loadContent!())!;
|
const {blogPosts} = (await plugin.loadContent!())!;
|
||||||
|
|
||||||
|
const TestLastUpdate = await TEST_VCS.getFileLastUpdateInfo('any path');
|
||||||
|
|
||||||
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb');
|
expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb');
|
||||||
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBeUndefined();
|
expect(blogPosts[0]?.metadata.lastUpdatedAt).toBeUndefined();
|
||||||
|
|
||||||
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(
|
expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(TestLastUpdate.author);
|
||||||
LAST_UPDATE_FALLBACK.lastUpdatedBy,
|
|
||||||
);
|
|
||||||
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBeUndefined();
|
expect(blogPosts[1]?.metadata.lastUpdatedAt).toBeUndefined();
|
||||||
|
|
||||||
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb');
|
expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb');
|
||||||
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBeUndefined();
|
expect(blogPosts[2]?.metadata.lastUpdatedAt).toBeUndefined();
|
||||||
|
|
||||||
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(
|
expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(TestLastUpdate.author);
|
||||||
LAST_UPDATE_FALLBACK.lastUpdatedBy,
|
|
||||||
);
|
|
||||||
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBeUndefined();
|
expect(blogPosts[3]?.metadata.lastUpdatedAt).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,324 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import {fromPartial} from '@total-typescript/shoehorn';
|
||||||
|
import {buildAllRoutes} from '../routes';
|
||||||
|
import {DEFAULT_OPTIONS} from '../options';
|
||||||
|
import type {PartialDeep} from '@total-typescript/shoehorn';
|
||||||
|
import type {BlogPost, BlogPostMetadata} from '@docusaurus/plugin-content-blog';
|
||||||
|
|
||||||
|
type Params = Parameters<typeof buildAllRoutes>[0];
|
||||||
|
|
||||||
|
async function testBuildAllRoutes(overrides: PartialDeep<Params> = {}) {
|
||||||
|
const createData = jest.fn(
|
||||||
|
async (name: string, _data: unknown) => `/data/${name}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const params: Params = fromPartial<Params>({
|
||||||
|
baseUrl: '/',
|
||||||
|
aliasedSource: (str: string) => `@aliased${str}`,
|
||||||
|
...overrides,
|
||||||
|
|
||||||
|
content: {
|
||||||
|
blogTitle: 'Blog Title',
|
||||||
|
blogDescription: 'Blog Description',
|
||||||
|
blogSidebarTitle: 'Blog Sidebar Title',
|
||||||
|
authorsMap: {},
|
||||||
|
blogTagsListPath: '',
|
||||||
|
blogTags: {},
|
||||||
|
blogPosts: [],
|
||||||
|
...overrides?.content,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
...DEFAULT_OPTIONS,
|
||||||
|
...overrides?.options,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
createData,
|
||||||
|
...overrides?.actions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const routes = await buildAllRoutes(params);
|
||||||
|
|
||||||
|
const data = Object.fromEntries(
|
||||||
|
createData.mock.calls.map((call) => [call[0], call[1]]),
|
||||||
|
);
|
||||||
|
|
||||||
|
function getRouteByPath(path: string) {
|
||||||
|
const route = routes.find((r) => r.path === path);
|
||||||
|
if (!route) {
|
||||||
|
throw new Error(`Route not found for path: ${path}`);
|
||||||
|
}
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRoutesByComponent(component: string) {
|
||||||
|
return routes.filter((r) => r.component === component);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {routes, data, utils: {getRouteByPath, getRoutesByComponent}};
|
||||||
|
}
|
||||||
|
|
||||||
|
function blogPost(overrides: PartialDeep<BlogPost> = {}): BlogPost {
|
||||||
|
const id = overrides.id ?? 'blog-post';
|
||||||
|
return fromPartial<BlogPost>({
|
||||||
|
id,
|
||||||
|
content: `Content for ${id}`,
|
||||||
|
...overrides,
|
||||||
|
metadata: fromPartial<BlogPostMetadata>({
|
||||||
|
title: `Title for ${id}`,
|
||||||
|
description: `Description for ${id}`,
|
||||||
|
permalink: `/blog/${id}`,
|
||||||
|
source: `@site/blog/${id}.md`,
|
||||||
|
date: new Date('2020-01-01'),
|
||||||
|
tags: [],
|
||||||
|
readingTime: 2,
|
||||||
|
authors: [],
|
||||||
|
frontMatter: {
|
||||||
|
...overrides?.metadata?.frontMatter,
|
||||||
|
},
|
||||||
|
...overrides?.metadata,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('buildAllRoutes', () => {
|
||||||
|
it('works for empty blog', async () => {
|
||||||
|
const {routes, data} = await testBuildAllRoutes({
|
||||||
|
content: {
|
||||||
|
blogPosts: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(routes).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogListPage",
|
||||||
|
"exact": true,
|
||||||
|
"modules": {
|
||||||
|
"items": [],
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog",
|
||||||
|
"props": {
|
||||||
|
"metadata": {
|
||||||
|
"blogDescription": "Blog Description",
|
||||||
|
"blogTitle": "Blog Title",
|
||||||
|
"nextPage": undefined,
|
||||||
|
"page": 1,
|
||||||
|
"permalink": "/blog",
|
||||||
|
"postsPerPage": 10,
|
||||||
|
"previousPage": undefined,
|
||||||
|
"totalCount": 0,
|
||||||
|
"totalPages": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
expect(data).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"blog-post-list-prop-default.json": {
|
||||||
|
"items": [],
|
||||||
|
"title": "Blog Sidebar Title",
|
||||||
|
},
|
||||||
|
"blogMetadata-default.json": {
|
||||||
|
"authorsListPath": "/blog/authors",
|
||||||
|
"blogBasePath": "/blog",
|
||||||
|
"blogTitle": "Blog Title",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works for single blog post', async () => {
|
||||||
|
const {routes, data} = await testBuildAllRoutes({
|
||||||
|
content: {
|
||||||
|
blogPosts: [blogPost()],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(routes).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogPostPage",
|
||||||
|
"context": {
|
||||||
|
"blogMetadata": "@aliased/data/blogMetadata-default.json",
|
||||||
|
},
|
||||||
|
"exact": true,
|
||||||
|
"metadata": {
|
||||||
|
"lastUpdatedAt": undefined,
|
||||||
|
"sourceFilePath": "blog/blog-post.md",
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"content": "@site/blog/blog-post.md",
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog/blog-post",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogListPage",
|
||||||
|
"exact": true,
|
||||||
|
"modules": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"__import": true,
|
||||||
|
"path": "@site/blog/blog-post.md",
|
||||||
|
"query": {
|
||||||
|
"truncated": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"sidebar": "@aliased/data/blog-post-list-prop-default.json",
|
||||||
|
},
|
||||||
|
"path": "/blog",
|
||||||
|
"props": {
|
||||||
|
"metadata": {
|
||||||
|
"blogDescription": "Blog Description",
|
||||||
|
"blogTitle": "Blog Title",
|
||||||
|
"nextPage": undefined,
|
||||||
|
"page": 1,
|
||||||
|
"permalink": "/blog",
|
||||||
|
"postsPerPage": 10,
|
||||||
|
"previousPage": undefined,
|
||||||
|
"totalCount": 1,
|
||||||
|
"totalPages": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "@theme/BlogArchivePage",
|
||||||
|
"exact": true,
|
||||||
|
"path": "/blog/archive",
|
||||||
|
"props": {
|
||||||
|
"archive": {
|
||||||
|
"blogPosts": [
|
||||||
|
{
|
||||||
|
"content": "Content for blog-post",
|
||||||
|
"id": "blog-post",
|
||||||
|
"metadata": {
|
||||||
|
"authors": [],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for blog-post",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/blog-post",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/blog-post.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for blog-post",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
expect(data).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"blog-post-list-prop-default.json": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"permalink": "/blog/blog-post",
|
||||||
|
"title": "Title for blog-post",
|
||||||
|
"unlisted": undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"title": "Blog Sidebar Title",
|
||||||
|
},
|
||||||
|
"blogMetadata-default.json": {
|
||||||
|
"authorsListPath": "/blog/authors",
|
||||||
|
"blogBasePath": "/blog",
|
||||||
|
"blogTitle": "Blog Title",
|
||||||
|
},
|
||||||
|
"site-blog-blog-post-md-0d7.json": {
|
||||||
|
"authors": [],
|
||||||
|
"date": 2020-01-01T00:00:00.000Z,
|
||||||
|
"description": "Description for blog-post",
|
||||||
|
"frontMatter": {},
|
||||||
|
"permalink": "/blog/blog-post",
|
||||||
|
"readingTime": 2,
|
||||||
|
"source": "@site/blog/blog-post.md",
|
||||||
|
"tags": [],
|
||||||
|
"title": "Title for blog-post",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works for realistic blog post', async () => {
|
||||||
|
const {routes, data} = await testBuildAllRoutes({
|
||||||
|
options: {
|
||||||
|
postsPerPage: 2,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
blogTitle: 'Custom blog title',
|
||||||
|
blogDescription: 'Custom blog description',
|
||||||
|
blogSidebarTitle: 'Custom blog sidebar title',
|
||||||
|
|
||||||
|
blogPosts: [
|
||||||
|
blogPost({id: 'post1', metadata: {authors: [{key: 'author1'}]}}),
|
||||||
|
blogPost({id: 'post2', metadata: {authors: [{key: 'author1'}]}}),
|
||||||
|
blogPost({
|
||||||
|
id: 'post3',
|
||||||
|
metadata: {
|
||||||
|
authors: [{key: 'author3'}],
|
||||||
|
unlisted: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
blogPost({
|
||||||
|
id: 'post4',
|
||||||
|
metadata: {
|
||||||
|
authors: [{key: 'author1'}, {key: 'author2'}],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
blogPost({
|
||||||
|
id: 'post5',
|
||||||
|
metadata: {authors: [{key: 'author2'}, {key: 'author3'}]},
|
||||||
|
}),
|
||||||
|
blogPost({id: 'post6'}),
|
||||||
|
],
|
||||||
|
|
||||||
|
authorsMap: {
|
||||||
|
author1: {
|
||||||
|
key: 'author1',
|
||||||
|
name: 'Author 1',
|
||||||
|
page: {permalink: '/blog/authors/author1'},
|
||||||
|
},
|
||||||
|
author2: {
|
||||||
|
key: 'author2',
|
||||||
|
name: 'Author 2',
|
||||||
|
page: null,
|
||||||
|
},
|
||||||
|
author3: {
|
||||||
|
key: 'author3',
|
||||||
|
name: 'Author 3',
|
||||||
|
page: {permalink: '/blog/authors/author3'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(_.countBy(routes, 'component')).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"@theme/Blog/Pages/BlogAuthorsListPage": 1,
|
||||||
|
"@theme/Blog/Pages/BlogAuthorsPostsPage": 3,
|
||||||
|
"@theme/BlogArchivePage": 1,
|
||||||
|
"@theme/BlogListPage": 3,
|
||||||
|
"@theme/BlogPostPage": 6,
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(routes).toMatchSnapshot();
|
||||||
|
expect(data).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {updateTranslationFileMessages} from '@docusaurus/utils';
|
import {updateTranslationFileMessages} from '@docusaurus/utils';
|
||||||
|
import {fromPartial} from '@total-typescript/shoehorn';
|
||||||
import {getTranslationFiles, translateContent} from '../translations';
|
import {getTranslationFiles, translateContent} from '../translations';
|
||||||
import {DEFAULT_OPTIONS} from '../options';
|
import {DEFAULT_OPTIONS} from '../options';
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -16,13 +17,13 @@ import type {
|
||||||
|
|
||||||
const sampleBlogOptions: PluginOptions = {
|
const sampleBlogOptions: PluginOptions = {
|
||||||
...DEFAULT_OPTIONS,
|
...DEFAULT_OPTIONS,
|
||||||
blogSidebarTitle: 'All my posts',
|
|
||||||
blogTitle: 'My blog',
|
blogTitle: 'My blog',
|
||||||
blogDescription: "Someone's random blog",
|
blogDescription: "Someone's random blog",
|
||||||
|
blogSidebarTitle: 'All my posts',
|
||||||
};
|
};
|
||||||
|
|
||||||
const sampleBlogPosts: BlogPost[] = [
|
const sampleBlogPosts: BlogPost[] = [
|
||||||
{
|
fromPartial({
|
||||||
id: 'hello',
|
id: 'hello',
|
||||||
metadata: {
|
metadata: {
|
||||||
permalink: '/blog/2021/06/19/hello',
|
permalink: '/blog/2021/06/19/hello',
|
||||||
|
|
@ -37,27 +38,13 @@ const sampleBlogPosts: BlogPost[] = [
|
||||||
unlisted: false,
|
unlisted: false,
|
||||||
},
|
},
|
||||||
content: '',
|
content: '',
|
||||||
},
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
const sampleBlogContent: BlogContent = {
|
const sampleBlogContent: BlogContent = {
|
||||||
|
blogTitle: sampleBlogOptions.blogTitle,
|
||||||
|
blogDescription: sampleBlogOptions.blogDescription,
|
||||||
blogSidebarTitle: sampleBlogOptions.blogSidebarTitle,
|
blogSidebarTitle: sampleBlogOptions.blogSidebarTitle,
|
||||||
blogListPaginated: [
|
|
||||||
{
|
|
||||||
items: ['hello'],
|
|
||||||
metadata: {
|
|
||||||
permalink: '/',
|
|
||||||
page: 1,
|
|
||||||
postsPerPage: 10,
|
|
||||||
totalPages: 1,
|
|
||||||
totalCount: 1,
|
|
||||||
previousPage: undefined,
|
|
||||||
nextPage: undefined,
|
|
||||||
blogTitle: sampleBlogOptions.blogTitle,
|
|
||||||
blogDescription: sampleBlogOptions.blogDescription,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
blogPosts: sampleBlogPosts,
|
blogPosts: sampleBlogPosts,
|
||||||
blogTags: {},
|
blogTags: {},
|
||||||
blogTagsListPath: '/tags',
|
blogTagsListPath: '/tags',
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ export const AuthorSocialsSchema = Joi.object<AuthorSocials>({
|
||||||
mastodon: Joi.string(),
|
mastodon: Joi.string(),
|
||||||
twitch: Joi.string(),
|
twitch: Joi.string(),
|
||||||
youtube: Joi.string(),
|
youtube: Joi.string(),
|
||||||
|
email: Joi.string(),
|
||||||
}).unknown();
|
}).unknown();
|
||||||
|
|
||||||
type PredefinedPlatformNormalizer = (value: string) => string;
|
type PredefinedPlatformNormalizer = (value: string) => string;
|
||||||
|
|
@ -47,12 +48,12 @@ const PredefinedPlatformNormalizers: Record<
|
||||||
mastodon: (handle: string) => `https://mastodon.social/@${handle}`, // can be in format user@other.server and it will redirect if needed
|
mastodon: (handle: string) => `https://mastodon.social/@${handle}`, // can be in format user@other.server and it will redirect if needed
|
||||||
twitch: (handle: string) => `https://twitch.tv/${handle}`,
|
twitch: (handle: string) => `https://twitch.tv/${handle}`,
|
||||||
youtube: (handle: string) => `https://youtube.com/@${handle}`, // https://support.google.com/youtube/answer/6180214?hl=en
|
youtube: (handle: string) => `https://youtube.com/@${handle}`, // https://support.google.com/youtube/answer/6180214?hl=en
|
||||||
|
email: (email: string) => `mailto:${email}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
type SocialEntry = [string, string];
|
type SocialEntry = [string, string];
|
||||||
|
|
||||||
function normalizeSocialEntry([platform, value]: SocialEntry): SocialEntry {
|
function normalizeSocialEntry([platform, value]: SocialEntry): SocialEntry {
|
||||||
const normalizer = PredefinedPlatformNormalizers[platform.toLowerCase()];
|
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Author socials should be usernames/userIds/handles, or fully qualified HTTP(s) absolute URLs.
|
`Author socials should be usernames/userIds/handles, or fully qualified HTTP(s) absolute URLs.
|
||||||
|
|
@ -60,7 +61,9 @@ Social platform '${platform}' has illegal value '${value}'`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const isAbsoluteUrl =
|
const isAbsoluteUrl =
|
||||||
value.startsWith('http://') || value.startsWith('https://');
|
value.startsWith('http://') ||
|
||||||
|
value.startsWith('https://') ||
|
||||||
|
value.startsWith('mailto:');
|
||||||
if (isAbsoluteUrl) {
|
if (isAbsoluteUrl) {
|
||||||
return [platform, value];
|
return [platform, value];
|
||||||
} else if (value.includes('/')) {
|
} else if (value.includes('/')) {
|
||||||
|
|
@ -69,6 +72,7 @@ Social platform '${platform}' has illegal value '${value}'`,
|
||||||
Social platform '${platform}' has illegal value '${value}'`,
|
Social platform '${platform}' has illegal value '${value}'`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const normalizer = PredefinedPlatformNormalizers[platform.toLowerCase()];
|
||||||
if (normalizer && !isAbsoluteUrl) {
|
if (normalizer && !isAbsoluteUrl) {
|
||||||
const normalizedPlatform = platform.toLowerCase();
|
const normalizedPlatform = platform.toLowerCase();
|
||||||
const normalizedValue = normalizer(value);
|
const normalizedValue = normalizer(value);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import {
|
||||||
Globby,
|
Globby,
|
||||||
groupTaggedItems,
|
groupTaggedItems,
|
||||||
getTagVisibility,
|
getTagVisibility,
|
||||||
getFileCommitDate,
|
|
||||||
getContentPathList,
|
getContentPathList,
|
||||||
isUnlisted,
|
isUnlisted,
|
||||||
isDraft,
|
isDraft,
|
||||||
|
|
@ -225,6 +224,7 @@ async function processBlogSourceFile(
|
||||||
siteConfig: {
|
siteConfig: {
|
||||||
baseUrl,
|
baseUrl,
|
||||||
markdown: {parseFrontMatter},
|
markdown: {parseFrontMatter},
|
||||||
|
future: {experimental_vcs: vcs},
|
||||||
},
|
},
|
||||||
siteDir,
|
siteDir,
|
||||||
i18n,
|
i18n,
|
||||||
|
|
@ -257,6 +257,7 @@ async function processBlogSourceFile(
|
||||||
blogSourceAbsolute,
|
blogSourceAbsolute,
|
||||||
options,
|
options,
|
||||||
frontMatter.last_update,
|
frontMatter.last_update,
|
||||||
|
vcs,
|
||||||
);
|
);
|
||||||
|
|
||||||
const draft = isDraft({frontMatter});
|
const draft = isDraft({frontMatter});
|
||||||
|
|
@ -285,17 +286,11 @@ async function processBlogSourceFile(
|
||||||
return parsedBlogFileName.date;
|
return parsedBlogFileName.date;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const result = await vcs.getFileCreationInfo(blogSourceAbsolute);
|
||||||
const result = await getFileCommitDate(blogSourceAbsolute, {
|
if (result == null) {
|
||||||
age: 'oldest',
|
|
||||||
includeAuthor: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return result.date;
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn(err);
|
|
||||||
return (await fs.stat(blogSourceAbsolute)).birthtime;
|
return (await fs.stat(blogSourceAbsolute)).birthtime;
|
||||||
}
|
}
|
||||||
|
return new Date(result.timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
const date = await getDate();
|
const date = await getDate();
|
||||||
|
|
@ -323,7 +318,9 @@ async function processBlogSourceFile(
|
||||||
} else if (typeof editUrl === 'string') {
|
} else if (typeof editUrl === 'string') {
|
||||||
const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
|
const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
|
||||||
const fileContentPath =
|
const fileContentPath =
|
||||||
isLocalized && options.editLocalizedFiles
|
isLocalized &&
|
||||||
|
options.editLocalizedFiles &&
|
||||||
|
contentPaths.contentPathLocalized
|
||||||
? contentPaths.contentPathLocalized
|
? contentPaths.contentPathLocalized
|
||||||
: contentPaths.contentPath;
|
: contentPaths.contentPath;
|
||||||
|
|
||||||
|
|
@ -404,6 +401,8 @@ export async function generateBlogPosts(
|
||||||
ignore: exclude,
|
ignore: exclude,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO this should be done outside of this function
|
||||||
|
// directly in plugin loadContent()
|
||||||
const tagsFile = await getTagsFile({contentPaths, tags: options.tags});
|
const tagsFile = await getTagsFile({contentPaths, tags: options.tags});
|
||||||
|
|
||||||
async function doProcessBlogSourceFile(blogSourceFile: string) {
|
async function doProcessBlogSourceFile(blogSourceFile: string) {
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,13 @@ import {
|
||||||
createAbsoluteFilePathMatcher,
|
createAbsoluteFilePathMatcher,
|
||||||
getContentPathList,
|
getContentPathList,
|
||||||
getDataFilePath,
|
getDataFilePath,
|
||||||
DEFAULT_PLUGIN_ID,
|
|
||||||
resolveMarkdownLinkPathname,
|
resolveMarkdownLinkPathname,
|
||||||
|
getLocaleConfig,
|
||||||
} from '@docusaurus/utils';
|
} from '@docusaurus/utils';
|
||||||
import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation';
|
import {getTagsFilePathsToWatch} from '@docusaurus/utils-validation';
|
||||||
import {createMDXLoaderItem} from '@docusaurus/mdx-loader';
|
import {createMDXLoaderItem} from '@docusaurus/mdx-loader';
|
||||||
import {
|
import {
|
||||||
getBlogTags,
|
getBlogTags,
|
||||||
paginateBlogPosts,
|
|
||||||
shouldBeListed,
|
shouldBeListed,
|
||||||
applyProcessBlogPosts,
|
applyProcessBlogPosts,
|
||||||
generateBlogPosts,
|
generateBlogPosts,
|
||||||
|
|
@ -44,7 +43,6 @@ import type {
|
||||||
Assets,
|
Assets,
|
||||||
BlogTags,
|
BlogTags,
|
||||||
BlogContent,
|
BlogContent,
|
||||||
BlogPaginated,
|
|
||||||
} from '@docusaurus/plugin-content-blog';
|
} from '@docusaurus/plugin-content-blog';
|
||||||
import type {RuleSetRule, RuleSetUseItem} from 'webpack';
|
import type {RuleSetRule, RuleSetUseItem} from 'webpack';
|
||||||
|
|
||||||
|
|
@ -71,17 +69,20 @@ export default async function pluginContentBlog(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {onBrokenMarkdownLinks, baseUrl} = siteConfig;
|
const {baseUrl} = siteConfig;
|
||||||
|
|
||||||
|
const shouldTranslate = getLocaleConfig(context.i18n).translate;
|
||||||
const contentPaths: BlogContentPaths = {
|
const contentPaths: BlogContentPaths = {
|
||||||
contentPath: path.resolve(siteDir, options.path),
|
contentPath: path.resolve(siteDir, options.path),
|
||||||
contentPathLocalized: getPluginI18nPath({
|
contentPathLocalized: shouldTranslate
|
||||||
localizationDir,
|
? getPluginI18nPath({
|
||||||
pluginName: PluginName,
|
localizationDir,
|
||||||
pluginId: options.id,
|
pluginName: PluginName,
|
||||||
}),
|
pluginId: options.id,
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
const pluginId = options.id ?? DEFAULT_PLUGIN_ID;
|
const pluginId = options.id;
|
||||||
|
|
||||||
const pluginDataDirRoot = path.join(generatedFilesDir, PluginName);
|
const pluginDataDirRoot = path.join(generatedFilesDir, PluginName);
|
||||||
const dataDir = path.join(pluginDataDirRoot, pluginId);
|
const dataDir = path.join(pluginDataDirRoot, pluginId);
|
||||||
|
|
@ -154,18 +155,12 @@ export default async function pluginContentBlog(
|
||||||
},
|
},
|
||||||
markdownConfig: siteConfig.markdown,
|
markdownConfig: siteConfig.markdown,
|
||||||
resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
|
resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
|
||||||
const permalink = resolveMarkdownLinkPathname(linkPathname, {
|
return resolveMarkdownLinkPathname(linkPathname, {
|
||||||
sourceFilePath,
|
sourceFilePath,
|
||||||
sourceToPermalink: contentHelpers.sourceToPermalink,
|
sourceToPermalink: contentHelpers.sourceToPermalink,
|
||||||
siteDir,
|
siteDir,
|
||||||
contentPaths,
|
contentPaths,
|
||||||
});
|
});
|
||||||
if (permalink === null) {
|
|
||||||
logger.report(
|
|
||||||
onBrokenMarkdownLinks,
|
|
||||||
)`Blog markdown link couldn't be resolved: (url=${linkPathname}) in source file path=${sourceFilePath}`;
|
|
||||||
}
|
|
||||||
return permalink;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -262,9 +257,10 @@ export default async function pluginContentBlog(
|
||||||
|
|
||||||
if (!blogPosts.length) {
|
if (!blogPosts.length) {
|
||||||
return {
|
return {
|
||||||
|
blogTitle,
|
||||||
|
blogDescription,
|
||||||
blogSidebarTitle,
|
blogSidebarTitle,
|
||||||
blogPosts: [],
|
blogPosts: [],
|
||||||
blogListPaginated: [],
|
|
||||||
blogTags: {},
|
blogTags: {},
|
||||||
blogTagsListPath,
|
blogTagsListPath,
|
||||||
authorsMap,
|
authorsMap,
|
||||||
|
|
@ -293,15 +289,9 @@ export default async function pluginContentBlog(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const blogListPaginated: BlogPaginated[] = paginateBlogPosts({
|
// TODO this is not the correct place to aggregate and paginate tags
|
||||||
blogPosts: listedBlogPosts,
|
// for reasons similar to https://github.com/facebook/docusaurus/pull/11562
|
||||||
blogTitle,
|
// What we should do here is only read the tags file (similar to authors)
|
||||||
blogDescription,
|
|
||||||
postsPerPageOption,
|
|
||||||
basePageUrl: baseBlogUrl,
|
|
||||||
pageBasePath,
|
|
||||||
});
|
|
||||||
|
|
||||||
const blogTags: BlogTags = getBlogTags({
|
const blogTags: BlogTags = getBlogTags({
|
||||||
blogPosts,
|
blogPosts,
|
||||||
postsPerPageOption,
|
postsPerPageOption,
|
||||||
|
|
@ -311,9 +301,10 @@ export default async function pluginContentBlog(
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
blogTitle,
|
||||||
|
blogDescription,
|
||||||
blogSidebarTitle,
|
blogSidebarTitle,
|
||||||
blogPosts,
|
blogPosts,
|
||||||
blogListPaginated,
|
|
||||||
blogTags,
|
blogTags,
|
||||||
blogTagsListPath,
|
blogTagsListPath,
|
||||||
authorsMap,
|
authorsMap,
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue