Merge remote-tracking branch 'origin/main' into regions

Resolves conflicts in the snippet plugin and processIncludes:
- snippet.ts: keep main's structure (parser/renderer factories,
  getFileOrError) with this branch's semantics on top (equivalent
  tightened region markers, multi-region findRegions, error on
  missing region, optional stripMarkersFromSnippets, token.meta for
  passing the snippet source). main's rawPathRegexp export,
  SnippetToken, findRegion, and extractRegion are superseded.
- processIncludes.ts: keep this branch's frontmatter-before-regions
  logic on main's async readFile + circular-include infrastructure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/5014/head
Divyansh Singh 2 days ago
commit e74393fbe5

@ -39,7 +39,7 @@ afterAll(async () => {
test.each(variations)('init %s', async (_, { theme, useTs }) => {
const root = getTempRoot()
await rm(root, { recursive: true, force: true })
scaffold({ root, theme, useTs, injectNpmScripts: false })
await scaffold({ root, theme, useTs, injectNpmScripts: false })
const port = await getPort()
const server = await createServer(root, { port })

@ -0,0 +1,54 @@
import { createMarkdownItAsync } from 'markdown-it-async'
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { processIncludes } from 'node/utils/processIncludes'
describe('node/utils/processIncludes', () => {
let root: string
beforeEach(async () => {
root = await mkdtemp(path.join(tmpdir(), 'vitepress-includes-'))
})
afterEach(async () => {
await rm(root, { recursive: true, force: true })
})
async function write(name: string, src: string) {
await writeFile(path.join(root, name), src)
}
async function run(name: string) {
const file = path.join(root, name)
const src = await readFile(file, 'utf8')
return processIncludes(createMarkdownItAsync(), root, src, file, [], false)
}
test('leaves a self-include unexpanded', async () => {
await write('a.md', '# A\n\n<!-- @include: ./a.md -->\n')
expect(await run('a.md')).toContain('<!-- @include: ./a.md -->')
})
test('leaves circular includes unexpanded', async () => {
await write('a.md', 'A-content\n\n<!-- @include: ./b.md -->\n')
await write('b.md', 'B-content\n\n<!-- @include: ./a.md -->\n')
const result = await run('a.md')
expect(result).toContain('B-content')
expect(result).toContain('<!-- @include: ./a.md -->')
})
test('expands repeated includes outside the ancestor chain', async () => {
await write(
'a.md',
'<!-- @include: ./b.md -->\n<!-- @include: ./c.md -->\n'
)
await write('b.md', 'B-content\n\n<!-- @include: ./d.md -->\n')
await write('c.md', 'C-content\n\n<!-- @include: ./d.md -->\n')
await write('d.md', 'D-content\n')
expect((await run('a.md')).match(/D-content/g)).toHaveLength(2)
})
})

@ -15,7 +15,7 @@
"open-cli": "^9.0.0",
"postcss-rtlcss": "^6.0.0",
"vitepress": "workspace:*",
"vitepress-plugin-group-icons": "^1.7.5",
"vitepress-plugin-group-icons": "^1.7.6",
"vitepress-plugin-llms": "^1.13.4"
}
}

@ -102,9 +102,9 @@
"@docsearch/js": "^4.7.0",
"@docsearch/sidepanel-js": "^4.7.0",
"@iconify-json/simple-icons": "^1.2.92",
"@shikijs/core": "^4.3.1",
"@shikijs/transformers": "^4.3.1",
"@shikijs/types": "^4.3.1",
"@shikijs/core": "^4.4.1",
"@shikijs/transformers": "^4.4.1",
"@shikijs/types": "^4.4.1",
"@types/markdown-it": "^14.1.2",
"@vitejs/plugin-vue": "^6.0.8",
"@vue/devtools-api": "^8.2.1",
@ -114,8 +114,8 @@
"focus-trap": "^8.2.2",
"mark.js": "8.11.1",
"minisearch": "^7.2.0",
"shiki": "^4.3.1",
"vite": "^8.1.5",
"shiki": "^4.4.1",
"vite": "^8.2.0",
"vue": "^3.5.40"
},
"devDependencies": {
@ -128,8 +128,8 @@
"@mdit-vue/plugin-title": "^3.0.2",
"@mdit-vue/plugin-toc": "^3.0.2",
"@mdit-vue/shared": "^3.0.2",
"@mdit/plugin-anchor": "^1.1.2",
"@mdit/plugin-attrs": "^1.1.0",
"@mdit/plugin-anchor": "^1.1.3",
"@mdit/plugin-attrs": "^1.1.1",
"@mdit/plugin-container": "^1.0.2",
"@mdit/plugin-emoji": "^1.1.1",
"@mdit/plugin-footnote": "^1.0.2",
@ -156,7 +156,7 @@
"get-port": "^7.2.0",
"gray-matter": "^4.0.3",
"image-size": "^2.0.2",
"lint-staged": "^17.2.0",
"lint-staged": "^17.3.0",
"lodash.template": "^4.18.1",
"lru-cache": "^11.5.2",
"markdown-it": "^14.3.0",
@ -172,7 +172,7 @@
"path-to-regexp": "^6.3.0",
"picocolors": "^1.1.1",
"picomatch": "^4.0.5",
"playwright-chromium": "^1.62.0",
"playwright-chromium": "^1.62.1",
"polka": "^1.0.0-next.28",
"postcss": "^8.5.6",
"postcss-selector-parser": "^7.1.4",
@ -188,7 +188,7 @@
"tinyglobby": "^0.2.17",
"typescript": "^5.9.3",
"vitest": "^4.1.10",
"vue-tsc": "^3.3.8",
"vue-tsc": "^3.3.9",
"wait-on": "^9.1.0"
},
"peerDependencies": {
@ -203,5 +203,5 @@
"optional": true
}
},
"packageManager": "pnpm@11.17.0"
"packageManager": "pnpm@11.18.0"
}

@ -21,20 +21,20 @@ importers:
specifier: ^1.2.92
version: 1.2.92
'@shikijs/core':
specifier: ^4.3.1
version: 4.3.1
specifier: ^4.4.1
version: 4.4.1
'@shikijs/transformers':
specifier: ^4.3.1
version: 4.3.1
specifier: ^4.4.1
version: 4.4.1
'@shikijs/types':
specifier: ^4.3.1
version: 4.3.1
specifier: ^4.4.1
version: 4.4.1
'@types/markdown-it':
specifier: ^14.1.2
version: 14.1.2
'@vitejs/plugin-vue':
specifier: ^6.0.8
version: 6.0.8(vite@8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3))
version: 6.0.8(vite@8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3))
'@vue/devtools-api':
specifier: ^8.2.1
version: 8.2.1
@ -46,7 +46,7 @@ importers:
version: 14.4.0(vue@3.5.40(typescript@5.9.3))
'@vueuse/integrations':
specifier: ^14.4.0
version: 14.4.0(axios@1.18.1(debug@4.4.3))(focus-trap@8.2.2)(vue@3.5.40(typescript@5.9.3))
version: 14.4.0(axios@1.19.0(debug@4.4.3))(focus-trap@8.2.2)(vue@3.5.40(typescript@5.9.3))
focus-trap:
specifier: ^8.2.2
version: 8.2.2
@ -57,11 +57,11 @@ importers:
specifier: ^7.2.0
version: 7.2.0
shiki:
specifier: ^4.3.1
version: 4.3.1
specifier: ^4.4.1
version: 4.4.1
vite:
specifier: ^8.1.5
version: 8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)
specifier: ^8.2.0
version: 8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)
vue:
specifier: ^3.5.40
version: 3.5.40(typescript@5.9.3)
@ -94,11 +94,11 @@ importers:
specifier: ^3.0.2
version: 3.0.2
'@mdit/plugin-anchor':
specifier: ^1.1.2
version: 1.1.2(markdown-it@14.3.0)
specifier: ^1.1.3
version: 1.1.3(markdown-it@14.3.0)
'@mdit/plugin-attrs':
specifier: ^1.1.0
version: 1.1.0(markdown-it@14.3.0)
specifier: ^1.1.1
version: 1.1.1(markdown-it@14.3.0)
'@mdit/plugin-container':
specifier: ^1.0.2
version: 1.0.2(markdown-it@14.3.0)
@ -178,8 +178,8 @@ importers:
specifier: ^2.0.2
version: 2.0.2
lint-staged:
specifier: ^17.2.0
version: 17.2.0
specifier: ^17.3.0
version: 17.3.0
lodash.template:
specifier: ^4.18.1
version: 4.18.1
@ -226,14 +226,14 @@ importers:
specifier: ^4.0.5
version: 4.0.5
playwright-chromium:
specifier: ^1.62.0
version: 1.62.0
specifier: ^1.62.1
version: 1.62.1
polka:
specifier: ^1.0.0-next.28
version: 1.0.0-next.28
postcss:
specifier: ^8.5.6
version: 8.5.24
version: 8.5.25
postcss-selector-parser:
specifier: ^7.1.4
version: 7.1.4
@ -272,10 +272,10 @@ importers:
version: 5.9.3
vitest:
specifier: ^4.1.10
version: 4.1.10(@types/node@26.1.2)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))
version: 4.1.10(@types/node@26.1.2)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))
vue-tsc:
specifier: ^3.3.8
version: 3.3.8(typescript@5.9.3)
specifier: ^3.3.9
version: 3.3.9(typescript@5.9.3)
wait-on:
specifier: ^9.1.0
version: 9.1.0(debug@4.4.3)
@ -305,13 +305,13 @@ importers:
version: 9.0.0
postcss-rtlcss:
specifier: ^6.0.0
version: 6.0.0(postcss@8.5.24)
version: 6.0.0(postcss@8.5.25)
vitepress:
specifier: workspace:*
version: link:..
vitepress-plugin-group-icons:
specifier: ^1.7.5
version: 1.7.5(vite@8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))
specifier: ^1.7.6
version: 1.7.6(vite@8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))
vitepress-plugin-llms:
specifier: ^1.13.4
version: 1.13.4
@ -385,14 +385,14 @@ packages:
'@docsearch/sidepanel-js@4.7.0':
resolution: {integrity: sha512-A8r34jCU8kcIk2viECEn2msA28ojUF1BLi/3v5OWWc5G2N3jOuuumBXoeYjfr8dA0UxgFSy5R2bt12dnFJQSyA==}
'@emnapi/core@1.11.1':
resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==}
'@emnapi/core@2.0.0-alpha.3':
resolution: {integrity: sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==}
'@emnapi/runtime@1.11.1':
resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==}
'@emnapi/runtime@2.0.0-alpha.3':
resolution: {integrity: sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==}
'@emnapi/wasi-threads@1.2.2':
resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==}
'@emnapi/wasi-threads@2.0.1':
resolution: {integrity: sha512-9DsSk+o5NBX0CCJT8s0EROGSGxjR/tKu6aBTaVyq+SjAEQH4XcdcRxPBRzsBLizTTJ49MJjF+jgu3qnO9GLQcQ==}
'@esbuild/aix-ppc64@0.27.7':
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
@ -640,13 +640,13 @@ packages:
markdown-it:
optional: true
'@mdit/plugin-anchor@1.1.2':
resolution: {integrity: sha512-/p+GIjjG2E8iQRGtIxAYgbVeZ37U0Rxn5w/KYNKzhgrpGGMYvgDKneLwV/pYDoxxZSKTXF8tpZeuFNY4jR0S4g==}
'@mdit/plugin-anchor@1.1.3':
resolution: {integrity: sha512-lkB9c+NTR//ZLv4MgrDb2ewYulr/WOCWPx0aQC/HbccWJc0JVPMeYwalWfzj517pvKbHGQdOpGRTcFcnn+NG0g==}
peerDependencies:
markdown-it: ^14.2.0
'@mdit/plugin-attrs@1.1.0':
resolution: {integrity: sha512-xdRnlnvjUoIGWNPmESk2BICLcyZFOk79Avpu/4dyf73Yz5JhQAhuJ+CAtaGTWQzp2NV5EVdlxg07qQc3f6BTfw==}
'@mdit/plugin-attrs@1.1.1':
resolution: {integrity: sha512-DgBqzdg8stCGtCsQUJLWhHsyk2HnNp/2d/h5WV9Op2lMGnHA8hjOgnHbmwp0AD8djfv+uUCD6VlOCw3y4L/rGQ==}
engines: {node: '>=22'}
peerDependencies:
markdown-it: ^14.2.0
@ -687,12 +687,12 @@ packages:
markdown-it:
optional: true
'@napi-rs/wasm-runtime@1.2.0':
resolution: {integrity: sha512-kDoONqMa+VnZ4vvvu/ZUurpJ4gkZU57e7g69qpNgWhYcZFPUHZM2CEMKm+cG6ufDVALbjMvfmMjFVqaK7uEMnA==}
'@napi-rs/wasm-runtime@1.2.2':
resolution: {integrity: sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0}
peerDependencies:
'@emnapi/core': ^2.0.0-alpha.3
'@emnapi/runtime': ^2.0.0-alpha.3
'@emnapi/core': ^1.7.1 || ^2.0.0-alpha.3
'@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.3
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
@ -706,8 +706,8 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@oxc-project/types@0.139.0':
resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==}
'@oxc-project/types@0.142.0':
resolution: {integrity: sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==}
'@polka/compression@1.0.0-next.28':
resolution: {integrity: sha512-aDmrBhgHJtxE+jy145WfhW9WmTAFmES/dNnn1LAs8UnnkFgBUj4T8I4ScQ9+rOkpDZStvnVP5iqhN3tvt7O1NA==}
@ -716,97 +716,96 @@ packages:
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
'@rolldown/binding-android-arm64@1.1.5':
resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==}
'@rolldown/binding-android-arm64@1.2.1':
resolution: {integrity: sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@rolldown/binding-darwin-arm64@1.1.5':
resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==}
'@rolldown/binding-darwin-arm64@1.2.1':
resolution: {integrity: sha512-fMsTOnN0OjFm3CyppWPitKnc8UlliVARUULW6cfU6AIqjdtgmSFWSk9vecHzZduv/yMWIHDlRhM1e8Iff9uAfA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.1.5':
resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==}
'@rolldown/binding-darwin-x64@1.2.1':
resolution: {integrity: sha512-1wjKdz/XLGKHaTNHjQveQ/B23TKx4ItAqm1JbyVuvNPc4Ze0Fb48s49TAd/2zcplPl8okE/UbTgmlVfwT7eFeQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@rolldown/binding-freebsd-x64@1.1.5':
resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==}
'@rolldown/binding-freebsd-x64@1.2.1':
resolution: {integrity: sha512-Fa0jHR07E7YBN4vOEsbVf2briYNsuOowfLJaXULZM0ldMlaCaj2LJgLMbMe4iacRyZmvR8efFhgR9wKuGclQUg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.1.5':
resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==}
'@rolldown/binding-linux-arm-gnueabihf@1.2.1':
resolution: {integrity: sha512-pzkgu1SSHGgRRyRZ4fbmSgmajbVt+epaLP99NDjFft69v/ypfTi6swBMiVdh2EkQ0OSnHE1lZDM7DRGkyAzUpA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.1.5':
resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==}
'@rolldown/binding-linux-arm64-gnu@1.2.1':
resolution: {integrity: sha512-QI5SEDY8cbiYWHx0VO4vIc3UlS6a32vXHjU8Qy/17adEmZIPuByJg13UEvo9c/UCiUkdcVWY83C+b+JrwnNyUg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.1.5':
resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==}
'@rolldown/binding-linux-arm64-musl@1.2.1':
resolution: {integrity: sha512-Sm41FyCeXqmYcERoYOCbGIL5hNfd8w9LQ7Y61Bev48HkcjaJqV/iiVOaiDxjVTRMS+QKrZmD8cfPt4uMVnvM+A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.1.5':
resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==}
'@rolldown/binding-linux-ppc64-gnu@1.2.1':
resolution: {integrity: sha512-2x+WhXTGl9yJYPbltW/BSEPTVz9OIWQyER4N+gJEDWkkn904eRcBzELqh/Hf7K0w/ubGbKNMv0ZC+94QK/IFEg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.1.5':
resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==}
'@rolldown/binding-linux-s390x-gnu@1.2.1':
resolution: {integrity: sha512-eEjmQpuRQayHPWWnywaWHkFT3ToPbP3RYy42VVd/B9aBGDA+Ol25EIWHxKQST3IiWJjikCWUF7KtbfqwZrzVwQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.1.5':
resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==}
'@rolldown/binding-linux-x64-gnu@1.2.1':
resolution: {integrity: sha512-/Orga1fZYkLc/56jBICcHrKchl8Z2UKdDSr3LG9ToWO1lQ6a4Livk9Xz+9WN91zsz5QR3XQz2NNoSDEvP6qadw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.1.5':
resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==}
'@rolldown/binding-linux-x64-musl@1.2.1':
resolution: {integrity: sha512-xxBJRL+0q0Kce7orznGWLuylHDY65vuARXZRpX+hPdv+DqK2c3NlCsVA98tlWzWNEE7yPqA/1NQ5nnCrj49Y5A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.1.5':
resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==}
'@rolldown/binding-openharmony-arm64@1.2.1':
resolution: {integrity: sha512-M6AdXIXw3s+/8XpKMzdGDEXGS1S7kwUsy+rcTIUIOx5Ge4nXKCtAFHFV9YKkXvGcC5WMoTjAteLzlsQROVI0Yw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@rolldown/binding-wasm32-wasi@1.1.5':
resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [wasm32]
'@rolldown/binding-wasm32-wasi@1.2.1':
resolution: {integrity: sha512-/TX0SoRGojHzSAHpfVBbavRVSazg5U3h3Y3VXfcc0cdugq6kxdqw8LPGFiPr+/7gE/60zRcsOY2Vi9b9eT0jww==}
engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0}
'@rolldown/binding-win32-arm64-msvc@1.1.5':
resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==}
'@rolldown/binding-win32-arm64-msvc@1.2.1':
resolution: {integrity: sha512-EvRrivJieyHG+AO9lleZWgq+g0+S7oV2C51yuqlcyU/R9net+sI4Pj0F+lUoP2bEr6TWX3SqFaaS0SzfLxSzkw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.1.5':
resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==}
'@rolldown/binding-win32-x64-msvc@1.2.1':
resolution: {integrity: sha512-Z4eCmn5QJ/5+azF9knpLWKfVd9aidn0mAe9TpJgvBLId9Ax3t0+JVxBmT25Bv7NBbVW1TZyKjQjQReouMeH5UQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
@ -1006,36 +1005,36 @@ packages:
cpu: [x64]
os: [win32]
'@shikijs/core@4.3.1':
resolution: {integrity: sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==}
'@shikijs/core@4.4.1':
resolution: {integrity: sha512-VeR2CY6Nn9/WbisoYLOQZ7HZOnwTrpBuOw4wExjqLnBCi62BNWynBUO6K2uPIASPFJwAv7cX1fUu+LrPlSstcw==}
engines: {node: '>=20'}
'@shikijs/engine-javascript@4.3.1':
resolution: {integrity: sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==}
'@shikijs/engine-javascript@4.4.1':
resolution: {integrity: sha512-6U4lJBh8LTvIkEVqRHv/rr3ruwtO6IweFQt1ME1ntHJMGHS+6N86vfYGO1o8c/DtOCTia2lfhdQBtBrps1sDfQ==}
engines: {node: '>=20'}
'@shikijs/engine-oniguruma@4.3.1':
resolution: {integrity: sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==}
'@shikijs/engine-oniguruma@4.4.1':
resolution: {integrity: sha512-p23RugMKss0r5DAtRJW1yAXUDl60JvhQYV20yuxei//26JyDSJefV3umyWzzwep2weblMnJGDYahuti6XkcMgA==}
engines: {node: '>=20'}
'@shikijs/langs@4.3.1':
resolution: {integrity: sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ==}
'@shikijs/langs@4.4.1':
resolution: {integrity: sha512-xb2kCMloBCIraIy2fS5MW0t/BxVY3q2nDyQKBoeSeq6KNrQbShHetCFlw2n35fGIJ6t3+hXDLQogP5ir9O9bvA==}
engines: {node: '>=20'}
'@shikijs/primitive@4.3.1':
resolution: {integrity: sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==}
'@shikijs/primitive@4.4.1':
resolution: {integrity: sha512-ko2OfDoG89YuQ7xL5LtcQiWKb7NIv1Ephb7g48TVU198OzAMLC8lXVEwaJGHK4sUMYrfAGJDqYmNLOLiW/Kz8w==}
engines: {node: '>=20'}
'@shikijs/themes@4.3.1':
resolution: {integrity: sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==}
'@shikijs/themes@4.4.1':
resolution: {integrity: sha512-wudOaoFro+/Zl9gQv2W1Ur5XlVduqvTuYLI483Xi0wgc1A+cy1hfB2r6ac6ufBgF+ID7KJEW7L41MHrzQ4wH+w==}
engines: {node: '>=20'}
'@shikijs/transformers@4.3.1':
resolution: {integrity: sha512-z6ir0bGDgWcF2FduktEfPgIsdOtIlDiLAjFBgBzE42Q9xHbkkIXZtORHzlLVB71iZP9elEcqKg6keajvOUwE2A==}
'@shikijs/transformers@4.4.1':
resolution: {integrity: sha512-Sb9Eehas+5EhClpFgNuklwY3aWf354FLaKRCiAWmjdNbHAjoQUpv6WmSj+N19eTXO6GLIWh1dIOH9dxyauhVWw==}
engines: {node: '>=20'}
'@shikijs/types@4.3.1':
resolution: {integrity: sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g==}
'@shikijs/types@4.4.1':
resolution: {integrity: sha512-GOwCLQDHM5EjGUWNPrhzJbr6JP8V/Dx/CDVkWvbZ1Avw5JFnNUckrgbLmE07qtg4WlW7Q7QFndhjIkeU9XMPvw==}
engines: {node: '>=20'}
'@shikijs/vscode-textmate@10.0.2':
@ -1217,8 +1216,8 @@ packages:
'@vue/devtools-shared@8.2.1':
resolution: {integrity: sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==}
'@vue/language-core@3.3.8':
resolution: {integrity: sha512-ieGT8jJdhhy0mGzStZhsg/qPw5bQZJg5yF+3+XU6saf4sM7yo9ZXy3h+nCwrm2+b4qS/SypkNdR2jAF3uei9tA==}
'@vue/language-core@3.3.9':
resolution: {integrity: sha512-in/68oAa4BCtVY6n/nkuhLIkV8DHYd2UivedJ6cMZ6UYtlq9jaoaSNUBHYCVO44z3nKg7MdE5OBoHKt5SxeBKQ==}
'@vue/reactivity@3.5.40':
resolution: {integrity: sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==}
@ -1337,8 +1336,8 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
axios@1.18.1:
resolution: {integrity: sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==}
axios@1.19.0:
resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==}
bail@2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
@ -1353,8 +1352,8 @@ packages:
boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
brace-expansion@5.0.8:
resolution: {integrity: sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==}
brace-expansion@5.0.9:
resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
engines: {node: 20 || >=22}
braces@3.0.3:
@ -1956,8 +1955,8 @@ packages:
linkify-it@5.0.2:
resolution: {integrity: sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==}
lint-staged@17.2.0:
resolution: {integrity: sha512-FchGnFe4i4B1C/a35SPU9bNGPEHSC1+1iV0plLjzBmKVe9klZrlRfSgK6Cw4VeHyqOXbJUXP0vON61uRftNQ0A==}
lint-staged@17.3.0:
resolution: {integrity: sha512-woZS3vNe3UKqBaLPvbLOtKRY4tLANpWQhom12MGWqC8Mh1lCOO+WgSwmX2amjJAqTY9BkXYW87fCUH5H9Ph6xw==}
engines: {node: '>=22.22.1'}
hasBin: true
@ -2268,13 +2267,13 @@ packages:
resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
engines: {node: '>=12'}
playwright-chromium@1.62.0:
resolution: {integrity: sha512-zigjm5a+G5PUky+O9XMnZDhHFD+SqVgdp0Kki4mU2/vtAmSGJTpl2AiumLknu5485gQLIA8PjF6VPDIALkUQlg==}
playwright-chromium@1.62.1:
resolution: {integrity: sha512-yRMzeViD44qD1ZVMNupp82NJQNjumZ02W5r7+DmtD5+7aP9+v66a0PsAk63ewpVDH1GxMf/J/mO+TS1sTaK29w==}
engines: {node: '>=20'}
hasBin: true
playwright-core@1.62.0:
resolution: {integrity: sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA==}
playwright-core@1.62.1:
resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==}
engines: {node: '>=20'}
hasBin: true
@ -2292,8 +2291,8 @@ packages:
resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==}
engines: {node: '>=4'}
postcss@8.5.24:
resolution: {integrity: sha512-8RyVklq0owXUTa4xlpzu4l9AaVKIdQvAcOHZWaMh98HgySsUtxRVf/chRe3dsSLqb6i40BzGRzEUddRaI+9TSw==}
postcss@8.5.25:
resolution: {integrity: sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==}
engines: {node: ^10 || ^12 || >=14}
powershell-utils@0.1.0:
@ -2376,8 +2375,8 @@ packages:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rolldown@1.1.5:
resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==}
rolldown@1.2.1:
resolution: {integrity: sha512-4FKJhg8d3OiyQOA6Q1Q0hoFFpW9/OoX+VsHzpECsdsIZoOArrAK90gl59YK/Z+gnDel45bgJZK03ozH/9bCqEw==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
@ -2436,8 +2435,8 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
shiki@4.3.1:
resolution: {integrity: sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==}
shiki@4.4.1:
resolution: {integrity: sha512-rFP+iYKzjLEIqiMiKANhARqiAbk4deDhWnBtnUO/K0D0dPxMGDH4N0FVfBY/VeI+lPrV4wNGCHQZp7EOr7NNBw==}
engines: {node: '>=20'}
siginfo@2.0.0:
@ -2554,8 +2553,8 @@ packages:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
tinyrainbow@3.1.0:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
tinyrainbow@3.1.1:
resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==}
engines: {node: '>=14.0.0'}
to-regex-range@5.0.1:
@ -2660,13 +2659,13 @@ packages:
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
vite@8.1.5:
resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==}
vite@8.2.0:
resolution: {integrity: sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0
'@vitejs/devtools': ^0.3.0
'@vitejs/devtools': ^0.4.0
esbuild: ^0.27.0 || ^0.28.0
jiti: '>=1.21.0'
less: ^4.0.0
@ -2703,8 +2702,8 @@ packages:
yaml:
optional: true
vitepress-plugin-group-icons@1.7.5:
resolution: {integrity: sha512-QzcroUuIiVKyXpmEiiHVbfRTQIy9Zbwxpk5JC/zavO8mavitwumz2RZWlwTchMCCHducYyPptkYvXvdnNUWkog==}
vitepress-plugin-group-icons@1.7.6:
resolution: {integrity: sha512-fXSpQAYHpFpEsZLKWQAwbRx/+0uRSHmY58YNAQcz0AVirGsM6w22jtSmQhOGLEj8ZKyeRtTJrpx6otUI6wvoaQ==}
peerDependencies:
vite: '>=3'
peerDependenciesMeta:
@ -2759,8 +2758,8 @@ packages:
vscode-uri@3.1.0:
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
vue-tsc@3.3.8:
resolution: {integrity: sha512-xXmYlVQpcwJDWyGlqbHrGVOl1h3UOsASymRibrHc+iy9j/UNnOrOn4u+fntHz4D6Cs74RtapeqVV6CzJeg+UlA==}
vue-tsc@3.3.9:
resolution: {integrity: sha512-TS3Y1ux/IRoE8OCP2PpACAeOseuIs0UvWrcr7u+w3PmfY+SlCfEf8zjrBgnQksHUgLpthi5vHlffcQTQTdPBZA==}
hasBin: true
peerDependencies:
typescript: '>=5.0.0'
@ -2908,18 +2907,18 @@ snapshots:
'@docsearch/sidepanel-js@4.7.0': {}
'@emnapi/core@1.11.1':
'@emnapi/core@2.0.0-alpha.3':
dependencies:
'@emnapi/wasi-threads': 1.2.2
'@emnapi/wasi-threads': 2.0.1
tslib: 2.8.1
optional: true
'@emnapi/runtime@1.11.1':
'@emnapi/runtime@2.0.0-alpha.3':
dependencies:
tslib: 2.8.1
optional: true
'@emnapi/wasi-threads@1.2.2':
'@emnapi/wasi-threads@2.0.1':
dependencies:
tslib: 2.8.1
optional: true
@ -3116,12 +3115,12 @@ snapshots:
optionalDependencies:
markdown-it: 14.3.0
'@mdit/plugin-anchor@1.1.2(markdown-it@14.3.0)':
'@mdit/plugin-anchor@1.1.3(markdown-it@14.3.0)':
dependencies:
'@types/markdown-it': 14.1.2
markdown-it: 14.3.0
'@mdit/plugin-attrs@1.1.0(markdown-it@14.3.0)':
'@mdit/plugin-attrs@1.1.1(markdown-it@14.3.0)':
dependencies:
'@mdit/helper': 1.0.1(markdown-it@14.3.0)
'@types/markdown-it': 14.1.2
@ -3151,10 +3150,10 @@ snapshots:
optionalDependencies:
markdown-it: 14.3.0
'@napi-rs/wasm-runtime@1.2.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
'@napi-rs/wasm-runtime@1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)':
dependencies:
'@emnapi/core': 1.11.1
'@emnapi/runtime': 1.11.1
'@emnapi/core': 2.0.0-alpha.3
'@emnapi/runtime': 2.0.0-alpha.3
'@tybys/wasm-util': 0.10.3
optional: true
@ -3170,59 +3169,59 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.20.1
'@oxc-project/types@0.139.0': {}
'@oxc-project/types@0.142.0': {}
'@polka/compression@1.0.0-next.28': {}
'@polka/url@1.0.0-next.29': {}
'@rolldown/binding-android-arm64@1.1.5':
'@rolldown/binding-android-arm64@1.2.1':
optional: true
'@rolldown/binding-darwin-arm64@1.1.5':
'@rolldown/binding-darwin-arm64@1.2.1':
optional: true
'@rolldown/binding-darwin-x64@1.1.5':
'@rolldown/binding-darwin-x64@1.2.1':
optional: true
'@rolldown/binding-freebsd-x64@1.1.5':
'@rolldown/binding-freebsd-x64@1.2.1':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.1.5':
'@rolldown/binding-linux-arm-gnueabihf@1.2.1':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.1.5':
'@rolldown/binding-linux-arm64-gnu@1.2.1':
optional: true
'@rolldown/binding-linux-arm64-musl@1.1.5':
'@rolldown/binding-linux-arm64-musl@1.2.1':
optional: true
'@rolldown/binding-linux-ppc64-gnu@1.1.5':
'@rolldown/binding-linux-ppc64-gnu@1.2.1':
optional: true
'@rolldown/binding-linux-s390x-gnu@1.1.5':
'@rolldown/binding-linux-s390x-gnu@1.2.1':
optional: true
'@rolldown/binding-linux-x64-gnu@1.1.5':
'@rolldown/binding-linux-x64-gnu@1.2.1':
optional: true
'@rolldown/binding-linux-x64-musl@1.1.5':
'@rolldown/binding-linux-x64-musl@1.2.1':
optional: true
'@rolldown/binding-openharmony-arm64@1.1.5':
'@rolldown/binding-openharmony-arm64@1.2.1':
optional: true
'@rolldown/binding-wasm32-wasi@1.1.5':
'@rolldown/binding-wasm32-wasi@1.2.1':
dependencies:
'@emnapi/core': 1.11.1
'@emnapi/runtime': 1.11.1
'@napi-rs/wasm-runtime': 1.2.0(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
'@emnapi/core': 2.0.0-alpha.3
'@emnapi/runtime': 2.0.0-alpha.3
'@napi-rs/wasm-runtime': 1.2.2(@emnapi/core@2.0.0-alpha.3)(@emnapi/runtime@2.0.0-alpha.3)
optional: true
'@rolldown/binding-win32-arm64-msvc@1.1.5':
'@rolldown/binding-win32-arm64-msvc@1.2.1':
optional: true
'@rolldown/binding-win32-x64-msvc@1.1.5':
'@rolldown/binding-win32-x64-msvc@1.2.1':
optional: true
'@rolldown/pluginutils@1.0.1': {}
@ -3349,45 +3348,45 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.62.3':
optional: true
'@shikijs/core@4.3.1':
'@shikijs/core@4.4.1':
dependencies:
'@shikijs/primitive': 4.3.1
'@shikijs/types': 4.3.1
'@shikijs/primitive': 4.4.1
'@shikijs/types': 4.4.1
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.5
hast-util-to-html: 9.0.5
'@shikijs/engine-javascript@4.3.1':
'@shikijs/engine-javascript@4.4.1':
dependencies:
'@shikijs/types': 4.3.1
'@shikijs/types': 4.4.1
'@shikijs/vscode-textmate': 10.0.2
oniguruma-to-es: 4.3.6
'@shikijs/engine-oniguruma@4.3.1':
'@shikijs/engine-oniguruma@4.4.1':
dependencies:
'@shikijs/types': 4.3.1
'@shikijs/types': 4.4.1
'@shikijs/vscode-textmate': 10.0.2
'@shikijs/langs@4.3.1':
'@shikijs/langs@4.4.1':
dependencies:
'@shikijs/types': 4.3.1
'@shikijs/types': 4.4.1
'@shikijs/primitive@4.3.1':
'@shikijs/primitive@4.4.1':
dependencies:
'@shikijs/types': 4.3.1
'@shikijs/types': 4.4.1
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.5
'@shikijs/themes@4.3.1':
'@shikijs/themes@4.4.1':
dependencies:
'@shikijs/types': 4.3.1
'@shikijs/types': 4.4.1
'@shikijs/transformers@4.3.1':
'@shikijs/transformers@4.4.1':
dependencies:
'@shikijs/core': 4.3.1
'@shikijs/types': 4.3.1
'@shikijs/core': 4.4.1
'@shikijs/types': 4.4.1
'@shikijs/types@4.3.1':
'@shikijs/types@4.4.1':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.5
@ -3503,10 +3502,10 @@ snapshots:
'@ungap/structured-clone@1.3.3': {}
'@vitejs/plugin-vue@6.0.8(vite@8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3))':
'@vitejs/plugin-vue@6.0.8(vite@8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))(vue@3.5.40(typescript@5.9.3))':
dependencies:
'@rolldown/pluginutils': 1.0.1
vite: 8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)
vite: 8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)
vue: 3.5.40(typescript@5.9.3)
'@vitest/expect@4.1.10':
@ -3516,19 +3515,19 @@ snapshots:
'@vitest/spy': 4.1.10
'@vitest/utils': 4.1.10
chai: 6.2.2
tinyrainbow: 3.1.0
tinyrainbow: 3.1.1
'@vitest/mocker@4.1.10(vite@8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))':
'@vitest/mocker@4.1.10(vite@8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))':
dependencies:
'@vitest/spy': 4.1.10
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)
vite: 8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)
'@vitest/pretty-format@4.1.10':
dependencies:
tinyrainbow: 3.1.0
tinyrainbow: 3.1.1
'@vitest/runner@4.1.10':
dependencies:
@ -3548,7 +3547,7 @@ snapshots:
dependencies:
'@vitest/pretty-format': 4.1.10
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
tinyrainbow: 3.1.1
'@volar/language-core@2.4.28':
dependencies:
@ -3584,7 +3583,7 @@ snapshots:
'@vue/shared': 3.5.40
estree-walker: 2.0.2
magic-string: 0.30.21
postcss: 8.5.24
postcss: 8.5.25
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.40':
@ -3605,7 +3604,7 @@ snapshots:
'@vue/devtools-shared@8.2.1': {}
'@vue/language-core@3.3.8':
'@vue/language-core@3.3.9':
dependencies:
'@volar/language-core': 2.4.28
'@vue/compiler-dom': 3.5.40
@ -3646,13 +3645,13 @@ snapshots:
'@vueuse/shared': 14.4.0(vue@3.5.40(typescript@5.9.3))
vue: 3.5.40(typescript@5.9.3)
'@vueuse/integrations@14.4.0(axios@1.18.1(debug@4.4.3))(focus-trap@8.2.2)(vue@3.5.40(typescript@5.9.3))':
'@vueuse/integrations@14.4.0(axios@1.19.0(debug@4.4.3))(focus-trap@8.2.2)(vue@3.5.40(typescript@5.9.3))':
dependencies:
'@vueuse/core': 14.4.0(vue@3.5.40(typescript@5.9.3))
'@vueuse/shared': 14.4.0(vue@3.5.40(typescript@5.9.3))
vue: 3.5.40(typescript@5.9.3)
optionalDependencies:
axios: 1.18.1(debug@4.4.3)
axios: 1.19.0(debug@4.4.3)
focus-trap: 8.2.2
'@vueuse/metadata@14.4.0': {}
@ -3695,7 +3694,7 @@ snapshots:
asynckit@0.4.0: {}
axios@1.18.1(debug@4.4.3):
axios@1.19.0(debug@4.4.3):
dependencies:
follow-redirects: 1.16.0(debug@4.4.3)
form-data: 4.0.6
@ -3713,7 +3712,7 @@ snapshots:
boolbase@1.0.0: {}
brace-expansion@5.0.8:
brace-expansion@5.0.9:
dependencies:
balanced-match: 4.0.4
@ -4296,7 +4295,7 @@ snapshots:
dependencies:
uc.micro: 2.1.0
lint-staged@17.2.0:
lint-staged@17.3.0:
dependencies:
picomatch: 4.0.5
string-argv: 0.3.2
@ -4603,7 +4602,7 @@ snapshots:
minimatch@10.2.6:
dependencies:
brace-expansion: 5.0.8
brace-expansion: 5.0.9
minimist@1.2.8: {}
@ -4704,20 +4703,20 @@ snapshots:
picomatch@4.0.5: {}
playwright-chromium@1.62.0:
playwright-chromium@1.62.1:
dependencies:
playwright-core: 1.62.0
playwright-core: 1.62.1
playwright-core@1.62.0: {}
playwright-core@1.62.1: {}
polka@1.0.0-next.28:
dependencies:
'@polka/url': 1.0.0-next.29
trouter: 4.0.0
postcss-rtlcss@6.0.0(postcss@8.5.24):
postcss-rtlcss@6.0.0(postcss@8.5.25):
dependencies:
postcss: 8.5.24
postcss: 8.5.25
rtlcss: 4.3.0
postcss-selector-parser@7.1.4:
@ -4725,7 +4724,7 @@ snapshots:
cssesc: 3.0.0
util-deprecate: 1.0.2
postcss@8.5.24:
postcss@8.5.25:
dependencies:
nanoid: 3.3.16
picocolors: 1.1.1
@ -4812,26 +4811,26 @@ snapshots:
reusify@1.1.0: {}
rolldown@1.1.5:
rolldown@1.2.1:
dependencies:
'@oxc-project/types': 0.139.0
'@oxc-project/types': 0.142.0
'@rolldown/pluginutils': 1.0.1
optionalDependencies:
'@rolldown/binding-android-arm64': 1.1.5
'@rolldown/binding-darwin-arm64': 1.1.5
'@rolldown/binding-darwin-x64': 1.1.5
'@rolldown/binding-freebsd-x64': 1.1.5
'@rolldown/binding-linux-arm-gnueabihf': 1.1.5
'@rolldown/binding-linux-arm64-gnu': 1.1.5
'@rolldown/binding-linux-arm64-musl': 1.1.5
'@rolldown/binding-linux-ppc64-gnu': 1.1.5
'@rolldown/binding-linux-s390x-gnu': 1.1.5
'@rolldown/binding-linux-x64-gnu': 1.1.5
'@rolldown/binding-linux-x64-musl': 1.1.5
'@rolldown/binding-openharmony-arm64': 1.1.5
'@rolldown/binding-wasm32-wasi': 1.1.5
'@rolldown/binding-win32-arm64-msvc': 1.1.5
'@rolldown/binding-win32-x64-msvc': 1.1.5
'@rolldown/binding-android-arm64': 1.2.1
'@rolldown/binding-darwin-arm64': 1.2.1
'@rolldown/binding-darwin-x64': 1.2.1
'@rolldown/binding-freebsd-x64': 1.2.1
'@rolldown/binding-linux-arm-gnueabihf': 1.2.1
'@rolldown/binding-linux-arm64-gnu': 1.2.1
'@rolldown/binding-linux-arm64-musl': 1.2.1
'@rolldown/binding-linux-ppc64-gnu': 1.2.1
'@rolldown/binding-linux-s390x-gnu': 1.2.1
'@rolldown/binding-linux-x64-gnu': 1.2.1
'@rolldown/binding-linux-x64-musl': 1.2.1
'@rolldown/binding-openharmony-arm64': 1.2.1
'@rolldown/binding-wasm32-wasi': 1.2.1
'@rolldown/binding-win32-arm64-msvc': 1.2.1
'@rolldown/binding-win32-x64-msvc': 1.2.1
rollup-plugin-dts@6.1.1(rollup@4.62.3)(typescript@5.9.3):
dependencies:
@ -4887,7 +4886,7 @@ snapshots:
dependencies:
escalade: 3.2.0
picocolors: 1.1.1
postcss: 8.5.24
postcss: 8.5.25
strip-json-comments: 3.1.1
run-applescript@7.1.0: {}
@ -4915,14 +4914,14 @@ snapshots:
shebang-regex@3.0.0: {}
shiki@4.3.1:
shiki@4.4.1:
dependencies:
'@shikijs/core': 4.3.1
'@shikijs/engine-javascript': 4.3.1
'@shikijs/engine-oniguruma': 4.3.1
'@shikijs/langs': 4.3.1
'@shikijs/themes': 4.3.1
'@shikijs/types': 4.3.1
'@shikijs/core': 4.4.1
'@shikijs/engine-javascript': 4.4.1
'@shikijs/engine-oniguruma': 4.4.1
'@shikijs/langs': 4.4.1
'@shikijs/themes': 4.4.1
'@shikijs/types': 4.4.1
'@shikijs/vscode-textmate': 10.0.2
'@types/hast': 3.0.5
@ -5033,7 +5032,7 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.5)
picomatch: 4.0.5
tinyrainbow@3.1.0: {}
tinyrainbow@3.1.1: {}
to-regex-range@5.0.1:
dependencies:
@ -5139,12 +5138,12 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
vite@8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0):
vite@8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0):
dependencies:
lightningcss: 1.33.0
picomatch: 4.0.5
postcss: 8.5.24
rolldown: 1.1.5
postcss: 8.5.25
rolldown: 1.2.1
tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 26.1.2
@ -5153,13 +5152,13 @@ snapshots:
jiti: 1.21.7
yaml: 2.9.0
vitepress-plugin-group-icons@1.7.5(vite@8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)):
vitepress-plugin-group-icons@1.7.6(vite@8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)):
dependencies:
'@iconify-json/logos': 1.2.11
'@iconify-json/vscode-icons': 1.2.67
'@iconify/utils': 3.1.4
optionalDependencies:
vite: 8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)
vite: 8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)
vitepress-plugin-llms@1.13.4:
dependencies:
@ -5180,10 +5179,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
vitest@4.1.10(@types/node@26.1.2)(vite@8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)):
vitest@4.1.10(@types/node@26.1.2)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)):
dependencies:
'@vitest/expect': 4.1.10
'@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))
'@vitest/mocker': 4.1.10(vite@8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0))
'@vitest/pretty-format': 4.1.10
'@vitest/runner': 4.1.10
'@vitest/snapshot': 4.1.10
@ -5199,8 +5198,8 @@ snapshots:
tinybench: 2.9.0
tinyexec: 1.2.4
tinyglobby: 0.2.17
tinyrainbow: 3.1.0
vite: 8.1.5(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)
tinyrainbow: 3.1.1
vite: 8.2.0(@types/node@26.1.2)(esbuild@0.27.7)(jiti@1.21.7)(yaml@2.9.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 26.1.2
@ -5209,10 +5208,10 @@ snapshots:
vscode-uri@3.1.0: {}
vue-tsc@3.3.8(typescript@5.9.3):
vue-tsc@3.3.9(typescript@5.9.3):
dependencies:
'@volar/typescript': 2.4.28
'@vue/language-core': 3.3.8
'@vue/language-core': 3.3.9
typescript: 5.9.3
vue@3.5.40(typescript@5.9.3):
@ -5227,7 +5226,7 @@ snapshots:
wait-on@9.1.0(debug@4.4.3):
dependencies:
axios: 1.18.1(debug@4.4.3)
axios: 1.19.0(debug@4.4.3)
joi: 18.2.3
lodash: 4.18.1
minimist: 1.2.8

@ -17,3 +17,5 @@ minimumReleaseAgeExclude:
- '@vueuse/*'
shellEmulator: true
strictPeerDependencies: true

@ -1,5 +1,6 @@
import { spawn } from 'cross-spawn'
import type { SpawnOptions } from 'node:child_process'
import { once } from 'node:events'
import fs from 'node:fs'
import { createRequire } from 'node:module'
import { resolve } from 'node:path'
@ -18,24 +19,20 @@ const tags = ['latest', 'next'] as const
const dir = fileURLToPath(new URL('.', import.meta.url))
const inc = (i: semver.ReleaseType) => _inc(currentVersion, i)
const run = (bin: string, args: string[], opts: SpawnOptions = {}) =>
new Promise<void>((resolve, reject) => {
const child = spawn(bin, args, {
stdio: 'inherit',
...opts
})
child.on('error', reject)
child.on('close', (code, signal) => {
if (code === 0) {
resolve()
} else if (signal) {
reject(new Error(`${bin} exited with signal ${signal}`))
} else {
reject(new Error(`${bin} exited with code ${code}`))
}
})
})
const run = async (bin: string, args: string[], opts: SpawnOptions = {}) => {
const child = spawn(bin, args, { stdio: 'inherit', ...opts })
const [code, signal] = (await once(child, 'close')) as [
number | null,
NodeJS.Signals | null
]
if (code !== 0) {
throw new Error(
signal
? `${bin} exited with signal ${signal}`
: `${bin} exited with code ${code}`
)
}
}
const cancel = () => prompts.cancel('Operation cancelled')
async function main() {

@ -1,16 +1,18 @@
import { getIconsCSS } from '@iconify/utils'
import { createHash } from 'node:crypto'
import fs from 'node:fs'
import { mkdir, rm, symlink, unlink, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import path from 'node:path'
import pMap from 'p-map'
import { packageDirectorySync } from 'package-directory'
import { packageDirectory } from 'package-directory'
import type { BuildOptions, Rolldown } from 'vite'
import { resolveConfig, type SiteConfig } from '../config'
import { clearCache } from '../markdownToVue'
import type { PageMeta } from '../plugin'
import { slash, type Awaitable, type HeadConfig } from '../shared'
import { deserializeFunctions, serializeFunctions } from '../utils/fnSerialize'
import { logVersion } from '../utils/logVersion'
import { nativeImport } from '../utils/nativeImport'
import { task } from '../utils/task'
import { bundle } from './bundle'
@ -27,15 +29,19 @@ export async function build(
onAfterConfigResolve?: (siteConfig: SiteConfig) => Awaitable<void>
} = {}
) {
const start = Date.now()
const start = performance.now()
process.env.NODE_ENV = 'production'
const siteConfig = await resolveConfig(root, 'build', 'production')
await buildOptions.onAfterConfigResolve?.(siteConfig)
if (buildOptions.onAfterConfigResolve) {
await buildOptions.onAfterConfigResolve(siteConfig)
} else {
logVersion(siteConfig.logger)
}
delete buildOptions.onAfterConfigResolve
const unlinkVue = linkVue()
const unlinkVue = await linkVue()
if (buildOptions.base) {
siteConfig.site.base = buildOptions.base
@ -55,120 +61,20 @@ export async function build(
const pageMetaMap = Object.create(null) as Record<string, PageMeta>
try {
const { clientResult, serverResult, pageToHashMap } = await bundle(
siteConfig,
buildOptions,
pageMetaMap
const out = await task(
'building client + server bundles',
bundle.bind(null, siteConfig, buildOptions, pageMetaMap)
)
if (process.env.BUNDLE_ONLY) {
return
}
const entryPath = path.join(siteConfig.tempDir, 'app.js')
const { render } = await nativeImport(entryPath)
await task('rendering pages', async () => {
const clientOutput: (Rolldown.OutputChunk | Rolldown.OutputAsset)[] =
clientResult?.output || []
const appChunk = clientOutput.find(
(chunk): chunk is Rolldown.OutputChunk =>
chunk.type === 'chunk' &&
chunk.isEntry &&
!!chunk.facadeModuleId?.endsWith('.js')
)
const isDefaultTheme = clientOutput.some(
(chunk): chunk is Rolldown.OutputChunk =>
chunk.type === 'chunk' &&
chunk.name === 'theme' &&
chunk.moduleIds.some((id) => id.includes('client/theme-default'))
)
// ----
const resultOutput: (Rolldown.OutputChunk | Rolldown.OutputAsset)[] =
(siteConfig.mpa ? serverResult : clientResult)?.output || []
const cssChunk = resultOutput.find(
(chunk): chunk is Rolldown.OutputAsset =>
chunk.type === 'asset' && chunk.fileName.endsWith('.css')
)
// prettier-ignore
const assets = resultOutput.filter(
(chunk): chunk is Rolldown.OutputAsset =>
chunk.type === 'asset' && !chunk.fileName.endsWith('.css')
).map((asset) => siteConfig.site.base + asset.fileName)
// ----
const additionalHeadTags: HeadConfig[] = []
const metadataScript = generateMetadataScript(pageToHashMap, siteConfig)
if (isDefaultTheme) {
const fontURL = assets.find((file) =>
/inter-roman-latin\.[\w-]+\.woff2/.test(file)
)
if (fontURL) {
additionalHeadTags.push([
'link',
{
rel: 'preload',
href: fontURL,
as: 'font',
type: 'font/woff2',
crossorigin: ''
}
])
}
}
const usedIcons = new Set<string>()
await pMap(
['404.md', ...siteConfig.pages],
async (page) => {
await renderPage(
render,
siteConfig,
siteConfig.rewrites.map[page] || page,
clientResult,
appChunk,
cssChunk,
assets,
pageToHashMap,
metadataScript,
additionalHeadTags,
usedIcons
)
},
{ concurrency: siteConfig.buildConcurrency }
)
const icons = require('@iconify-json/simple-icons/icons.json')
const iconsCss = getIconsCSS(icons, Array.from(usedIcons).sort(), {
iconSelector: '.vpi-social-{name}',
commonSelector: '.vpi-social',
varName: 'icon',
format: process.env.DEBUG ? 'expanded' : 'compressed',
mode: 'mask'
}).replace(/[^]*?}\n*/, '')
fs.writeFileSync(path.join(siteConfig.outDir, 'vp-icons.css'), iconsCss)
})
// emit page hash map for the case where a user session is open
// when the site got redeployed (which invalidates current hash map)
fs.writeFileSync(
path.join(siteConfig.outDir, 'hashmap.json'),
JSON.stringify(pageToHashMap)
)
await task('rendering pages', render.bind(null, siteConfig, out))
} finally {
unlinkVue()
await unlinkVue()
if (!process.env.DEBUG) {
fs.rmSync(siteConfig.tempDir, {
await rm(siteConfig.tempDir, {
recursive: true,
force: true,
maxRetries: 10
@ -176,36 +82,148 @@ export async function build(
}
}
await generateSitemap(siteConfig, pageMetaMap)
if (siteConfig.sitemap?.hostname) {
await task(
'generating sitemap',
generateSitemap.bind(null, siteConfig, pageMetaMap)
)
}
await siteConfig.buildEnd?.(siteConfig)
clearCache()
siteConfig.logger.info(
`build complete in ${((Date.now() - start) / 1000).toFixed(2)}s.`
`build complete in ${((performance.now() - start) / 1000).toFixed(2)}s.`
)
}
function linkVue() {
const root = packageDirectorySync()
async function linkVue() {
const root = await packageDirectory()
if (root) {
const dest = path.resolve(root, 'node_modules/vue')
// if user did not install vue by themselves, link VitePress' version
if (!fs.existsSync(dest)) {
const src = path.dirname(createRequire(import.meta.url).resolve('vue'))
fs.mkdirSync(path.dirname(dest), { recursive: true })
fs.symlinkSync(src, dest, 'junction')
return () => {
fs.unlinkSync(dest)
}
await mkdir(path.dirname(dest), { recursive: true })
await symlink(src, dest, 'junction')
return () => unlink(dest)
}
}
return () => {}
return async () => {}
}
function generateMetadataScript(
async function render(
siteConfig: SiteConfig,
{
clientResult,
serverResult,
pageToHashMap
}: Awaited<ReturnType<typeof bundle>>
): Promise<void> {
const entryPath = path.join(siteConfig.tempDir, 'app.js')
const { render } = await nativeImport(entryPath)
const clientOutput: (Rolldown.OutputChunk | Rolldown.OutputAsset)[] =
clientResult?.output || []
const appChunk = clientOutput.find(
(chunk): chunk is Rolldown.OutputChunk =>
chunk.type === 'chunk' &&
chunk.isEntry &&
!!chunk.facadeModuleId?.endsWith('.js')
)
const isDefaultTheme = clientOutput.some(
(chunk): chunk is Rolldown.OutputChunk =>
chunk.type === 'chunk' &&
chunk.name === 'theme' &&
chunk.moduleIds.some((id) => id.includes('client/theme-default'))
)
// ----
const resultOutput: (Rolldown.OutputChunk | Rolldown.OutputAsset)[] =
(siteConfig.mpa ? serverResult : clientResult)?.output || []
const cssChunk = resultOutput.find(
(chunk): chunk is Rolldown.OutputAsset =>
chunk.type === 'asset' && chunk.fileName.endsWith('.css')
)
// prettier-ignore
const assets = resultOutput.filter(
(chunk): chunk is Rolldown.OutputAsset =>
chunk.type === 'asset' && !chunk.fileName.endsWith('.css')
).map((asset) => siteConfig.site.base + asset.fileName)
// ----
const additionalHeadTags: HeadConfig[] = []
const metadataScript = await generateMetadataScript(pageToHashMap, siteConfig)
if (isDefaultTheme) {
const fontURL = assets.find((file) =>
/inter-roman-latin\.[\w-]+\.woff2/.test(file)
)
if (fontURL) {
additionalHeadTags.push([
'link',
{
rel: 'preload',
href: fontURL,
as: 'font',
type: 'font/woff2',
crossorigin: ''
}
])
}
}
const usedIcons = new Set<string>()
await pMap(
['404.md', ...siteConfig.pages],
async (page) => {
await renderPage(
render,
siteConfig,
siteConfig.rewrites.map[page] || page,
clientResult,
appChunk,
cssChunk,
assets,
pageToHashMap,
metadataScript,
additionalHeadTags,
usedIcons
)
},
{ concurrency: siteConfig.buildConcurrency }
)
const icons = require('@iconify-json/simple-icons/icons.json')
const iconsCss = getIconsCSS(icons, Array.from(usedIcons).sort(), {
iconSelector: '.vpi-social-{name}',
commonSelector: '.vpi-social',
varName: 'icon',
format: process.env.DEBUG ? 'expanded' : 'compressed',
mode: 'mask'
}).replace(/[^]*?}\n*/, '')
await writeFile(path.join(siteConfig.outDir, 'vp-icons.css'), iconsCss)
// emit page hash map for the case where a user session is open
// when the site got redeployed (which invalidates current hash map)
await writeFile(
path.join(siteConfig.outDir, 'hashmap.json'),
JSON.stringify(pageToHashMap)
)
}
async function generateMetadataScript(
pageToHashMap: Record<string, string>,
config: SiteConfig
) {
): Promise<{ html: string; inHead: boolean }> {
if (config.mpa) {
return { html: '', inHead: false }
}
@ -237,8 +255,8 @@ function generateMetadataScript(
const resolvedMetadataFile = path.join(config.outDir, metadataFile)
const metadataFileURL = slash(`${config.site.base}${metadataFile}`)
fs.mkdirSync(path.dirname(resolvedMetadataFile), { recursive: true })
fs.writeFileSync(resolvedMetadataFile, metadataContent)
await mkdir(path.dirname(resolvedMetadataFile), { recursive: true })
await writeFile(resolvedMetadataFile, metadataContent)
return {
html: `<script type="module" src="${metadataFileURL}"></script>`,

@ -14,7 +14,6 @@ import { APP_PATH } from '../alias'
import type { SiteConfig } from '../config'
import { createVitePressPlugin, type PageMeta } from '../plugin'
import { escapeRegExp, sanitizeFileName, slash } from '../shared'
import { task } from '../utils/task'
import { buildMPAClient } from './buildMPAClient'
// https://github.com/vitejs/vite/blob/a55d0b34400e3360c4100d05e422ae9cf10fa07b/packages/vite/src/node/constants.ts#L50
@ -35,6 +34,9 @@ const excludedModules = [
clientDir
]
const cache = new Map<string, boolean>()
const cacheTheme = new Map<string, boolean>()
// bundles the VitePress app for both client AND server.
export async function bundle(
config: SiteConfig,
@ -61,9 +63,7 @@ export async function bundle(
})
const themeEntryRE = new RegExp(
`^${escapeRegExp(
path.resolve(config.themeDir, 'index.js').replace(/\\/g, '/')
).slice(0, -2)}m?(j|t)s`
`^${escapeRegExp(slash(path.resolve(config.themeDir, 'index.js'))).slice(0, -2)}m?(j|t)s`
)
// resolve options to pass to vite
@ -120,49 +120,11 @@ export async function bundle(
chunkFileNames(chunk) {
// avoid ads chunk being intercepted by adblock
return /(?:Carbon|BuySell)Ads/.test(chunk.name)
? `${config.assetsDir}/chunks/ui-custom.[hash].js`
? `${config.assetsDir}/chunks/[hash].js`
: `${config.assetsDir}/chunks/[name].[hash].js`
},
codeSplitting: {
groups: [
{
name(id, ctx) {
const getModuleInfo = ctx.getModuleInfo.bind(ctx)
// avoid emitting multiple files for assets
// see: https://github.com/rolldown/rolldown/issues/4246
if (getModuleInfo(id)?.meta['vite:asset']) {
return 'assets'
}
// move known framework code into a stable chunk so that
// custom theme changes do not invalidate hash for all pages
if (
id.startsWith('\0vite') ||
id.includes('plugin-vue:export-helper') ||
(id.includes(`${clientDir}/app`) &&
id !== `${clientDir}/app/index.js`) ||
(isEagerChunk(id, getModuleInfo) &&
/@vue\/(runtime|shared|reactivity)/.test(id))
) {
return 'framework'
}
if (
(id.startsWith(`${clientDir}/theme-default`) ||
!excludedModules.some((i) => id.includes(i))) &&
staticImportedByEntry(
id,
getModuleInfo,
cacheTheme,
themeEntryRE
)
) {
return 'theme'
}
}
}
]
groups: [{ name: chunkName.bind(null, themeEntryRE) }]
}
})
},
@ -172,16 +134,12 @@ export async function bundle(
configFile: config.vite?.configFile
})
let clientResult: Rolldown.RolldownOutput | null = null
let serverResult!: Rolldown.RolldownOutput
// prettier-ignore
await task('building client + server bundles', async () => {
if (!config.mpa) clientResult =
(await build(await resolveViteConfig(false))) as Rolldown.RolldownOutput
serverResult =
(await build(await resolveViteConfig(true))) as Rolldown.RolldownOutput
})
let clientResult = config.mpa
? null
: ((await build(await resolveViteConfig(false))) as Rolldown.RolldownOutput)
const serverResult = (await build(
await resolveViteConfig(true)
)) as Rolldown.RolldownOutput
if (config.mpa) {
// in MPA mode, we need to copy over the non-js asset files from the
@ -223,8 +181,39 @@ export async function bundle(
return { clientResult, serverResult, pageToHashMap: sortedPageToHashMap }
}
const cache = new Map<string, boolean>()
const cacheTheme = new Map<string, boolean>()
function chunkName(
themeEntryRE: RegExp,
id: string,
ctx: { getModuleInfo: Rolldown.GetModuleInfo }
): string | undefined {
const getModuleInfo = ctx.getModuleInfo.bind(ctx)
// avoid emitting multiple files for assets
// see: https://github.com/rolldown/rolldown/issues/4246
if (getModuleInfo(id)?.meta['vite:asset']) {
return 'assets'
}
// move known framework code into a stable chunk so that
// custom theme changes do not invalidate hash for all pages
if (
id.startsWith('\0vite') ||
id.includes('plugin-vue:export-helper') ||
(id.includes(`${clientDir}/app`) && id !== `${clientDir}/app/index.js`) ||
(isEagerChunk(id, getModuleInfo) &&
/@vue\/(runtime|shared|reactivity)/.test(id))
) {
return 'framework'
}
if (
(id.startsWith(`${clientDir}/theme-default`) ||
!excludedModules.some((i) => id.includes(i))) &&
staticImportedByEntry(id, getModuleInfo, cacheTheme, themeEntryRE)
) {
return 'theme'
}
}
/**
* Check if a module is statically imported by at least one entry.

@ -1,5 +1,6 @@
import fs from 'node:fs'
import path from 'node:path'
import { pipeline } from 'node:stream/promises'
import {
SitemapStream,
type EnumChangefreq,
@ -9,72 +10,63 @@ import {
} from 'sitemap'
import type { SiteConfig } from '../config'
import type { PageMeta } from '../plugin'
import { task } from '../utils/task'
export async function generateSitemap(
siteConfig: SiteConfig,
pageMetaMap: Record<string, PageMeta>
) {
if (!siteConfig.sitemap?.hostname) return
const locales = siteConfig.userConfig.locales || {}
const defaultLang =
locales.root?.lang || siteConfig.userConfig.lang || 'en-US'
await task('generating sitemap', async () => {
const locales = siteConfig.userConfig.locales || {}
const defaultLang =
locales.root?.lang || siteConfig.userConfig.lang || 'en-US'
// locale directories whose pages are translations of each other
const localeDirs = Object.keys(locales).filter(
(locale) => locale !== 'root' && locales[locale].lang
)
// locale directories whose pages are translations of each other
const localeDirs = Object.keys(locales).filter(
(locale) => locale !== 'root' && locales[locale].lang
)
// group each page with its translations under a locale-independent key
const pageGroups: Record<
string,
{ lang: string; url: string; lastmod?: number }[]
> = {}
// group each page with its translations under a locale-independent key
const pageGroups: Record<
string,
{ lang: string; url: string; lastmod?: number }[]
> = {}
for (const sourcePage of siteConfig.pages) {
const page = siteConfig.rewrites.map[sourcePage] || sourcePage
const localeDir = page.split('/')[0]
for (const sourcePage of siteConfig.pages) {
const page = siteConfig.rewrites.map[sourcePage] || sourcePage
const localeDir = page.split('/')[0]
const url = page
.replace(/(^|\/)index\.md$/, '$1')
.replace(/\.md$/, siteConfig.cleanUrls ? '' : '.html')
const url = page
.replace(/(^|\/)index\.md$/, '$1')
.replace(/\.md$/, siteConfig.cleanUrls ? '' : '.html')
const key = localeDirs.includes(localeDir)
? page.slice(localeDir.length + 1)
: page
const key = localeDirs.includes(localeDir)
? page.slice(localeDir.length + 1)
: page
;(pageGroups[key] ??= []).push({
lang: locales[localeDir]?.lang || defaultLang,
url,
lastmod: pageMetaMap[page]?.lastUpdated || undefined
})
}
;(pageGroups[key] ??= []).push({
lang: locales[localeDir]?.lang || defaultLang,
url,
lastmod: pageMetaMap[page]?.lastUpdated || undefined
})
}
// translated pages link to all their variants (including themselves)
let items: SitemapItem[] = Object.values(pageGroups).flatMap((variants) =>
variants.length < 2
? { url: variants[0].url, lastmod: variants[0].lastmod }
: variants.map(({ url, lastmod }) => ({
url,
lastmod,
links: variants
}))
)
items = (await siteConfig.sitemap?.transformItems?.(items)) || items
// translated pages link to all their variants (including themselves)
let items: SitemapItem[] = Object.values(pageGroups).flatMap((variants) =>
variants.length < 2
? { url: variants[0].url, lastmod: variants[0].lastmod }
: variants.map(({ url, lastmod }) => ({
url,
lastmod,
links: variants
}))
)
items = (await siteConfig.sitemap?.transformItems?.(items)) || items
const sitemapPath = path.join(siteConfig.outDir, 'sitemap.xml')
const sitemapStream = new SitemapStream(siteConfig.sitemap)
const sitemapPath = path.join(siteConfig.outDir, 'sitemap.xml')
const sitemapStream = new SitemapStream(siteConfig.sitemap)
const writeStream = fs.createWriteStream(sitemapPath)
sitemapStream.pipe(writeStream)
items.forEach((item) => sitemapStream.write(item))
sitemapStream.end()
await new Promise((resolve, reject) =>
writeStream.on('finish', resolve).on('error', reject)
)
})
items.forEach((item) => sitemapStream.write(item))
sitemapStream.end()
await pipeline(sitemapStream, fs.createWriteStream(sitemapPath))
}
// ============================== Patched Types ===============================

@ -1,8 +1,7 @@
import { isBooleanAttr } from '@vue/shared'
import fs from 'node:fs'
import { mkdir, writeFile } from 'node:fs/promises'
import { mkdir, realpath, rm, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { minifySync, normalizePath, type Rolldown } from 'vite'
import { minify, normalizePath, type Rolldown } from 'vite'
import { version } from '../../../package.json'
import type { SiteConfig } from '../config'
import {
@ -86,7 +85,7 @@ export async function renderPage(
// resolve imports for index.js + page.md.js and inject script tags
// for them as well so we fetch everything as early as possible
// without having to wait for entry chunks to parse
...resolvePageImports(config, page, result, appChunk),
...(await resolvePageImports(config, page, result, appChunk)),
pageClientJsFileName
])
]
@ -148,7 +147,7 @@ export async function renderPage(
if (matchingChunk) {
if (!matchingChunk.code.includes('import')) {
inlinedScript = `<script type="module">${matchingChunk.code}</script>`
fs.rmSync(path.resolve(config.outDir, matchingChunk.fileName), {
await rm(path.resolve(config.outDir, matchingChunk.fileName), {
force: true
})
} else {
@ -208,7 +207,7 @@ export async function renderPage(
await writeFile(htmlFileName, transformedHtml || html)
}
function resolvePageImports(
async function resolvePageImports(
config: SiteConfig,
page: string,
result: Rolldown.RolldownOutput,
@ -220,7 +219,7 @@ function resolvePageImports(
let srcPath = path.resolve(config.srcDir, page)
try {
if (!config.vite?.resolve?.preserveSymlinks) {
srcPath = fs.realpathSync(srcPath)
srcPath = await realpath(srcPath)
}
} catch (e) {
// if the page is a virtual page generated by a dynamic route this would
@ -248,7 +247,7 @@ async function renderHead(head: HeadConfig[]): Promise<string> {
tag === 'script' &&
(attrs.type === undefined || attrs.type.includes('javascript'))
) {
innerHTML = minifySync('inline-script.js', innerHTML).code
innerHTML = (await minify('inline-script.js', innerHTML)).code
}
return `${openTag}${innerHTML}</${tag}>`
} else {

@ -1,6 +1,6 @@
import minimist from 'minimist'
import c from 'picocolors'
import { createLogger, version as viteVersion, type Logger } from 'vite'
import { createLogger } from 'vite'
import {
build,
createServer,
@ -8,10 +8,10 @@ import {
resolveConfig,
serve
} from '.'
import { version } from '../../package.json'
import { init } from './init/init'
import { clearCache } from './markdownToVue'
import { bindShortcuts } from './shortcuts'
import { logVersion } from './utils/logVersion'
const argv: any = minimist(process.argv.slice(2))
@ -23,13 +23,6 @@ Object.keys(argv).forEach((key) => {
}
})
const logVersion = (logger: Logger) => {
logger.info(
`\n ${c.green(`${c.bold('vitepress')} ${version}`)} ${c.gray(`(using vite ${viteVersion})`)}\n`,
{ clear: !logger.hasWarned }
)
}
const command = argv._[0]
const root = argv._[command ? 1 : 0]
if (root) {
@ -81,12 +74,7 @@ if (!command || command === 'dev') {
init(argv.root)
} else {
if (command === 'build') {
build(root, {
...argv,
onAfterConfigResolve(siteConfig) {
logVersion(siteConfig.logger)
}
}).catch(logErrorAndExit.bind(null, `build error:`))
build(root, argv).catch(logErrorAndExit.bind(null, `build error:`))
} else if (command === 'serve' || command === 'preview') {
serve(argv).catch(
logErrorAndExit.bind(null, `failed to start server. error:`)

@ -33,6 +33,10 @@ export * from './siteConfig'
const debug = createDebug('vitepress:config')
const supportedConfigExtensions = ['js', 'ts', 'mjs', 'mts']
const additionalConfigRE = /(?:^|\/|\\)config\.m?[jt]s$/
const additionalConfigGlob = `**/config.{js,mjs,ts,mts}`
const resolve = (root: string, file: string) =>
normalizePath(path.resolve(root, `.vitepress`, file))
@ -181,10 +185,6 @@ export async function resolveConfig(
return config as SiteConfig
}
const supportedConfigExtensions = ['js', 'ts', 'mjs', 'mts']
const additionalConfigRE = /(?:^|\/|\\)config\.m?[jt]s$/
const additionalConfigGlob = `**/config.{js,mjs,ts,mts}`
export function isAdditionalConfigFile(path: string) {
return additionalConfigRE.test(path)
}
@ -248,7 +248,7 @@ export async function resolveUserConfig(
resolve(root, `config/index.${ext}`),
resolve(root, `config.${ext}`)
])
.find(fs.existsSync)
.find((p) => fs.existsSync(p))
let userConfig: RawConfigExports = {}
let configDeps: string[] = []

@ -1,5 +1,5 @@
import matter from 'gray-matter'
import fs from 'node:fs'
import { stat } from 'node:fs/promises'
import path from 'node:path'
import pMap from 'p-map'
import { normalizePath } from 'vite'
@ -9,6 +9,7 @@ import {
mergeMarkdownLocales
} from './markdown/markdown'
import type { Awaitable, MarkdownEnv } from './shared'
import { readFile } from './utils/fs'
import { glob, normalizeGlob, type GlobOptions } from './utils/glob'
export interface ContentOptions<T = ContentData[]> {
@ -116,12 +117,12 @@ export function createContentLoader<T = ContentData[]>(
async (file) => {
if (!file.endsWith('.md')) return null
const timestamp = fs.statSync(file).mtimeMs
const timestamp = (await stat(file)).mtimeMs
const cached = cache.get(file)
if (cached && timestamp === cached.timestamp) return cached.data
const src = fs.readFileSync(file, 'utf-8')
const src = await readFile(file)
const renderExcerpt = options.excerpt
const { data: frontmatter, excerpt } = matter(

@ -9,10 +9,12 @@ import {
} from '@clack/prompts'
import template from 'lodash.template'
import fs from 'node:fs'
import { mkdir, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import c from 'picocolors'
import { slash } from '../shared'
import { readFile } from '../utils/fs'
export enum ScaffoldThemeType {
Default = 'default theme',
@ -141,10 +143,10 @@ export async function init(root?: string) {
}
)
outro(scaffold(options))
outro(await scaffold(options))
}
export function scaffold({
export async function scaffold({
root: root_ = './',
srcDir: srcDir_ = root_,
title = 'My Awesome Project',
@ -178,12 +180,12 @@ export function scaffold({
const pkgPath = path.resolve('package.json')
const userPkg = fs.existsSync(pkgPath)
? JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
? JSON.parse(await readFile(pkgPath))
: {}
const useMjs = userPkg.type !== 'module'
const renderFile = (file: string) => {
const renderFile = async (file: string) => {
const filePath = path.resolve(templateDir, file)
let targetPath = path.resolve(resolvedRoot, file)
@ -197,11 +199,11 @@ export function scaffold({
targetPath = path.resolve(resolvedSrcDir, file)
}
const content = fs.readFileSync(filePath, 'utf-8')
const content = await readFile(filePath)
const compiled = template(content)(data)
fs.mkdirSync(path.dirname(targetPath), { recursive: true })
fs.writeFileSync(targetPath, compiled)
await mkdir(path.dirname(targetPath), { recursive: true })
await writeFile(targetPath, compiled)
}
const filesToScaffold = [
@ -225,7 +227,7 @@ export function scaffold({
}
for (const file of filesToScaffold) {
renderFile(file)
await renderFile(file)
}
const tips = []
@ -260,7 +262,7 @@ export function scaffold({
scripts[`${prefix}preview`] = `vitepress preview${dir}`
Object.assign(userPkg.scripts || (userPkg.scripts = {}), scripts)
fs.writeFileSync(pkgPath, JSON.stringify(userPkg, null, 2))
await writeFile(pkgPath, JSON.stringify(userPkg, null, 2))
return `Done! Now run ${c.cyan(`${pm} run ${prefix}dev`)} and start writing.${tip}`
} else {

@ -64,14 +64,6 @@ export type { Header } from '../shared'
// not exported from @mdit/plugin-emoji, so derive it from the plugin signature
type EmojiPluginOptions = NonNullable<Parameters<typeof emojiPlugin>[1]>
// `true` and `undefined` enable a plugin with its default options - only an
// object carries user-provided plugin options
function normalizePluginOptions<T>(
value: T | boolean | undefined
): T | undefined {
return typeof value === 'boolean' ? undefined : value
}
export type ThemeOptions =
| ThemeRegistrationAny
| BuiltinTheme
@ -80,17 +72,24 @@ export type ThemeOptions =
dark: ThemeRegistrationAny | BuiltinTheme
}
// highlight is marked as any to avoid type conflicts with plugins expecting
// regular markdown-it which has sync highlight function. Such plugins will fail
// if they access highlight directly but currently none of the ones we use do that.
export type MarkdownRenderer = MarkdownItAsync & {
options: { highlight?: any }
}
export interface MarkdownOptions extends MarkdownItAsyncOptions {
/* ==================== General Options ==================== */
/**
* Configure the markdown-it instance before any plugins are applied.
*/
preConfig?: (md: MarkdownItAsync) => Awaitable<void>
preConfig?: (md: MarkdownRenderer) => Awaitable<void>
/**
* Configure the markdown-it instance after all built-in plugins are applied.
*/
config?: (md: MarkdownItAsync) => Awaitable<void>
config?: (md: MarkdownRenderer) => Awaitable<void>
/**
* Disable cache (experimental)
*/
@ -320,8 +319,6 @@ export interface MarkdownOptions extends MarkdownItAsyncOptions {
sfc?: SfcPluginOptions
}
export type MarkdownRenderer = MarkdownItAsync
// folds `locales.<index>.markdown` entries from the site config into
// `MarkdownOptions.locales` so per-locale strings reach the renderer -
// site config entries win over directly passed ones
@ -338,10 +335,7 @@ export function mergeMarkdownLocales(
return { ...options, locales: merged }
}
// highlight is marked as any to avoid type conflicts with plugins expecting
// regular markdown-it which has sync highlight function. Such plugins will fail
// if they access highlight directly but currently none of the ones we use do that.
let md: (MarkdownRenderer & { options: { highlight?: any } }) | undefined
let md: MarkdownRenderer | undefined
let _disposeHighlighter: (() => void) | undefined
export function disposeMdItInstance() {
@ -519,6 +513,10 @@ export async function createMarkdownRenderer(
if (options.component !== false) {
componentPlugin(md, normalizePluginOptions(options.component))
}
// pass an empty options object to gray-matter, otherwise it would memoize
// the results in an unbounded cache, where the key is the full file content.
// https://github.com/jonschlinkert/gray-matter/blob/310f9349381775d10a221cef903989eb5acc8843/index.js#L44-L47
;(options.frontmatter ??= {}).grayMatterOptions ??= {}
frontmatterPlugin(md, options.frontmatter)
if (options.headers) {
headersPlugin(md, {
@ -548,3 +546,11 @@ export async function createMarkdownRenderer(
return md
}
// `true` and `undefined` enable a plugin with its default options - only an
// object carries user-provided plugin options
function normalizePluginOptions<T>(
value: T | boolean | undefined
): T | undefined {
return typeof value === 'boolean' ? undefined : value
}

@ -18,6 +18,19 @@ export interface ContainerPluginOptions {
locales?: Record<string, MarkdownLocaleOptions | undefined>
}
const containerLabels = [
['tip', 'tipLabel', 'TIP'],
['info', 'infoLabel', 'INFO'],
['warning', 'warningLabel', 'WARNING'],
['danger', 'dangerLabel', 'DANGER'],
['details', 'detailsLabel', 'Details'],
['note', 'noteLabel', 'NOTE'],
['important', 'importantLabel', 'IMPORTANT'],
['caution', 'cautionLabel', 'CAUTION']
] as const
const alertMarkerRE = /^\[!([\w-]+)\]([^\n\r]*)/
export const containerPlugin = (
md: MarkdownItAsync,
options?: ContainerOptions,
@ -64,17 +77,6 @@ function titlesFor(
return (localeIndex && titles.byLocale[localeIndex]) || titles.base
}
const containerLabels = [
['tip', 'tipLabel', 'TIP'],
['info', 'infoLabel', 'INFO'],
['warning', 'warningLabel', 'WARNING'],
['danger', 'dangerLabel', 'DANGER'],
['details', 'detailsLabel', 'Details'],
['note', 'noteLabel', 'NOTE'],
['important', 'importantLabel', 'IMPORTANT'],
['caution', 'cautionLabel', 'CAUTION']
] as const
function resolveTitlesByLocale(
options?: ContainerOptions,
locales?: Record<string, MarkdownLocaleOptions | undefined>
@ -185,8 +187,6 @@ function createCodeGroupOpenRender(md: MarkdownItAsync): RenderRule {
}
}
const alertMarkerRE = /^\[!([\w-]+)\]([^\n\r]*)/
export const gitHubAlertsPlugin = (
md: MarkdownItAsync,
options?: ContainerOptions,

@ -12,6 +12,8 @@ export interface Options {
stripMarkersFromSnippets?: boolean
}
type FenceRenderer = NonNullable<MarkdownItAsync['renderer']['rules']['fence']>
/**
* raw path format: "/path/to/file.extension#region {meta} [title]"
* where #region, {meta} and [title] are optional
@ -26,26 +28,6 @@ export interface Options {
const RAW_PATH_RE =
/^(.+?)(?:#([\w-]+))?(?: ?(?:{(\d+(?:[,-]\d+)*)? ?(\S+)? ?(\S+)?}))? ?(?:\[(.+)\])?$/
export function rawPathToToken(rawPath: string) {
const [
,
filepath = '',
region = '',
lines = '',
lang = '',
attrs = '',
rawTitle = ''
] = RAW_PATH_RE.exec(rawPath) || []
const filename = filepath.split('/').pop() ?? ''
const extension = filename.includes('.') ? filename.split('.').pop()! : ''
const title = rawTitle || filename
return { filepath, extension, region, lines, lang, attrs, title }
}
const MIGHT_BE_MARKER_RE = /region/i
const MARKER_RES = [
{
@ -86,6 +68,28 @@ const MARKER_RES = [
}
]
const snippetMarker = '<<<'
export function rawPathToToken(rawPath: string) {
const [
,
filepath = '',
region = '',
lines = '',
lang = '',
attrs = '',
rawTitle = ''
] = RAW_PATH_RE.exec(rawPath) || []
const filename = filepath.split('/').pop() ?? ''
const extension = filename.includes('.') ? filename.split('.').pop()! : ''
const title = rawTitle || filename
return { filepath, extension, region, lines, lang, attrs, title }
}
export function findRegions(lines: string[], region: string) {
const returned: { start: number; end: number }[] = []
@ -146,31 +150,33 @@ export function dedent(lines: string[]): string[] {
return lines
}
export const snippetPlugin = (
export function snippetPlugin(
md: MarkdownItAsync,
srcDir: string,
options?: Options
) => {
const parser: RuleBlock = (state, startLine, _endLine, silent) => {
const CH = '<'.charCodeAt(0)
) {
const renderFence = md.renderer.rules.fence!
md.renderer.rules.fence = createSnippetRenderer(renderFence, options)
md.block.ruler.before('fence', 'snippet', createSnippetParser(srcDir))
}
function createSnippetParser(srcDir: string): RuleBlock {
return (state, startLine, _endLine, silent) => {
const pos = state.bMarks[startLine] + state.tShift[startLine]
const max = state.eMarks[startLine]
// if it's indented more than 3 spaces, it should be a code block
if (state.sCount[startLine] - state.blkIndent >= 4) {
if (
state.sCount[startLine] - state.blkIndent >= 4 ||
pos + snippetMarker.length > max ||
!state.src.startsWith(snippetMarker, pos)
) {
return false
}
for (let i = 0; i < 3; ++i) {
const ch = state.src.charCodeAt(pos + i)
if (ch !== CH || pos + i >= max) return false
}
if (silent) {
return true
}
if (silent) return true
const start = pos + 3
const start = pos + snippetMarker.length
const end = state.skipSpacesBack(max, pos)
const rawPath = state.src
@ -187,7 +193,7 @@ export const snippetPlugin = (
const token = state.push('fence', 'code', 0)
token.info = `${lang || extension}${lines ? `{${lines}}` : ''}${
title ? `[${title}]` : ''
} ${attrs ?? ''}`
} ${attrs}`
const { realPath, path: _path } = state.env as MarkdownEnv
const src = path.resolve(path.dirname(realPath ?? _path), filepath)
@ -198,44 +204,56 @@ export const snippetPlugin = (
return true
}
}
const fence = md.renderer.rules.fence!
function getFileOrError(src: string): { content: string; error?: string } {
try {
const content = fs.readFileSync(src, 'utf8').replace(/\r\n/g, '\n')
return { content }
} catch (error) {
switch ((error as NodeJS.ErrnoException).code) {
case 'ENOENT':
return { content: '', error: `Code snippet path not found: ${src}` }
case 'EISDIR':
return { content: '', error: 'Invalid code snippet option' }
default:
throw error
}
}
}
md.renderer.rules.fence = (...args) => {
function createSnippetRenderer(
renderFence: FenceRenderer,
options?: Options
): FenceRenderer {
return (...args) => {
const [tokens, idx, , { includes }] = args
const token = tokens[idx]
const { src, region } = token.meta ?? {}
if (!src) return fence(...args)
if (!src) return renderFence(...args)
if (includes) {
includes.push(src)
}
includes?.push(src)
if (!fs.existsSync(src)) {
token.content = `Code snippet path not found: ${src}`
const { content, error } = getFileOrError(src)
if (error) {
token.content = error
token.info = ''
return fence(...args)
return renderFence(...args)
}
if (!fs.statSync(src).isFile()) {
token.content = `Invalid code snippet option`
token.info = ''
return fence(...args)
}
let lines = fs.readFileSync(src, 'utf8').split(/\r?\n/)
let lines = content.split('\n')
if (region) {
const regions = findRegions(lines, region)
if (regions.length > 0) {
lines = regions.flatMap((r) => lines.slice(r.start, r.end))
} else {
if (regions.length === 0) {
token.content = `No region #${region} found in path: ${src}`
token.info = ''
return fence(...args)
return renderFence(...args)
}
lines = regions.flatMap((r) => lines.slice(r.start, r.end))
}
if (options?.stripMarkersFromSnippets) {
@ -243,8 +261,6 @@ export const snippetPlugin = (
}
token.content = dedent(lines).join('\n')
return fence(...args)
return renderFence(...args)
}
md.block.ruler.before('fence', 'snippet', parser)
}

@ -1,5 +1,6 @@
import { resolveTitleFromToken } from '@mdit-vue/shared'
import { LRUCache } from 'lru-cache'
import { hash } from 'node:crypto'
import fs from 'node:fs'
import path from 'node:path'
import { createDebug } from 'obug'
@ -24,7 +25,24 @@ import { getGitTimestamp } from './utils/getGitTimestamp'
import { processIncludes } from './utils/processIncludes'
const debug = createDebug('vitepress:md')
const cache = new LRUCache<string, MarkdownCompileResult>({ max: 1024 })
const cache = new LRUCache<string, MarkdownCompileResult>({
maxSize: 64 * 1024 * 1024,
sizeCalculation(value, key) {
return Math.max(1, 2 * (key.length + value.vueSrc.length))
}
})
const scriptRE = /<\/script>/
const scriptLangTsRE = /<\s*script[^>]*\blang=['"]ts['"][^>]*/
const scriptSetupRE = /<\s*script[^>]*\bsetup\b[^>]*/
const scriptClientRE = /<\s*script[^>]*\bclient\b[^>]*/
const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/
const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)as(\s*)default/
let __pages: string[] = []
let __dynamicRoutes = new Map<string, [string, string]>()
let __rewrites = new Map<string, string>()
let __ts: number
export interface MarkdownCompileResult {
vueSrc: string
@ -39,15 +57,9 @@ export function clearCache(relativePath?: string) {
return
}
relativePath = JSON.stringify({ relativePath }).slice(1)
cache.find((_, key) => key.endsWith(relativePath!) && cache.delete(key))
cache.find((_, key) => key.endsWith(`:${relativePath}`) && cache.delete(key))
}
let __pages: string[] = []
let __dynamicRoutes = new Map<string, [string, string]>()
let __rewrites = new Map<string, string>()
let __ts: number
function normalizeDriveLetter(file: string) {
return file.replace(/^[a-z]:/i, (drive) => drive.toLowerCase())
}
@ -87,10 +99,10 @@ function getResolutionCache(siteConfig: SiteConfig) {
export async function createMarkdownToVueRenderFn(
srcDir: string,
options: MarkdownOptions = {},
base = '/',
includeLastUpdatedData = false,
cleanUrls = false,
options: MarkdownOptions,
base: string,
includeLastUpdatedData: boolean,
cleanUrls: boolean,
siteConfig: SiteConfig
) {
const md = await createMarkdownRenderer(
@ -115,7 +127,8 @@ export async function createMarkdownToVueRenderFn(
file = rewrites.get(normalizeDriveLetter(file)) || file
const relativePath = slash(path.relative(srcDir, file))
const cacheKey = JSON.stringify({ src, ts, relativePath })
const srcHash = hash('sha256', src, 'base64url')
const cacheKey = `${srcHash}:${ts}:${relativePath}`
if (options.cache !== false) {
const cached = cache.get(cacheKey)
if (cached) {
@ -138,7 +151,7 @@ export async function createMarkdownToVueRenderFn(
// resolve includes
let includes: string[] = []
src = processIncludes(md, srcDir, src, fileOrig, includes, cleanUrls)
src = await processIncludes(md, srcDir, src, fileOrig, includes, cleanUrls)
const localeIndex = getLocaleForPath(siteConfig?.site, relativePath)
@ -280,13 +293,6 @@ export async function createMarkdownToVueRenderFn(
}
}
const scriptRE = /<\/script>/
const scriptLangTsRE = /<\s*script[^>]*\blang=['"]ts['"][^>]*/
const scriptSetupRE = /<\s*script[^>]*\bsetup\b[^>]*/
const scriptClientRE = /<\s*script[^>]*\bclient\b[^>]*/
const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/
const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)as(\s*)default/
function injectPageDataCode(tags: string[], data: PageData) {
const code = `\nexport const __pageData = JSON.parse(${JSON.stringify(
JSON.stringify(data)

@ -126,10 +126,10 @@ export async function createVitePressPlugin(
if (lastUpdated) await cacheAllGitTimestamps(srcDir)
markdownToVue = await createMarkdownToVueRenderFn(
srcDir,
markdown,
markdown ?? {},
config.base,
lastUpdated,
cleanUrls,
lastUpdated ?? false,
cleanUrls ?? false,
siteConfig
)
},

@ -12,6 +12,7 @@ import {
} from 'vite'
import type { Awaitable } from '../shared'
import { type SiteConfig, type UserConfig } from '../siteConfig'
import { readFile } from '../utils/fs'
import { glob, normalizeGlob, type GlobOptions } from '../utils/glob'
import { ModuleGraph } from '../utils/moduleGraph'
import { resolveRewrites } from './rewritesPlugin'
@ -109,7 +110,7 @@ export async function resolvePages(
siteConfig.pages?.filter((p) => !discoveredPages.has(p)) || []
const finalDynamicRoutes = [...dynamicRoutes, ...externalDynamicRoutes].sort(
(a, b) => a.path.localeCompare(b.path)
(a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
)
const finalPages = [...pages, ...externalPages].sort()
@ -148,7 +149,7 @@ export const dynamicRoutesPlugin = async (
load: {
filter: { id: /\.md$/ },
handler(id) {
async handler(id) {
const matched = config.dynamicRoutes.find((r) => r.fullPath === id)
if (matched) {
const { route, params, content } = matched
@ -157,7 +158,7 @@ export const dynamicRoutesPlugin = async (
moduleGraph.add(id, [routeFile])
moduleGraph.add(routeFile, [matched.loaderPath])
let baseContent = fs.readFileSync(routeFile, 'utf-8')
let baseContent = await readFile(routeFile)
// inject raw content
// this is intended for integration with CMS

@ -1,7 +1,5 @@
import { prefixRegex } from '@rolldown/pluginutils'
import MiniSearch from 'minisearch'
import fs from 'node:fs'
import { readFile } from 'node:fs/promises'
import path from 'node:path'
import { createDebug } from 'obug'
import type { Plugin, ViteDevServer } from 'vite'
@ -9,6 +7,7 @@ import type { SiteConfig } from '../config'
import type { DefaultTheme } from '../defaultTheme'
import { createMarkdownRenderer } from '../markdown/markdown'
import { getLocaleForPath, slash, type MarkdownEnv } from '../shared'
import { readFile } from '../utils/fs'
import { processIncludes } from '../utils/processIncludes'
const debug = createDebug('vitepress:local-search')
@ -16,6 +15,9 @@ const debug = createDebug('vitepress:local-search')
const LOCAL_SEARCH_INDEX_ID = '@localSearchIndex'
const LOCAL_SEARCH_INDEX_REQUEST_PATH = '/' + LOCAL_SEARCH_INDEX_ID
const headingRegex = /<h(\d*).*?>(.*?<a.*? href="#.*?".*?>.*?<\/a>)<\/h\1>/gi
const headingContentRegex = /(.*)<a.*? href="#(.*?)".*?>.*?<\/a>/i
interface IndexObject {
id: string
text: string
@ -50,16 +52,21 @@ export async function localSearchPlugin(
const options = siteConfig.site.themeConfig.search.options || {}
async function render(file: string) {
if (!fs.existsSync(file)) return ''
const { srcDir, cleanUrls = false } = siteConfig
const relativePath = slash(path.relative(srcDir, file))
const env: MarkdownEnv = { path: file, relativePath, cleanUrls }
const md_raw = await readFile(file, 'utf-8')
const md_src = processIncludes(md, srcDir, md_raw, file, [], cleanUrls)
const raw = await readFile(file).catch((e) => {
if (e.code === 'ENOENT') {
debug(`File not found: ${file}`)
return ''
}
throw e
})
const src = await processIncludes(md, srcDir, raw, file, [], cleanUrls)
if (options._render) {
return await options._render(md_src, env, md)
return options._render(src, env, md)
} else {
const html = await md.renderAsync(md_src, env)
const html = await md.renderAsync(src, env)
return env.frontmatter?.search === false ? '' : html
}
}
@ -128,6 +135,7 @@ export async function localSearchPlugin(
const index = getIndexByLocale(locale)
// retrieve file and split into "sections"
const html = await render(file)
if (!html) return
const sections =
// user provided generator
(await options.miniSearch?._splitIntoSections?.(file, html)) ??
@ -169,15 +177,17 @@ export async function localSearchPlugin(
)
},
config: () => ({
optimizeDeps: {
include: [
'vitepress > @vueuse/integrations/useFocusTrap',
'vitepress > mark.js/src/vanilla.js',
'vitepress > minisearch'
]
config() {
return {
optimizeDeps: {
include: [
'vitepress > @vueuse/integrations/useFocusTrap',
'vitepress > mark.js/src/vanilla.js',
'vitepress > minisearch'
]
}
}
}),
},
configureServer(_server) {
server = _server
@ -241,9 +251,6 @@ export async function localSearchPlugin(
}
}
const headingRegex = /<h(\d*).*?>(.*?<a.*? href="#.*?".*?>.*?<\/a>)<\/h\1>/gi
const headingContentRegex = /(.*)<a.*? href="#(.*?)".*?>.*?<\/a>/i
/**
* Splits HTML into sections based on headings
*/

@ -14,19 +14,6 @@ const loaderMatch = /\.data\.m?(j|t)s($|\?)/
let server: ViteDevServer
export interface LoaderModule<T = any> {
watch?: string[] | string
load: (watchedFiles: string[]) => Awaitable<T>
options?: { globOptions?: GlobOptions }
}
/**
* Helper for defining loaders with type inference
*/
export function defineLoader<T>(loader: LoaderModule<T>): LoaderModule<T> {
return loader
}
// Map from loader module id to its module info
const idToLoaderModulesMap: Record<
string,
@ -45,6 +32,19 @@ let idToPendingPromiseMap: Record<string, Promise<string> | undefined> =
Object.create(null)
let isBuild = false
export interface LoaderModule<T = any> {
watch?: string[] | string
load: (watchedFiles: string[]) => Awaitable<T>
options?: { globOptions?: GlobOptions }
}
/**
* Helper for defining loaders with type inference
*/
export function defineLoader<T>(loader: LoaderModule<T>): LoaderModule<T> {
return loader
}
export const staticDataPlugin: Plugin = {
name: 'vitepress:data',
@ -58,59 +58,9 @@ export const staticDataPlugin: Plugin = {
load: {
filter: { id: loaderMatch },
async handler(id) {
let _resolve: ((res: any) => void) | undefined
if (isBuild) {
if (idToPendingPromiseMap[id]) return idToPendingPromiseMap[id]
idToPendingPromiseMap[id] = new Promise((r) => {
_resolve = r
})
}
const base = path.dirname(id)
let watch: LoaderModule['watch']
let load: LoaderModule['load']
let options: LoaderModule['options']
const existing = idToLoaderModulesMap[id]
if (existing) {
;({ watch, load, options } = existing)
} else {
// use vite's load config util as a way to load Node.js file with
// TS & native ESM support
const res = await loadConfigFromFile({} as any, id.replace(/\?.*$/, ''))
// record deps for hmr
if (server && res) {
for (const dep of res.dependencies) {
const depPath = normalizePath(path.resolve(dep))
if (!depToLoaderModuleIdsMap[depPath]) {
depToLoaderModuleIdsMap[depPath] = new Set()
}
depToLoaderModuleIdsMap[depPath].add(id)
}
}
const loaderModule = res?.config as LoaderModule
watch = normalizeGlob(loaderModule.watch, base)
load = loaderModule.load
options = loaderModule.options || {}
}
// load the data
const watchedFiles = await glob(watch, {
absolute: true,
...options.globOptions
})
const data = await load(watchedFiles)
// record loader module for HMR
if (server) idToLoaderModulesMap[id] = { watch, load, options }
const result = `export const data = JSON.parse(${JSON.stringify(JSON.stringify(data))})`
if (_resolve) _resolve(result)
return result
handler(id) {
if (isBuild) return (idToPendingPromiseMap[id] ??= loadData(id))
return loadData(id)
}
},
@ -146,3 +96,47 @@ export const staticDataPlugin: Plugin = {
return modules.length ? [...existingMods, ...modules] : undefined
}
}
async function loadData(id: string): Promise<string> {
const base = path.dirname(id)
let watch: LoaderModule['watch']
let load: LoaderModule['load']
let options: LoaderModule['options']
const existing = idToLoaderModulesMap[id]
if (existing) {
;({ watch, load, options } = existing)
} else {
// use vite's load config util as a way to load Node.js file with
// TS & native ESM support
const res = await loadConfigFromFile({} as any, id.replace(/\?.*$/, ''))
// record deps for hmr
if (server && res) {
for (const dep of res.dependencies) {
const depPath = normalizePath(path.resolve(dep))
if (!depToLoaderModuleIdsMap[depPath]) {
depToLoaderModuleIdsMap[depPath] = new Set()
}
depToLoaderModuleIdsMap[depPath].add(id)
}
}
const loaderModule = res?.config as LoaderModule
watch = normalizeGlob(loaderModule.watch, base)
load = loaderModule.load
options = loaderModule.options || {}
}
// load the data
const watchedFiles = await glob(watch, {
absolute: true,
...options.globOptions
})
const data = await load(watchedFiles)
// record loader module for HMR
if (server) idToLoaderModulesMap[id] = { watch, load, options }
return `export const data = JSON.parse(${JSON.stringify(JSON.stringify(data))})`
}

@ -1,21 +1,10 @@
import compression from '@polka/compression'
import fs from 'node:fs'
import { once } from 'node:events'
import path from 'node:path'
import polka, { type IOptions } from 'polka'
import sirv from 'sirv'
import { resolveConfig } from '../config'
function trimChar(str: string, char: string) {
while (str.charAt(0) === char) {
str = str.substring(1)
}
while (str.charAt(str.length - 1) === char) {
str = str.substring(0, str.length - 1)
}
return str
}
import { readFile } from '../utils/fs'
export interface ServeOptions {
base?: string
@ -26,14 +15,17 @@ export interface ServeOptions {
export async function serve(options: ServeOptions = {}) {
const port = options.port ?? 4173
const config = await resolveConfig(options.root, 'serve', 'production')
const base = trimChar(options?.base ?? config?.site?.base ?? '', '/')
const base = (options?.base ?? config?.site?.base ?? '').replace(
/^\/+|\/+$/g,
''
)
const notAnAsset = (pathname: string) =>
!pathname.includes(`/${config.assetsDir}/`)
const notFound = fs.readFileSync(path.resolve(config.outDir, './404.html'))
const notFound = await readFile(path.resolve(config.outDir, './404.html'))
const onNoMatch: IOptions['onNoMatch'] = (req, res) => {
res.statusCode = 404
if (notAnAsset(req.path)) res.write(notFound.toString())
if (notAnAsset(req.path)) res.write(notFound)
res.end()
}
@ -51,19 +43,15 @@ export async function serve(options: ServeOptions = {}) {
}
})
if (base) {
return polka({ onNoMatch })
.use(base, compress, serve)
.listen(port, () => {
config.logger.info(
`Built site served at http://localhost:${port}/${base}/`
)
})
} else {
return polka({ onNoMatch })
.use(compress, serve)
.listen(port, () => {
config.logger.info(`Built site served at http://localhost:${port}/`)
})
}
const app = base
? polka({ onNoMatch }).use(base, compress, serve)
: polka({ onNoMatch }).use(compress, serve)
app.listen(port)
await once(app.server, 'listening')
config.logger.info(
`Built site served at http://localhost:${port}/${base ? `${base}/` : ''}`
)
return app
}

@ -11,6 +11,49 @@ export type CLIShortcut = {
): Awaitable<void>
}
const SHORTCUTS: CLIShortcut[] = [
{
key: 'r',
description: 'restart the server',
async action(server, restartServer) {
server.config.logger.info(c.green(`restarting server...\n`), {
clear: true,
timestamp: true
})
await restartServer()
}
},
{
key: 'u',
description: 'show server url',
action(server) {
server.config.logger.info('')
server.printUrls()
}
},
{
key: 'o',
description: 'open in browser',
action(server) {
server.openBrowser()
}
},
{
key: 'c',
description: 'clear console',
action(server) {
server.config.logger.clearScreen('error')
}
},
{
key: 'q',
description: 'quit',
async action(server) {
await server.close().finally(() => process.exit())
}
}
]
export function bindShortcuts(
server: ViteDevServer,
restartServer: () => Promise<void>
@ -69,46 +112,3 @@ export function bindShortcuts(
process.stdin.setRawMode(false)
})
}
const SHORTCUTS: CLIShortcut[] = [
{
key: 'r',
description: 'restart the server',
async action(server, restartServer) {
server.config.logger.info(c.green(`restarting server...\n`), {
clear: true,
timestamp: true
})
await restartServer()
}
},
{
key: 'u',
description: 'show server url',
action(server) {
server.config.logger.info('')
server.printUrls()
}
},
{
key: 'o',
description: 'open in browser',
action(server) {
server.openBrowser()
}
},
{
key: 'c',
description: 'clear console',
action(server) {
server.config.logger.clearScreen('error')
}
},
{
key: 'q',
description: 'quit',
async action(server) {
await server.close().finally(() => process.exit())
}
}
]

@ -1,3 +1,23 @@
/*
export function deserializeFunctions(value: any): any {
if (Array.isArray(value)) {
return value.map(deserializeFunctions)
} else if (typeof value === 'object' && value !== null) {
return Object.keys(value).reduce((acc, key) => {
acc[key] = deserializeFunctions(value[key])
return acc
}, {} as any)
} else if (typeof value === 'string' && value.startsWith('_vp-fn_')) {
return new Function(`return ${value.slice(7)}`)()
} else {
return value
}
}
*/
export const deserializeFunctions =
'function deserializeFunctions(r){return Array.isArray(r)?r.map(deserializeFunctions):typeof r=="object"&&r!==null?Object.keys(r).reduce((t,n)=>(t[n]=deserializeFunctions(r[n]),t),{}):typeof r=="string"&&r.startsWith("_vp-fn_")?new Function(`return ${r.slice(7)}`)():r}'
export function serializeFunctions(value: any, key?: string): any {
if (Array.isArray(value)) {
return value.map((v) => serializeFunctions(v))
@ -20,23 +40,3 @@ export function serializeFunctions(value: any, key?: string): any {
return value
}
}
/*
export function deserializeFunctions(value: any): any {
if (Array.isArray(value)) {
return value.map(deserializeFunctions)
} else if (typeof value === 'object' && value !== null) {
return Object.keys(value).reduce((acc, key) => {
acc[key] = deserializeFunctions(value[key])
return acc
}, {} as any)
} else if (typeof value === 'string' && value.startsWith('_vp-fn_')) {
return new Function(`return ${value.slice(7)}`)()
} else {
return value
}
}
*/
export const deserializeFunctions =
'function deserializeFunctions(r){return Array.isArray(r)?r.map(deserializeFunctions):typeof r=="object"&&r!==null?Object.keys(r).reduce((t,n)=>(t[n]=deserializeFunctions(r[n]),t),{}):typeof r=="string"&&r.startsWith("_vp-fn_")?new Function(`return ${r.slice(7)}`)():r}'

@ -0,0 +1,20 @@
import { readFile as fsReadFile } from 'node:fs/promises'
import { setTimeout } from 'node:timers/promises'
const retryCodes = new Set(['EMFILE', 'ENFILE'])
/**
* Reads a file as utf8, retrying with backoff when the process is
* temporarily out of file descriptors (EMFILE/ENFILE).
*/
export async function readFile(file: string): Promise<string> {
for (let attempt = 0; ; attempt++) {
try {
return await fsReadFile(file, 'utf8')
} catch (e) {
const code = (e as NodeJS.ErrnoException).code
if (attempt >= 9 || !code || !retryCodes.has(code)) throw e
await setTimeout(2 ** attempt * 10)
}
}
}

@ -1,4 +1,5 @@
import { spawn, sync } from 'cross-spawn'
import { once } from 'node:events'
import fs from 'node:fs'
import path from 'node:path'
import { Transform, type TransformCallback } from 'node:stream'
@ -120,23 +121,17 @@ export async function cacheAllGitTimestamps(
...pathspec
]
return new Promise((resolve, reject) => {
cache.clear()
const child = spawn('git', args, { cwd: root })
cache.clear()
const child = spawn('git', args, { cwd: root })
const records = child.stdout.pipe(new GitLogParser())
child.on('error', (err) => records.destroy(err))
child.stdout
.pipe(new GitLogParser())
.on('data', (rec: GitLogRecord) => {
for (const file of rec.files) {
const slashed = slash(path.resolve(gitRoot, file))
if (!cache.has(slashed)) cache.set(slashed, rec.ts)
}
})
.on('error', reject)
.on('end', resolve)
child.on('error', reject)
})
for await (const rec of records as AsyncIterable<GitLogRecord>) {
for (const file of rec.files) {
const slashed = slash(path.resolve(gitRoot, file))
if (!cache.has(slashed)) cache.set(slashed, rec.ts)
}
}
}
export async function getGitTimestamp(file: string): Promise<number> {
@ -148,24 +143,19 @@ export async function getGitTimestamp(file: string): Promise<number> {
if (!fs.existsSync(file)) return 0
return new Promise((resolve, reject) => {
const child = spawn(
'git',
['log', '-1', '--pretty=%at', '--', path.basename(file)],
{ cwd: path.dirname(file) }
)
let output = ''
child.stdout.on('data', (d) => (output += String(d)))
const child = spawn(
'git',
['log', '-1', '--pretty=%at', '--', path.basename(file)],
{ cwd: path.dirname(file) }
)
child.on('close', () => {
const ts = Number.parseInt(output.trim(), 10) * 1000
if (!(ts > 0)) return resolve(0)
let output = ''
child.stdout.on('data', (d) => (output += String(d)))
await once(child, 'close')
cache.set(file, ts)
resolve(ts)
})
const ts = Number.parseInt(output.trim(), 10) * 1000
if (!(ts > 0)) return 0
child.on('error', reject)
})
cache.set(file, ts)
return ts
}

@ -0,0 +1,10 @@
import c from 'picocolors'
import { version as viteVersion, type Logger } from 'vite'
import { version } from '../../../package.json'
export function logVersion(logger: Logger) {
logger.info(
`\n ${c.green(`${c.bold('vitepress')} ${version}`)} ${c.gray(`(using vite ${viteVersion})`)}\n`,
{ clear: !logger.hasWarned }
)
}

@ -1,9 +1,9 @@
import matter from 'gray-matter'
import type { MarkdownItAsync } from 'markdown-it-async'
import fs from 'node:fs'
import { replaceAsync, type MarkdownItAsync } from 'markdown-it-async'
import path from 'node:path'
import { findRegions } from '../markdown/plugins/snippet'
import { slash, type MarkdownEnv } from '../shared'
import { readFile } from './fs'
export function processIncludes(
md: MarkdownItAsync,
@ -11,13 +11,14 @@ export function processIncludes(
src: string,
file: string,
includes: string[],
cleanUrls: boolean
): string {
cleanUrls: boolean,
ancestors: string[] = []
): Promise<string> {
const includesRE = /<!--\s*@include:\s*(.*?)\s*-->/g
const regionRE = /#([^\s\{]+)$/
const rangeRE = /\{(\d*),(\d*)\}$/
return src.replace(includesRE, (m: string, m1: string) => {
return replaceAsync(src, includesRE, async (m: string, m1: string) => {
if (!m1.length) return m
const rangeMeta = m1.match(rangeRE)
@ -35,7 +36,11 @@ export function processIncludes(
? path.join(srcDir, m1.slice(m1[1] === '/' ? 2 : 1))
: path.join(path.dirname(file), m1)
let content = fs.readFileSync(includePath, 'utf-8')
// leave circular includes unexpanded — only repeats along the ancestor
// chain are cycles, the same file may still be included by siblings
if (includePath === file || ancestors.includes(includePath)) return m
let content = await readFile(includePath)
// for markdown files, if a range is used without a region,
// the line numbers must account for the frontmatter,
@ -104,7 +109,8 @@ export function processIncludes(
lines.join('\n'),
includePath,
includes,
cleanUrls
cleanUrls,
[...ancestors, file]
)
})
}

@ -3,16 +3,21 @@ import ora from 'ora'
export const okMark = '\x1b[32m✓\x1b[0m'
export const failMark = '\x1b[31m✗\x1b[0m'
export async function task(taskName: string, task: () => Promise<void>) {
export async function task<T>(
taskName: string,
task: () => Promise<T>
): Promise<T> {
const spinner = ora({ discardStdin: false })
spinner.start(taskName + '...')
let result: T
try {
await task()
result = await task()
} catch (e) {
spinner.stopAndPersist({ symbol: failMark })
throw e
}
spinner.stopAndPersist({ symbol: okMark })
return result
}

@ -37,6 +37,14 @@ const HASH_WITHOUT_FRAGMENT_RE = /#.*?(?=:~:|$)/
const HASH_OR_QUERY_RE = /[?#].*$/
const INDEX_OR_EXT_RE = /(?:(^|\/)index)?(?:\.(?:md|html))?$/
// https://github.com/rollup/rollup/blob/fec513270c6ac350072425cc045db367656c623b/src/utils/sanitizeFileName.ts
const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g
const DRIVE_LETTER_REGEX = /^[a-z]:/i
const KNOWN_EXTENSIONS = new Set()
const shellLangs = ['shellscript', 'shell', 'bash', 'sh', 'zsh']
export const inBrowser = typeof document !== 'undefined'
export const notFoundPageData: PageData = {
@ -217,11 +225,6 @@ export function mergeHead(...headArrays: HeadConfig[][]): HeadConfig[] {
return merged
}
// https://github.com/rollup/rollup/blob/fec513270c6ac350072425cc045db367656c623b/src/utils/sanitizeFileName.ts
const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g
const DRIVE_LETTER_REGEX = /^[a-z]:/i
export function sanitizeFileName(name: string): string {
const match = DRIVE_LETTER_REGEX.exec(name)
const driveLetter = match ? match[0] : ''
@ -239,8 +242,6 @@ export function slash(p: string): string {
return p.replace(/\\/g, '/')
}
const KNOWN_EXTENSIONS = new Set()
export function treatAsHtml(filename: string): boolean {
if (KNOWN_EXTENSIONS.size === 0) {
const extraExts =
@ -368,7 +369,6 @@ export function isObject(value: unknown): value is ObjectType {
return Object.prototype.toString.call(value) === '[object Object]'
}
const shellLangs = ['shellscript', 'shell', 'bash', 'sh', 'zsh']
export function isShell(lang: string): boolean {
return shellLangs.includes(lang)
}

Loading…
Cancel
Save