From 0dd781ba422625f0a0bdf447dbd4c38ed79853b3 Mon Sep 17 00:00:00 2001 From: Dania Rifki Date: Sun, 11 Oct 2026 13:37:28 +0700 Subject: [PATCH 1/2] feat: integrate og-image generation f9or embeds --- doc/README.md | 8 + doc/astro.config.mjs | 96 +++ doc/package.json | 3 + ...@alfanjauhari__astro-og-images@0.2.0.patch | 63 ++ doc/pnpm-lock.yaml | 644 ++++++++++++++++++ doc/pnpm-workspace.yaml | 2 + doc/scripts/check-docs.mjs | 65 +- doc/src/components/Head.astro | 50 +- doc/src/styles/custom.css | 14 +- 9 files changed, 933 insertions(+), 12 deletions(-) create mode 100644 doc/patches/@alfanjauhari__astro-og-images@0.2.0.patch diff --git a/doc/README.md b/doc/README.md index e32bf9f..9447b8c 100755 --- a/doc/README.md +++ b/doc/README.md @@ -23,6 +23,14 @@ Each section and heading has a unique anchor. The site uses [Starlight](https://starlight.astro.build/)'s default theme. Navigation and locale settings are in `astro.config.mjs`. Set `DOCS_BASE` when deploying beneath a subpath. Production files are written to `dist/`. +Body text and controls use Fontsource Noto Sans and Noto Sans SC; headings use Noto Serif. + +[`@alfanjauhari/astro-og-images`](https://github.com/alfanjauhari/alfanjauhari.com/tree/main/packages/astro-og-images) +generates a 1200 × 630 PNG preview beside each page during the build. The shared +head component adds Open Graph and Twitter image metadata. Previews use the page +title and description, the logo, and Xiaoyu. Titles use Noto Serif, and descriptions +use Noto Sans. Both families include Chinese fonts. +The pnpm patch keeps image path checks and 404 image URLs compatible with `DOCS_BASE`. Run `make wasm` from the repository root to build the browser compiler into `public/js/`. Without those generated files, the compiler shows the build command. diff --git a/doc/astro.config.mjs b/doc/astro.config.mjs index 83f6881..85c2a1f 100644 --- a/doc/astro.config.mjs +++ b/doc/astro.config.mjs @@ -4,9 +4,52 @@ import { defineConfig } from "astro/config"; import starlight from "@astrojs/starlight"; import vue from "@astrojs/vue"; +import ogImages, { element, Renderer } from "@alfanjauhari/astro-og-images"; import { locales } from "./src/locales.mjs"; +const ogRenderer = new Renderer(); +for (const [name, font] of [ + [ + "Noto Serif", + "@fontsource/noto-serif/files/noto-serif-latin-700-normal.woff2", + ], + [ + "Noto Serif", + "@fontsource/noto-serif/files/noto-serif-latin-ext-700-normal.woff2", + ], + [ + "Noto Serif SC", + "@fontsource/noto-serif-sc/files/noto-serif-sc-chinese-simplified-700-normal.woff2", + ], + ["Noto Sans", "@fontsource/noto-sans/files/noto-sans-latin-400-normal.woff2"], + [ + "Noto Sans", + "@fontsource/noto-sans/files/noto-sans-latin-ext-400-normal.woff2", + ], + [ + "Noto Sans SC", + "@fontsource/noto-sans-sc/files/noto-sans-sc-chinese-simplified-400-normal.woff2", + ], +]) { + await ogRenderer.registerFont({ + name, + data: readFileSync(new URL(import.meta.resolve(font))), + }); +} +const ogMascot = `data:image/png;base64,${readFileSync( + new URL( + "./public/image/mascot/electrichearts_20260211A_yuescript_xiaoyu.png", + import.meta.url, + ), +).toString("base64")}`; +const ogLogo = `data:image/svg+xml;base64,${Buffer.from( + readFileSync( + new URL("./public/image/yuescript.svg", import.meta.url), + "utf8", + ).replaceAll("fill:#b4ac8f", "fill:#ffffff"), +).toString("base64")}`; + // The sidebar config runs before Astro loads content collections. const allInOneLabels = Object.fromEntries( Object.values(locales).map(({ lang }) => [ @@ -44,6 +87,59 @@ export default defineConfig({ trailingSlash: "always", redirects, integrations: [ + ogImages({ + format: "png", + width: 1200, + height: 630, + renderer: ogRenderer, + emoji: "from-font", + render: ({ title, description }) => + element( + "div", + { + tw: "flex h-full w-full flex-col bg-[#0b0e14] p-16 text-[#f1f5f9]", + style: { fontFamily: "Noto Serif, Noto Serif SC", fontWeight: 700 }, + }, + element( + "div", + { + tw: "flex items-center gap-3 text-[32px] leading-[1.2] text-[#d4ceb8]", + }, + element("img", { src: ogLogo, width: 40, height: 34 }), + "YueScript", + ), + element( + "div", + { tw: "flex flex-1 items-center gap-12" }, + element( + "div", + { tw: "flex flex-1 flex-col gap-6" }, + element( + "div", + { + tw: "leading-[1.2] text-[#d4ceb8]", + style: { fontSize: title.length > 60 ? 48 : 64 }, + }, + title, + ), + description && + element( + "div", + { + tw: "text-[26px] leading-[1.4] text-[#e2e8f0]", + style: { + fontFamily: "Noto Sans, Noto Sans SC", + fontWeight: 400, + }, + }, + description, + ), + ), + element("img", { src: ogMascot, width: 250, height: 350 }), + ), + element("div", { tw: "text-[22px] text-[#f9c749]" }, "yuescript.org"), + ), + }), { name: "legacy-html-redirects", hooks: { diff --git a/doc/package.json b/doc/package.json index 6249bf6..8e53cf7 100755 --- a/doc/package.json +++ b/doc/package.json @@ -23,6 +23,7 @@ "typescript": "^6.0.3" }, "dependencies": { + "@alfanjauhari/astro-og-images": "0.2.0", "@astrojs/starlight": "^0.42.5", "@astrojs/vue": "^7.0.3", "@codemirror/commands": "^6.10.3", @@ -30,6 +31,8 @@ "@codemirror/legacy-modes": "^6.5.4", "@codemirror/state": "^6.6.0", "@codemirror/view": "^6.40.0", + "@fontsource/noto-sans": "^5.3.0", + "@fontsource/noto-sans-sc": "^5.3.0", "@fontsource/noto-serif": "^5.3.0", "@fontsource/noto-serif-sc": "^5.3.0", "@lezer/highlight": "^1.2.5", diff --git a/doc/patches/@alfanjauhari__astro-og-images@0.2.0.patch b/doc/patches/@alfanjauhari__astro-og-images@0.2.0.patch new file mode 100644 index 0000000..55908a3 --- /dev/null +++ b/doc/patches/@alfanjauhari__astro-og-images@0.2.0.patch @@ -0,0 +1,63 @@ +diff --git a/dist/index.js b/dist/index.js +index d282c2db996ea10473714cf63285693e5ad8b0f1..e2a5795fedc3412207a9ccc20456efcbec051db7 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -75,9 +75,13 @@ function getImagePath(image) { + return new URL(image, "https://og-image.invalid").pathname.slice(1); + } + function ogImages(options) { ++ let base = "/"; + return { + name: "og-images", + hooks: { ++ "astro:config:done": ({ config }) => { ++ base = config.base; ++ }, + "astro:build:done": async ({ dir, logger, pages }) => { + const outputDir = fileURLToPath(dir); + await Promise.all( +@@ -90,7 +94,7 @@ function ogImages(options) { + const imageBuffer = await render(element2, options); + const imageFile = htmlFile.replace(/\.html$/, `.${options.format}`); + await writeFile(imageFile, imageBuffer); +- const expectedImagePath = getRelativePath(outputDir, imageFile); ++ const expectedImagePath = path.posix.join(base, getRelativePath(outputDir, imageFile)).replace(/^\/+/, ""); + const actualImagePath = getImagePath(pageDetails.image); + if (actualImagePath !== expectedImagePath) { + throw new Error( +diff --git a/dist/path.d.ts b/dist/path.d.ts +index 7fb0322f126106415b23b0d2943b4c2b5a44403a..863eb351a03efbe2409e404c7b19b2cee0546eeb 100644 +--- a/dist/path.d.ts ++++ b/dist/path.d.ts +@@ -1,6 +1,7 @@ + import { OutputFormat } from 'takumi-js'; + +-declare function getOgImagePath({ format, site, url, }: { ++declare function getOgImagePath({ base, format, site, url, }: { ++ base?: string; + format?: OutputFormat; + site: URL | undefined; + url: URL; +diff --git a/dist/path.js b/dist/path.js +index 736011d96fb43cd28a4c57dcbb7422cf7e2423e8..07c415a779e0b8c7d8bba97ca3ff3ddbf252416b 100644 +--- a/dist/path.js ++++ b/dist/path.js +@@ -1,5 +1,6 @@ + // src/path.ts + function getOgImagePath({ ++ base = "/", + format = "webp", + site, + url +@@ -15,8 +16,9 @@ function getOgImagePath({ + } else { + target += `.${format}`; + } +- if (target === `/404/index.${format}`) return new URL(`404.${format}`, site).toString(); +- if (target === `/500/index.${format}`) return new URL(`500.${format}`, site).toString(); ++ const basePath = base.replace(/\/?$/, "/"); ++ if (target === `${basePath}404/index.${format}`) return new URL(`${basePath}404.${format}`, site).toString(); ++ if (target === `${basePath}500/index.${format}`) return new URL(`${basePath}500.${format}`, site).toString(); + return new URL(target.slice(1), site).toString(); + } + export { diff --git a/doc/pnpm-lock.yaml b/doc/pnpm-lock.yaml index 68f160d..0a302a3 100644 --- a/doc/pnpm-lock.yaml +++ b/doc/pnpm-lock.yaml @@ -4,9 +4,15 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + "@alfanjauhari/astro-og-images@0.2.0": 21946ac883dbe7a81731461757ca1e71371ce7f22e0565e12a7603e0c717ae07 + importers: .: dependencies: + "@alfanjauhari/astro-og-images": + specifier: 0.2.0 + version: 0.2.0(patch_hash=21946ac883dbe7a81731461757ca1e71371ce7f22e0565e12a7603e0c717ae07)(astro@7.3.5(@types/node@24.19.1)(yaml@2.9.1))(csstype@3.2.3) "@astrojs/starlight": specifier: ^0.42.5 version: 0.42.5(astro@7.3.5(@types/node@24.19.1)(yaml@2.9.1))(typescript@6.0.3) @@ -28,6 +34,12 @@ importers: "@codemirror/view": specifier: ^6.40.0 version: 6.43.13 + "@fontsource/noto-sans": + specifier: ^5.3.0 + version: 5.3.0 + "@fontsource/noto-sans-sc": + specifier: ^5.3.0 + version: 5.3.0 "@fontsource/noto-serif": specifier: ^5.3.0 version: 5.3.0 @@ -58,6 +70,42 @@ importers: version: 6.0.3 packages: + "@alfanjauhari/astro-og-images@0.2.0": + resolution: + { + integrity: sha512-rqgKPhDLzw5eX/B91JuhFgbw86WkVap6QRLPKpQV/9qkxoT5CiiTgulGaanofj7ksDIaJYS/cBmyyWZ6Pqwtag==, + } + engines: { node: ">=24.19.0" } + peerDependencies: + astro: ^7.2.0 + + "@asamuzakjp/css-color@5.1.11": + resolution: + { + integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==, + } + engines: { node: ^20.19.0 || ^22.12.0 || >=24.0.0 } + + "@asamuzakjp/dom-selector@7.1.1": + resolution: + { + integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==, + } + engines: { node: ^20.19.0 || ^22.12.0 || >=24.0.0 } + + "@asamuzakjp/generational-cache@1.0.1": + resolution: + { + integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==, + } + engines: { node: ^20.19.0 || ^22.12.0 || >=24.0.0 } + + "@asamuzakjp/nwsapi@2.3.9": + resolution: + { + integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==, + } + "@astrojs/astro2tsx@0.1.2": resolution: { @@ -607,6 +655,13 @@ packages: } engines: { node: ">=6.9.0" } + "@bramus/specificity@2.4.3": + resolution: + { + integrity: sha512-0GEz8qJWokkUVYQYRukmkSHIFNoBH8QTreeHrAFbvz1XFYc3Y+puhMs8PQ5x8GNugiC2l5jvDBXKLbBIByeSBw==, + } + hasBin: true + "@bruits/satteri-darwin-arm64@0.10.5": resolution: { @@ -734,6 +789,60 @@ packages: integrity: sha512-sihaFrUzAsYBQsL9J2t69y8nfMQGwcYmggAZsk+kjPbjYZMyuf2hU8tUNTZ+P+isb6XRr8JE22TZlJxBoVdH1A==, } + "@csstools/color-helpers@6.1.2": + resolution: + { + integrity: sha512-grhRy3OKmniaAEKXMjua5z/EODX0MSqBGjunw8+j/3HQjOnahs2AGhvEOIYVUWcU6ScApbhLhVrQTX8XqrMrow==, + } + engines: { node: ">=20.19.0" } + + "@csstools/css-calc@3.4.3": + resolution: + { + integrity: sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.2 + "@csstools/css-tokenizer": ^4.0.2 + + "@csstools/css-color-parser@4.2.6": + resolution: + { + integrity: sha512-iiPQ3iRWwnJkeEn6RIu6SJPr7hYrLz6XZ9s/QZl+2/LI5KQVjpl2fdmDSZKuD4xP6GMmMPgHFFXg6k1Wkz0Trg==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + "@csstools/css-parser-algorithms": ^4.0.2 + "@csstools/css-tokenizer": ^4.0.2 + + "@csstools/css-parser-algorithms@4.0.2": + resolution: + { + integrity: sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==, + } + engines: { node: ">=20.19.0" } + peerDependencies: + "@csstools/css-tokenizer": ^4.0.2 + + "@csstools/css-syntax-patches-for-csstree@1.1.15": + resolution: + { + integrity: sha512-J0u7HkVl2nzSlhsiTOp4AmwcUQ3D+mGEEKfBy/7To5/y7F2OHwyLrXfrhR0SMgr4p5Lo+eaMVSeai24zUcBIxA==, + } + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + "@csstools/css-tokenizer@4.0.2": + resolution: + { + integrity: sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==, + } + engines: { node: ">=20.19.0" } + "@ctrl/tinycolor@4.2.1": resolution: { @@ -1053,6 +1162,18 @@ packages: cpu: [x64] os: [win32] + "@exodus/bytes@1.16.0": + resolution: + { + integrity: sha512-IcpW84uEn3N7ETtNZMlxKhfl6Pec8rUNGOTBtWbK1FKhJxIFAptZyVrvVRVBimAJxJCgc3PxepxkdWWG4DVzfA==, + } + engines: { node: ^20.19.0 || ^22.12.0 || >=24.0.0 } + peerDependencies: + "@noble/hashes": ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + "@noble/hashes": + optional: true + "@expressive-code/core@0.44.2": resolution: { @@ -1077,6 +1198,18 @@ packages: integrity: sha512-SNI7EgUSiADqbqUzzMbj1ZeC/r2pa5eg3KyFOGAd3a7EsIA4qzjXQLp+ODWQtHVDKFCdGs4J3ovSJUqvKIK96g==, } + "@fontsource/noto-sans-sc@5.3.0": + resolution: + { + integrity: sha512-HeqIlGm0+ohOKxZLuHj1qW6r6avHH0OWdKERAcSDI0RQ+MXrteuLKA+M+5eOA8rYy0MFvOR5AT0fQo2rUkye0Q==, + } + + "@fontsource/noto-sans@5.3.0": + resolution: + { + integrity: sha512-fBCog2PY7DiVVTEEqtI/Qdinx/knobHYfoGpjFXdfBX5RoJaBp1Prw2G75p0OIsdlMZg3cLo0c+YbIUYOxnQJw==, + } + "@fontsource/noto-serif-sc@5.3.0": resolution: { @@ -1691,6 +1824,121 @@ packages: integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==, } + "@takumi-rs/core-darwin-arm64@2.14.0": + resolution: + { + integrity: sha512-To8/1EFFVM40V3FlkQIkOl2Ge/xg6bP8KWlxoeBUEusJvjZ3d3PfmKoVmG9erM3oh7ztzw1u3nUXQqLFTNeJBQ==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + + "@takumi-rs/core-darwin-x64@2.14.0": + resolution: + { + integrity: sha512-PQ5CiWB7gxap2NElJt9xzoqP0Bzm93SnY//x8OpXuiRQmsW8dOdk5M7mPpJOghebxfRuDc3YIcOEEcsQFpmncQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + + "@takumi-rs/core-linux-arm64-gnu@2.14.0": + resolution: + { + integrity: sha512-2YKku0pJd63ojLKRY8UXerN/NCGhcdyc+HRxbFk27B4enMtxjPPKGgT6xVjBvW1J8vxsOo/eRSGMq2nDq5REBQ==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + libc: [glibc] + + "@takumi-rs/core-linux-arm64-musl@2.14.0": + resolution: + { + integrity: sha512-0XJyEleC6qzxxDOHhobo3TjMddOEvnI3uwSDNq8EH40SUp+Qw71DTBNXqwtwT/NPDj50f8bD3w/M54a86eK3Ow==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + libc: [musl] + + "@takumi-rs/core-linux-x64-gnu@2.14.0": + resolution: + { + integrity: sha512-fINzz8oeamnFqbg75xgOXMrzljhI8BmCf2SZl31dUr+o1oRWUuQyTBm2RjSl2nKjamHd71IoQRo3ilAUqNkadg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + libc: [glibc] + + "@takumi-rs/core-linux-x64-musl@2.14.0": + resolution: + { + integrity: sha512-aMDWTKRSnShorZHx/rFFfk7ovnK8kffmqprwYppNIG25DaTf90GN3CYsFlgGAIRYv0aJ1CbMGjRjzxu2raorYw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + libc: [musl] + + "@takumi-rs/core-win32-arm64-msvc@2.14.0": + resolution: + { + integrity: sha512-apuJ7bT+3tZO6PtZeuuMZ2Qkkxbi+lTniT+iAAX0lGziuTfoguIvDMhbQtQGKUbg7WUE2tqGixOHdUgab7KKCA==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + + "@takumi-rs/core-win32-x64-msvc@2.14.0": + resolution: + { + integrity: sha512-s5FK8ntDdvIBPCro3OXQEWsHpMYwRLU3JLPdgZK8eILSKjCg+NenAiGDZdU+HmObuqRQn9Q3PYjMc1qwkBzmeQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@takumi-rs/core@2.14.0": + resolution: + { + integrity: sha512-PGgLdS+A1xaxPhJtYy/d+k5GYw9MOcwcLMs2zT4RGNkw28FxN+WWHvR1+nnn34rdyD6sG6K2W+50oQZtQV1h/g==, + } + engines: { node: ">=18" } + peerDependencies: + csstype: "*" + peerDependenciesMeta: + csstype: + optional: true + + "@takumi-rs/helpers@2.14.0": + resolution: + { + integrity: sha512-jFIpuWfBMAxlULx3tEk2V+FCs1CLOMo2/c7h3I2P+eNzMmxwE9TJ/BtwJVghnLlFW1M88QW/ZOY/E7MqXACWJA==, + } + engines: { node: ">=18" } + peerDependencies: + preact: ^10.0.0 + react: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + preact: + optional: true + react: + optional: true + + "@takumi-rs/wasm@2.14.0": + resolution: + { + integrity: sha512-ncaNPVl9CL47g5eOyQp1kMPzc0GsUDbQdxRURvNBzMpUC2u6ImeMwJN6ZXiu3tqfSVgYZP1aU0Ntkg+q1S2d4g==, + } + engines: { node: ">=20.19" } + peerDependencies: + csstype: "*" + peerDependenciesMeta: + csstype: + optional: true + "@tybys/wasm-util@0.10.4": resolution: { @@ -1751,6 +1999,12 @@ packages: integrity: sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==, } + "@types/react@19.3.0": + resolution: + { + integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==, + } + "@types/sax@1.2.7": resolution: { @@ -2118,6 +2372,12 @@ packages: integrity: sha512-KLw+H/gd2p4zly1X7Yh/qziuyae5/w/QFnvTng9eZL5fvszL7Whl3MBoWF8yxL7ksUjBfOD+OxkytiqbBpG+Fw==, } + bidi-js@1.1.0: + resolution: + { + integrity: sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==, + } + birpc@2.9.0: resolution: { @@ -2327,6 +2587,13 @@ packages: integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==, } + data-urls@7.0.0: + resolution: + { + integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==, + } + engines: { node: ^20.19.0 || ^22.12.0 || >=24.0.0 } + debug@4.4.3: resolution: { @@ -2339,6 +2606,12 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: + { + integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==, + } + decode-named-character-reference@1.3.0: resolution: { @@ -2482,6 +2755,13 @@ packages: } engines: { node: ">=0.12" } + entities@8.1.0: + resolution: + { + integrity: sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==, + } + engines: { node: ">=20.19.0" } + error-stack-parser-es@1.0.5: resolution: { @@ -2740,6 +3020,13 @@ packages: integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==, } + html-encoding-sniffer@6.0.0: + resolution: + { + integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==, + } + engines: { node: ^20.19.0 || ^22.12.0 || >=24.0.0 } + html-escaper@3.0.3: resolution: { @@ -2843,6 +3130,12 @@ packages: } engines: { node: ">=12" } + is-potential-custom-element-name@1.0.1: + resolution: + { + integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==, + } + is-wsl@3.1.1: resolution: { @@ -2863,6 +3156,18 @@ packages: } hasBin: true + jsdom@29.1.1: + resolution: + { + integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==, + } + engines: { node: ^20.19.0 || ^22.13.0 || >=24.0.0 } + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: { @@ -3407,6 +3712,12 @@ packages: integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==, } + parse5@8.0.1: + resolution: + { + integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==, + } + path-browserify@1.0.1: resolution: { @@ -3518,6 +3829,13 @@ packages: integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==, } + punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, + } + engines: { node: ">=6" } + radix3@1.1.2: resolution: { @@ -3640,6 +3958,13 @@ packages: } engines: { node: ">=11.0.0" } + saxes@6.0.0: + resolution: + { + integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==, + } + engines: { node: ">=v12.22.7" } + semver@6.3.1: resolution: { @@ -3762,6 +4087,19 @@ packages: engines: { node: ">=16" } hasBin: true + symbol-tree@3.2.4: + resolution: + { + integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==, + } + + takumi-js@2.14.0: + resolution: + { + integrity: sha512-dZHMrK9KI2T8uKjFa+tid3KK5HOlk8Nb08Uoq321Y6ISkUkZRFQ69acQUdFGeQjEinenzt1qAkXP/62AjmL7hg==, + } + engines: { node: ">=20.19" } + tiny-inflate@1.0.3: resolution: { @@ -3789,6 +4127,19 @@ packages: } engines: { node: ">=12.0.0" } + tldts-core@7.4.18: + resolution: + { + integrity: sha512-KI2PKvCmvQI/xD6ypPqkU0i7oxNXR/O58RLRaHR4svsgVUQ2JE/XC8BtMbG6PSehD27I8sZJRcC1ASnCy6lp3g==, + } + + tldts@7.4.18: + resolution: + { + integrity: sha512-somxA8xKAUzlAxl0TX/jMSYIbRNFIVGnI96pVGJPeMc5BsHmfVXn6mOJT6U++ELnl04eGfzpfcEuZe8WiASkXg==, + } + hasBin: true + totalist@3.0.1: resolution: { @@ -3796,6 +4147,20 @@ packages: } engines: { node: ">=6" } + tough-cookie@6.0.2: + resolution: + { + integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==, + } + engines: { node: ">=16" } + + tr46@6.0.0: + resolution: + { + integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==, + } + engines: { node: ">=20" } + trim-lines@3.0.1: resolution: { @@ -3858,6 +4223,13 @@ packages: integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==, } + undici@7.30.0: + resolution: + { + integrity: sha512-dkrQXeHSaoamnItlYbmzG0wFYrM0ZwDxCIg0A7aKjTyyhh9svRzCNFEzV+Vm05/yehjCzjDZ31KXfGEjYSztDQ==, + } + engines: { node: ">=20.18.1" } + undici@8.11.2: resolution: { @@ -4315,6 +4687,34 @@ packages: integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==, } + w3c-xmlserializer@5.0.0: + resolution: + { + integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==, + } + engines: { node: ">=18" } + + webidl-conversions@8.0.1: + resolution: + { + integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==, + } + engines: { node: ">=20" } + + whatwg-mimetype@5.0.0: + resolution: + { + integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==, + } + engines: { node: ">=20" } + + whatwg-url@16.0.1: + resolution: + { + integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==, + } + engines: { node: ^20.19.0 || ^22.12.0 || >=24.0.0 } + wrap-ansi@9.0.2: resolution: { @@ -4329,6 +4729,19 @@ packages: } engines: { node: ">=20" } + xml-name-validator@5.0.0: + resolution: + { + integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==, + } + engines: { node: ">=18" } + + xmlchars@2.2.0: + resolution: + { + integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==, + } + xxhash-wasm@1.1.0: resolution: { @@ -4405,6 +4818,40 @@ packages: } snapshots: + "@alfanjauhari/astro-og-images@0.2.0(patch_hash=21946ac883dbe7a81731461757ca1e71371ce7f22e0565e12a7603e0c717ae07)(astro@7.3.5(@types/node@24.19.1)(yaml@2.9.1))(csstype@3.2.3)": + dependencies: + "@takumi-rs/core": 2.14.0(csstype@3.2.3) + "@types/react": 19.3.0 + astro: 7.3.5(@types/node@24.19.1)(yaml@2.9.1) + jsdom: 29.1.1 + takumi-js: 2.14.0(csstype@3.2.3) + transitivePeerDependencies: + - "@noble/hashes" + - canvas + - csstype + - preact + - react + + "@asamuzakjp/css-color@5.1.11": + dependencies: + "@asamuzakjp/generational-cache": 1.0.1 + "@csstools/css-calc": 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + "@csstools/css-color-parser": 4.2.6(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + "@csstools/css-parser-algorithms": 4.0.2(@csstools/css-tokenizer@4.0.2) + "@csstools/css-tokenizer": 4.0.2 + + "@asamuzakjp/dom-selector@7.1.1": + dependencies: + "@asamuzakjp/generational-cache": 1.0.1 + "@asamuzakjp/nwsapi": 2.3.9 + bidi-js: 1.1.0 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + "@asamuzakjp/generational-cache@1.0.1": {} + + "@asamuzakjp/nwsapi@2.3.9": {} + "@astrojs/astro2tsx@0.1.2(@emnapi/runtime@1.11.3)": dependencies: "@emnapi/core": 1.11.3 @@ -4855,6 +5302,10 @@ snapshots: "@babel/helper-string-parser": 7.29.7 "@babel/helper-validator-identifier": 7.29.7 + "@bramus/specificity@2.4.3": + dependencies: + css-tree: 3.2.1 + "@bruits/satteri-darwin-arm64@0.10.5": optional: true @@ -4933,6 +5384,30 @@ snapshots: style-mod: 4.1.3 w3c-keyname: 2.2.8 + "@csstools/color-helpers@6.1.2": {} + + "@csstools/css-calc@3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)": + dependencies: + "@csstools/css-parser-algorithms": 4.0.2(@csstools/css-tokenizer@4.0.2) + "@csstools/css-tokenizer": 4.0.2 + + "@csstools/css-color-parser@4.2.6(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2)": + dependencies: + "@csstools/color-helpers": 6.1.2 + "@csstools/css-calc": 3.4.3(@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) + "@csstools/css-parser-algorithms": 4.0.2(@csstools/css-tokenizer@4.0.2) + "@csstools/css-tokenizer": 4.0.2 + + "@csstools/css-parser-algorithms@4.0.2(@csstools/css-tokenizer@4.0.2)": + dependencies: + "@csstools/css-tokenizer": 4.0.2 + + "@csstools/css-syntax-patches-for-csstree@1.1.15(css-tree@3.2.1)": + optionalDependencies: + css-tree: 3.2.1 + + "@csstools/css-tokenizer@4.0.2": {} + "@ctrl/tinycolor@4.2.1": {} "@emmetio/abbreviation@2.3.3": @@ -5065,6 +5540,8 @@ snapshots: "@esbuild/win32-x64@0.28.2": optional: true + "@exodus/bytes@1.16.0": {} + "@expressive-code/core@0.44.2": dependencies: "@ctrl/tinycolor": 4.2.1 @@ -5090,6 +5567,10 @@ snapshots: dependencies: "@expressive-code/core": 0.44.2 + "@fontsource/noto-sans-sc@5.3.0": {} + + "@fontsource/noto-sans@5.3.0": {} + "@fontsource/noto-serif-sc@5.3.0": {} "@fontsource/noto-serif@5.3.0": {} @@ -5367,6 +5848,58 @@ snapshots: "@shikijs/vscode-textmate@10.0.2": {} + "@takumi-rs/core-darwin-arm64@2.14.0": + optional: true + + "@takumi-rs/core-darwin-x64@2.14.0": + optional: true + + "@takumi-rs/core-linux-arm64-gnu@2.14.0": + optional: true + + "@takumi-rs/core-linux-arm64-musl@2.14.0": + optional: true + + "@takumi-rs/core-linux-x64-gnu@2.14.0": + optional: true + + "@takumi-rs/core-linux-x64-musl@2.14.0": + optional: true + + "@takumi-rs/core-win32-arm64-msvc@2.14.0": + optional: true + + "@takumi-rs/core-win32-x64-msvc@2.14.0": + optional: true + + "@takumi-rs/core@2.14.0(csstype@3.2.3)": + dependencies: + "@takumi-rs/helpers": 2.14.0 + optionalDependencies: + "@takumi-rs/core-darwin-arm64": 2.14.0 + "@takumi-rs/core-darwin-x64": 2.14.0 + "@takumi-rs/core-linux-arm64-gnu": 2.14.0 + "@takumi-rs/core-linux-arm64-musl": 2.14.0 + "@takumi-rs/core-linux-x64-gnu": 2.14.0 + "@takumi-rs/core-linux-x64-musl": 2.14.0 + "@takumi-rs/core-win32-arm64-msvc": 2.14.0 + "@takumi-rs/core-win32-x64-msvc": 2.14.0 + csstype: 3.2.3 + transitivePeerDependencies: + - preact + - react + + "@takumi-rs/helpers@2.14.0": {} + + "@takumi-rs/wasm@2.14.0(csstype@3.2.3)": + dependencies: + "@takumi-rs/helpers": 2.14.0 + optionalDependencies: + csstype: 3.2.3 + transitivePeerDependencies: + - preact + - react + "@tybys/wasm-util@0.10.4": dependencies: tslib: 2.8.1 @@ -5401,6 +5934,10 @@ snapshots: dependencies: undici-types: 7.24.6 + "@types/react@19.3.0": + dependencies: + csstype: 3.2.3 + "@types/sax@1.2.7": dependencies: "@types/node": 24.19.1 @@ -5757,6 +6294,10 @@ snapshots: is-alphanumerical: 2.0.1 is-decimal: 2.0.1 + bidi-js@1.1.0: + dependencies: + require-from-string: 2.0.2 + birpc@2.9.0: {} birpc@4.2.0: {} @@ -5853,10 +6394,19 @@ snapshots: csstype@3.2.3: {} + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - "@noble/hashes" + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 @@ -5921,6 +6471,8 @@ snapshots: entities@7.0.1: {} + entities@8.1.0: {} + error-stack-parser-es@1.0.5: {} es-module-lexer@2.3.2: {} @@ -6130,6 +6682,12 @@ snapshots: hookable@5.5.3: {} + html-encoding-sniffer@6.0.0: + dependencies: + "@exodus/bytes": 1.16.0 + transitivePeerDependencies: + - "@noble/hashes" + html-escaper@3.0.3: {} html-void-elements@3.0.0: {} @@ -6167,6 +6725,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} + is-wsl@3.1.1: dependencies: is-inside-container: 1.0.0 @@ -6177,6 +6737,32 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@29.1.1: + dependencies: + "@asamuzakjp/css-color": 5.1.11 + "@asamuzakjp/dom-selector": 7.1.1 + "@bramus/specificity": 2.4.3 + "@csstools/css-syntax-patches-for-csstree": 1.1.15(css-tree@3.2.1) + "@exodus/bytes": 1.16.0 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.3 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + undici: 7.30.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - "@noble/hashes" + jsesc@3.1.0: {} json-schema-traverse@1.0.0: {} @@ -6572,6 +7158,10 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse5@8.0.1: + dependencies: + entities: 8.1.0 + path-browserify@1.0.1: {} pathe@2.0.3: {} @@ -6622,6 +7212,8 @@ snapshots: property-information@7.1.0: {} + punycode@2.3.1: {} + radix3@1.1.2: {} readdirp@4.1.2: {} @@ -6713,6 +7305,10 @@ snapshots: sax@1.6.1: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + semver@6.3.1: {} semver@7.8.5: {} @@ -6819,6 +7415,18 @@ snapshots: picocolors: 1.1.1 sax: 1.6.1 + symbol-tree@3.2.4: {} + + takumi-js@2.14.0(csstype@3.2.3): + dependencies: + "@takumi-rs/core": 2.14.0(csstype@3.2.3) + "@takumi-rs/helpers": 2.14.0 + "@takumi-rs/wasm": 2.14.0(csstype@3.2.3) + transitivePeerDependencies: + - csstype + - preact + - react + tiny-inflate@1.0.3: {} tinyclip@1.0.3: {} @@ -6830,8 +7438,22 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + tldts-core@7.4.18: {} + + tldts@7.4.18: + dependencies: + tldts-core: 7.4.18 + totalist@3.0.1: {} + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.18 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + trim-lines@3.0.1: {} trough@2.2.0: {} @@ -6854,6 +7476,8 @@ snapshots: undici-types@7.24.6: {} + undici@7.30.0: {} + undici@8.11.2: {} unified@11.0.5: @@ -7127,6 +7751,22 @@ snapshots: w3c-keyname@2.2.8: {} + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + "@exodus/bytes": 1.16.0 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - "@noble/hashes" + wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 @@ -7138,6 +7778,10 @@ snapshots: is-wsl: 3.1.1 powershell-utils: 0.2.1 + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + xxhash-wasm@1.1.0: {} y18n@5.0.8: {} diff --git a/doc/pnpm-workspace.yaml b/doc/pnpm-workspace.yaml index 5ed0b5a..2589c6b 100644 --- a/doc/pnpm-workspace.yaml +++ b/doc/pnpm-workspace.yaml @@ -1,2 +1,4 @@ allowBuilds: esbuild: true +patchedDependencies: + "@alfanjauhari/astro-og-images@0.2.0": patches/@alfanjauhari__astro-og-images@0.2.0.patch diff --git a/doc/scripts/check-docs.mjs b/doc/scripts/check-docs.mjs index 14dbbbf..d40bf9e 100644 --- a/doc/scripts/check-docs.mjs +++ b/doc/scripts/check-docs.mjs @@ -1,8 +1,28 @@ import assert from "node:assert/strict"; import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; +import { getOgImagePath } from "@alfanjauhari/astro-og-images/path"; import { locales } from "../src/locales.mjs"; +for (const [pathname, imagePath] of [ + ["/docs/404/", "/docs/404.png"], + ["/docs/zh/404/", "/docs/zh/404/index.png"], + ["/docs/doc/", "/docs/doc/index.png"], +]) { + assert.equal( + new URL( + getOgImagePath({ + base: "/docs/", + format: "png", + site: new URL("https://yuescript.org"), + url: new URL(pathname, "https://yuescript.org"), + }), + ).pathname, + imagePath, + `Wrong preview path under DOCS_BASE: ${pathname}`, + ); +} + const translations = Object.fromEntries( Object.values(locales).map(({ lang }) => [ lang, @@ -110,6 +130,36 @@ function checkTranslations(html, lang, route) { } function checkInterface(html, route) { + const previewImages = [ + ...html.matchAll(/]*>/g)) { assert.match( image, @@ -128,7 +178,18 @@ function checkInterface(html, route) { ); } assert.match(html, /class="[^"]*sl-skip-link/, `Missing skip link: ${route}`); - assert.match(html, /name="theme-color"/, `Missing theme color: ${route}`); + for (const [theme, color] of [ + ["light", "#8c8260"], + ["dark", "#b4ac8f"], + ]) { + assert.match( + html, + new RegExp( + `name="theme-color" content="${color}" media="\\(prefers-color-scheme: ${theme}\\)"`, + ), + `Missing ${theme} accent theme color: ${route}`, + ); + } assert.match( html, /rel="preload"[^>]*as="font"/, @@ -301,5 +362,5 @@ assert.ok( "Standalone yue-*.md manuals must be removed", ); console.log( - `Verified ${docs.length} routes, legacy HTML redirects, ${Object.keys(locales).length} language aggregates, localized UI, ordering and anchors, and ${controls} compiler controls.`, + `Verified ${docs.length} routes, preview images, legacy HTML redirects, ${Object.keys(locales).length} language aggregates, localized UI, ordering and anchors, and ${controls} compiler controls.`, ); diff --git a/doc/src/components/Head.astro b/doc/src/components/Head.astro index d2a4b29..fb1903a 100644 --- a/doc/src/components/Head.astro +++ b/doc/src/components/Head.astro @@ -2,9 +2,33 @@ import Head from "@astrojs/starlight/components/Head.astro"; import headingFont from "@fontsource/noto-serif/files/noto-serif-latin-700-normal.woff2?url"; import heroFont from "@fontsource/noto-serif/files/noto-serif-latin-800-normal.woff2?url"; +import bodyFont from "@fontsource/noto-sans/files/noto-sans-latin-400-normal.woff2?url"; +import { getOgImagePath } from "@alfanjauhari/astro-og-images/path"; + +const image = getOgImagePath({ + base: import.meta.env.BASE_URL, + format: "png", + site: Astro.site, + url: Astro.url, +}); +const imageAlt = `${Astro.locals.starlightRoute.entry.data.title} | YueScript`; --- + + + + + + + + )} - + +