@xingwangzhe/stalux 1.51.1 → 1.52.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release/cli.mjs +16 -20
- package/package.json +48 -45
- package/src/components/stalux/footer/FooterBadges.astro +2 -2
- package/src/components/stalux/layout/head.astro +1 -1
- package/src/components/stalux/posts/postContent.astro +1 -1
- package/src/index.ts +57 -51
- package/src/internal/cjk-search-index.ts +4 -7
- package/src/internal/components-plugin.ts +1 -3
- package/src/internal/font-slices.ts +36 -29
- package/src/internal/html-images.ts +1 -1
- package/src/internal/html-styles.ts +1 -1
- package/src/internal/image-dimensions.ts +3 -8
- package/src/internal/injected-routes.ts +1 -3
- package/src/internal/page-output-cache.ts +11 -15
- package/src/internal/post-cover.ts +22 -26
- package/src/internal/project-mode.ts +3 -2
- package/src/internal/public-assets.ts +42 -23
- package/src/internal/runtime-cache-key.ts +42 -27
- package/src/internal/satteri-config.ts +2 -2
- package/src/internal/search-style.ts +6 -7
- package/src/internal/social-card-cover.ts +12 -10
- package/src/internal/social-card-renderer.ts +8 -11
- package/src/internal/webmcp-mutations.ts +11 -5
- package/src/layouts/Stalux.astro +4 -4
- package/src/pages/openapi.json.ts +1 -1
- package/src/plugins/feature-flags.ts +2 -2
- package/src/schemas/collections.ts +1 -1
- package/src/schemas/config.ts +4 -1
- package/src/scripts/list-reveal.ts +7 -5
- package/src/scripts/swup-router.ts +1 -1
- package/src/scripts/weather.ts +2 -2
- package/src/scripts/webmcp.ts +4 -4
- package/src/utils/accent-color.ts +1 -1
- package/src/utils/ai-discovery.ts +6 -6
- package/src/utils/badge-generator.ts +1 -2
- package/src/utils/cc.ts +2 -2
- package/src/utils/dayjs.ts +5 -5
- package/src/utils/diagnostics.ts +3 -3
- package/src/utils/public-routes.ts +3 -3
- package/src/utils/seo-document.ts +2 -2
- package/src/utils/site-path.ts +1 -1
- package/src/utils/social-image.ts +1 -3
package/.release/cli.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
2
3
|
|
|
3
4
|
// scripts/cli.ts
|
|
4
|
-
import {
|
|
5
|
-
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { join, resolve } from "path";
|
|
6
6
|
function timestamp() {
|
|
7
7
|
return new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
8
8
|
}
|
|
@@ -130,13 +130,13 @@ This is your first post. Edit or delete it, then start writing your own content.
|
|
|
130
130
|
## Features
|
|
131
131
|
|
|
132
132
|
- \uD83C\uDF19 Dark mode with elegant glassmorphism design
|
|
133
|
-
- \uD83D\uDD24 Per-route font subsetting
|
|
133
|
+
- \uD83D\uDD24 Per-route font subsetting \u2014 each page loads only needed characters
|
|
134
134
|
- \uD83D\uDD0D Full-text search (Pagefind)
|
|
135
135
|
- \uD83D\uDCE1 RSS & Atom feeds
|
|
136
136
|
- \uD83D\uDCAC Comment system (Waline)
|
|
137
137
|
- \uD83D\uDCCA Mermaid diagrams
|
|
138
138
|
- \uD83D\uDCD0 Math formula rendering
|
|
139
|
-
- \uD83D\uDDBC
|
|
139
|
+
- \uD83D\uDDBC\uFE0F PhotoSwipe lightbox
|
|
140
140
|
|
|
141
141
|
For more details, visit [Stalux documentation](https://stalux.needhelp.icu).
|
|
142
142
|
`;
|
|
@@ -182,22 +182,21 @@ date: "${date}"
|
|
|
182
182
|
> Imagination is more important than knowledge.
|
|
183
183
|
`;
|
|
184
184
|
}
|
|
185
|
-
function writeIfMissing(filePath, label, content) {
|
|
186
|
-
if (
|
|
187
|
-
console.log(`
|
|
185
|
+
async function writeIfMissing(filePath, label, content) {
|
|
186
|
+
if (await Bun.file(filePath).exists()) {
|
|
187
|
+
console.log(` \u23ED ${label} (exists, skipped)`);
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
console.log(` ✅ ${label}`);
|
|
190
|
+
await Bun.write(filePath, content);
|
|
191
|
+
console.log(` \u2705 ${label}`);
|
|
193
192
|
}
|
|
194
|
-
function main([command, targetArg = "."] =
|
|
193
|
+
async function main([command, targetArg = "."] = Bun.argv.slice(2)) {
|
|
195
194
|
if (!command || command === "--help" || command === "-h") {
|
|
196
195
|
printHelp();
|
|
197
196
|
return;
|
|
198
197
|
}
|
|
199
198
|
if (command !== "init") {
|
|
200
|
-
console.error(
|
|
199
|
+
console.error(`\u274C Unknown command: ${command}`);
|
|
201
200
|
printHelp();
|
|
202
201
|
process.exitCode = 1;
|
|
203
202
|
return;
|
|
@@ -206,9 +205,6 @@ function main([command, targetArg = "."] = process.argv.slice(2)) {
|
|
|
206
205
|
const contentRoot = join(targetPath, "stalux");
|
|
207
206
|
console.log(`\uD83D\uDCE6 Initializing Stalux content in ${targetPath}...
|
|
208
207
|
`);
|
|
209
|
-
for (const dir of ["config", "posts", "about", "words"]) {
|
|
210
|
-
mkdirSync(join(contentRoot, dir), { recursive: true });
|
|
211
|
-
}
|
|
212
208
|
const files = {
|
|
213
209
|
...Object.fromEntries(Object.entries(getConfigYamls()).map(([name, content]) => [`config/${name}`, content])),
|
|
214
210
|
"posts/hello-stalux.md": getExamplePost(),
|
|
@@ -217,16 +213,16 @@ function main([command, targetArg = "."] = process.argv.slice(2)) {
|
|
|
217
213
|
"words/einstein-imagination.md": getWordsExample()
|
|
218
214
|
};
|
|
219
215
|
for (const [file, content] of Object.entries(files)) {
|
|
220
|
-
writeIfMissing(join(contentRoot, file), `stalux/${file}`, content);
|
|
216
|
+
await writeIfMissing(join(contentRoot, file), `stalux/${file}`, content);
|
|
221
217
|
}
|
|
222
218
|
console.log(`
|
|
223
|
-
|
|
219
|
+
\u2705 Done! Stalux content initialized.
|
|
224
220
|
`);
|
|
225
221
|
printNextSteps();
|
|
226
222
|
}
|
|
227
223
|
function printHelp() {
|
|
228
224
|
console.log(`
|
|
229
|
-
Stalux
|
|
225
|
+
Stalux \u2014 Modern Astro Blog Theme
|
|
230
226
|
|
|
231
227
|
Usage:
|
|
232
228
|
stalux init Initialize Stalux content (configs, posts, pages)
|
|
@@ -262,4 +258,4 @@ function printNextSteps() {
|
|
|
262
258
|
console.log(" \uD83D\uDCD6 Docs: https://stalux.needhelp.icu");
|
|
263
259
|
console.log("");
|
|
264
260
|
}
|
|
265
|
-
main();
|
|
261
|
+
await main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xingwangzhe/stalux",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.52.2",
|
|
4
4
|
"description": "A powerful, modern Astro blog theme — use as template or install as plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"url": "git+https://github.com/xingwangzhe/stalux.git"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|
|
23
|
-
"
|
|
23
|
+
"bun": ">=1.4.0"
|
|
24
24
|
},
|
|
25
25
|
"bin": {
|
|
26
26
|
"stalux": ".release/cli.mjs"
|
|
@@ -70,13 +70,13 @@
|
|
|
70
70
|
"./package.json": "./package.json"
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
|
-
"dev": "bun run styles:build &&
|
|
74
|
-
"build": "bun run styles:build && astro build --timings &&
|
|
75
|
-
"preview": "astro preview",
|
|
76
|
-
"astro": "astro",
|
|
77
|
-
"init": "
|
|
78
|
-
"node:check": "tsc6 --project tsconfig.node.json",
|
|
79
|
-
"build:cli": "bun build scripts/cli.ts --target=
|
|
73
|
+
"dev": "bun run styles:build && bun scripts/dev.ts",
|
|
74
|
+
"build": "bun run styles:build && bunx --bun astro build --timings && bun scripts/verify-build.ts",
|
|
75
|
+
"preview": "bunx --bun astro preview",
|
|
76
|
+
"astro": "bunx --bun astro",
|
|
77
|
+
"init": "bun scripts/cli.ts init",
|
|
78
|
+
"node:check": "bun --bun tsc6 --project tsconfig.node.json",
|
|
79
|
+
"build:cli": "bun build scripts/cli.ts --target=bun --outfile .release/cli.mjs",
|
|
80
80
|
"prepack": "bun run styles:build && bun run build:cli",
|
|
81
81
|
"check": "biome check .",
|
|
82
82
|
"check:fix": "biome check --write .",
|
|
@@ -84,77 +84,80 @@
|
|
|
84
84
|
"format:check": "biome format .",
|
|
85
85
|
"lint": "biome lint .",
|
|
86
86
|
"lint:fix": "biome lint --write .",
|
|
87
|
-
"astro:check": "astro check --minimumFailingSeverity warning",
|
|
88
|
-
"test": "vitest",
|
|
89
|
-
"test:run": "vitest run",
|
|
90
|
-
"test:coverage": "vitest run --coverage",
|
|
91
|
-
"knip": "knip",
|
|
92
|
-
"verify:build": "
|
|
87
|
+
"astro:check": "bunx --bun astro check --minimumFailingSeverity warning",
|
|
88
|
+
"test": "bun --bun vitest",
|
|
89
|
+
"test:run": "bun --bun vitest run",
|
|
90
|
+
"test:coverage": "bun --bun vitest run --coverage",
|
|
91
|
+
"knip": "bunx --bun knip",
|
|
92
|
+
"verify:build": "bun scripts/verify-build.ts",
|
|
93
93
|
"validate": "bun run styles:build && bun run check && bun run node:check && bun run astro:check && bun run knip && bun run test:coverage && bun run build && bun run build:cli",
|
|
94
94
|
"prepare": "sh -c 'if [ -d scripts/git-hooks ] && git rev-parse --git-dir >/dev/null 2>&1; then git config core.hooksPath scripts/git-hooks; fi'",
|
|
95
|
-
"dev:debug": "STALUX_DEBUG=1 astro dev --verbose",
|
|
96
|
-
"build:debug": "STALUX_DEBUG=1 astro build --timings --verbose &&
|
|
97
|
-
"styles:build": "
|
|
98
|
-
"styles:watch": "
|
|
99
|
-
"benchmark:performance": "
|
|
95
|
+
"dev:debug": "STALUX_DEBUG=1 bunx --bun astro dev --verbose",
|
|
96
|
+
"build:debug": "STALUX_DEBUG=1 bunx --bun astro build --timings --verbose && bun scripts/verify-build.ts",
|
|
97
|
+
"styles:build": "bun scripts/build-styles.ts && bun scripts/build-style-names.ts",
|
|
98
|
+
"styles:watch": "bun scripts/build-styles.ts --watch",
|
|
99
|
+
"benchmark:performance": "bun scripts/benchmark-performance.ts",
|
|
100
100
|
"benchmark:builds": "python3 scripts/benchmark-builds.py",
|
|
101
101
|
"analyze": "SONDA=1 bun run build",
|
|
102
|
-
"backgrounds:build": "bun
|
|
102
|
+
"backgrounds:build": "bun scripts/build-backgrounds.ts"
|
|
103
103
|
},
|
|
104
104
|
"dependencies": {
|
|
105
105
|
"@astrojs/compiler": "^4.0.0",
|
|
106
|
-
"@astrojs/markdown-satteri": "^0.4.
|
|
106
|
+
"@astrojs/markdown-satteri": "^0.4.6",
|
|
107
107
|
"@astrojs/rss": "^4.0.19",
|
|
108
108
|
"@astrojs/sitemap": "^3.7.4",
|
|
109
|
-
"@expressive-code/plugin-line-numbers": "^0.44.
|
|
110
|
-
"@lucide/astro": "^1.
|
|
111
|
-
"@mcp-b/webmcp-polyfill": "^
|
|
109
|
+
"@expressive-code/plugin-line-numbers": "^0.44.3",
|
|
110
|
+
"@lucide/astro": "^1.66.0",
|
|
111
|
+
"@mcp-b/webmcp-polyfill": "^6.0.3",
|
|
112
112
|
"@microsoft/clarity": "1.0.2",
|
|
113
|
-
"@pagefind/component-ui": "^1.
|
|
113
|
+
"@pagefind/component-ui": "^1.6.0",
|
|
114
114
|
"@swup/a11y-plugin": "^5.2.1",
|
|
115
115
|
"@swup/head-plugin": "^2.3.1",
|
|
116
116
|
"@swup/js-plugin": "^3.2.0",
|
|
117
117
|
"@swup/preload-plugin": "^3.2.12",
|
|
118
118
|
"@swup/scroll-plugin": "^4.0.0",
|
|
119
|
-
"@takumi-rs/core": "2.
|
|
120
|
-
"@waline/client": "^3.16.
|
|
119
|
+
"@takumi-rs/core": "2.19.0",
|
|
120
|
+
"@waline/client": "^3.16.2",
|
|
121
121
|
"@xingwangzhe/cjk-font-split-native": "^0.4.10",
|
|
122
122
|
"@xingwangzhe/satteri-mermaid": "^0.8.2",
|
|
123
123
|
"@xingwangzhe/satteri-photoswipe": "^0.2.6",
|
|
124
124
|
"@xingwangzhe/tags-cloud": "^1.2.9",
|
|
125
|
-
"astro-expressive-code": "^0.44.
|
|
125
|
+
"astro-expressive-code": "^0.44.3",
|
|
126
126
|
"astro-seo": "^1.2.0",
|
|
127
127
|
"badge-maker": "^6.0.0",
|
|
128
128
|
"css-tree": "^3.2.1",
|
|
129
129
|
"image-size": "^2.0.4",
|
|
130
|
-
"magic-string": "^1.4.
|
|
131
|
-
"pagefind": "^1.
|
|
132
|
-
"parse5": "^8.0
|
|
130
|
+
"magic-string": "^1.4.5",
|
|
131
|
+
"pagefind": "^1.6.0",
|
|
132
|
+
"parse5": "^8.1.0",
|
|
133
133
|
"photoswipe": "^5.4.4",
|
|
134
|
-
"satteri": "
|
|
135
|
-
"simple-icons": "^16.
|
|
134
|
+
"satteri": "0.11.6",
|
|
135
|
+
"simple-icons": "^16.36.0",
|
|
136
136
|
"swup": "4.10.0",
|
|
137
137
|
"temml": "^0.14.0",
|
|
138
138
|
"yaml": "^2.9.1"
|
|
139
139
|
},
|
|
140
140
|
"devDependencies": {
|
|
141
|
-
"@astrojs/check": "^0.9.
|
|
142
|
-
"@biomejs/biome": "^2.5
|
|
141
|
+
"@astrojs/check": "^0.9.12",
|
|
142
|
+
"@biomejs/biome": "^2.6.5",
|
|
143
|
+
"@types/bun": "1.4.4",
|
|
143
144
|
"@types/css-tree": "^3.2.0",
|
|
144
|
-
"@
|
|
145
|
-
"@
|
|
146
|
-
"
|
|
147
|
-
"astro": "^7.4.4",
|
|
145
|
+
"@unocss/astro": "^66.12.0",
|
|
146
|
+
"@vitest/coverage-v8": "^5.1.0",
|
|
147
|
+
"astro": "^7.4.7",
|
|
148
148
|
"happy-dom": "^21.0.4",
|
|
149
|
-
"knip": "^6.
|
|
149
|
+
"knip": "^6.44.1",
|
|
150
150
|
"sharp": "0.35.5",
|
|
151
151
|
"sonda": "^0.14.0",
|
|
152
152
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
153
|
-
"unocss": "^66.
|
|
154
|
-
"vite": "^8.3.
|
|
155
|
-
"vitest": "^5.0
|
|
153
|
+
"unocss": "^66.12.0",
|
|
154
|
+
"vite": "^8.3.6",
|
|
155
|
+
"vitest": "^5.1.0"
|
|
156
156
|
},
|
|
157
157
|
"peerDependencies": {
|
|
158
|
-
"astro": "^7.4.
|
|
158
|
+
"astro": "^7.4.7"
|
|
159
|
+
},
|
|
160
|
+
"overrides": {
|
|
161
|
+
"satteri": "0.11.6"
|
|
159
162
|
}
|
|
160
163
|
}
|
|
@@ -29,8 +29,8 @@ function badgeImg(opts: BadgeOptions) {
|
|
|
29
29
|
? sitePath(`stalux-assets/badges/${id}.svg`)
|
|
30
30
|
: svgToDataUrl(svg);
|
|
31
31
|
const alt = opts.alt || `${opts.label}: ${opts.message}`;
|
|
32
|
-
const width = svg.match(/<svg\b[^>]*\bwidth="(\d+)"/)?.[1];
|
|
33
|
-
const height = svg.match(/<svg\b[^>]*\bheight="(\d+)"/)?.[1];
|
|
32
|
+
const width = svg.match(/<svg\b[^>]*\bwidth="(\d+)"/u)?.[1];
|
|
33
|
+
const height = svg.match(/<svg\b[^>]*\bheight="(\d+)"/u)?.[1];
|
|
34
34
|
const dimensions = width && height ? ` width="${width}" height="${height}"` : "";
|
|
35
35
|
// Closed badge groups should not initialize every SVG document during first paint.
|
|
36
36
|
const img = `<img src="${src}" alt="${alt}" class="stalux-badge" loading="lazy" decoding="async"${dimensions} />`;
|
|
@@ -48,7 +48,7 @@ const seo = buildSeoDocument({
|
|
|
48
48
|
exportMarkdown: Boolean(promoteConfig?.export_md),
|
|
49
49
|
version: packageJson.version,
|
|
50
50
|
});
|
|
51
|
-
const ldJson = JSON.stringify(seo.schema).replace(/<\//
|
|
51
|
+
const ldJson = JSON.stringify(seo.schema).replace(/<\//gu, "<\\/");
|
|
52
52
|
---
|
|
53
53
|
|
|
54
54
|
<head>
|
|
@@ -36,7 +36,7 @@ const timezone = siteConfig.timezone;
|
|
|
36
36
|
// /about 等非文章页面固定不显示 Markdown 原文按钮
|
|
37
37
|
const isAboutPage = Astro.url.pathname.startsWith("/about");
|
|
38
38
|
const exportMd = Boolean(promoteConfig?.export_md) && !isAboutPage;
|
|
39
|
-
const mdSourceUrl = exportMd ? `${Astro.url.pathname.replace(
|
|
39
|
+
const mdSourceUrl = exportMd ? `${Astro.url.pathname.replace(/\/$/u, "")}.md` : "";
|
|
40
40
|
---
|
|
41
41
|
|
|
42
42
|
<article
|
package/src/index.ts
CHANGED
|
@@ -13,10 +13,9 @@ import { styleNames } from "./internal/style-names.generated.ts";
|
|
|
13
13
|
* 基于 Astro 7.1.3 Integration API(astro:config:setup / injectRoute / injectScript)
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
17
17
|
import { createRequire } from "node:module";
|
|
18
18
|
import path from "node:path";
|
|
19
|
-
import { fileURLToPath } from "node:url";
|
|
20
19
|
import sitemap from "@astrojs/sitemap";
|
|
21
20
|
import { mermaidHast, mermaidMdast } from "@xingwangzhe/satteri-mermaid";
|
|
22
21
|
import { photoswipe } from "@xingwangzhe/satteri-photoswipe";
|
|
@@ -151,15 +150,19 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
151
150
|
const markdownLogger = logger.fork("stalux/markdown");
|
|
152
151
|
const fontsLogger = logger.fork("stalux/fonts");
|
|
153
152
|
configLogger.debug(`setup started; mode=${isPluginMode ? "plugin" : "source"}`);
|
|
154
|
-
const srcDir = fileURLToPath(new URL(".", import.meta.url));
|
|
155
|
-
const runtimeCacheKey = createRuntimeCacheKey(
|
|
153
|
+
const srcDir = Bun.fileURLToPath(new URL(".", import.meta.url));
|
|
154
|
+
const runtimeCacheKey = await createRuntimeCacheKey(
|
|
156
155
|
path.join(srcDir, "scripts"),
|
|
157
156
|
path.resolve(srcDir, "../package.json"),
|
|
158
157
|
path.join(srcDir, "styles"),
|
|
159
158
|
JSON.stringify([styleNames, modernCssTargets]) +
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
path.resolve(
|
|
159
|
+
(await Bun.file(path.join(srcDir, "internal/compact-styles.ts")).text()) +
|
|
160
|
+
(await Bun.file(path.join(srcDir, "internal/offscreen-layout.ts")).text()),
|
|
161
|
+
path.resolve(
|
|
162
|
+
Bun.fileURLToPath(config.root),
|
|
163
|
+
opt.contentDir ?? "stalux",
|
|
164
|
+
"config",
|
|
165
|
+
),
|
|
163
166
|
);
|
|
164
167
|
|
|
165
168
|
// 1. 注入 Vite 别名 + 组件覆盖插件 + Vue 特性标记(Waline 依赖)
|
|
@@ -223,8 +226,7 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
223
226
|
define: {
|
|
224
227
|
__STALUX_COMPACT_STYLES__: JSON.stringify(command === "build"),
|
|
225
228
|
__STALUX_DEBUG__: JSON.stringify(
|
|
226
|
-
|
|
227
|
-
process.argv.includes("--verbose"),
|
|
229
|
+
Bun.env.STALUX_DEBUG === "1" || Bun.argv.includes("--verbose"),
|
|
228
230
|
),
|
|
229
231
|
__VUE_OPTIONS_API__: true,
|
|
230
232
|
__VUE_PROD_DEVTOOLS__: false,
|
|
@@ -256,7 +258,7 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
256
258
|
id: "stalux",
|
|
257
259
|
name: "Stalux",
|
|
258
260
|
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>',
|
|
259
|
-
entrypoint: fileURLToPath(
|
|
261
|
+
entrypoint: Bun.fileURLToPath(
|
|
260
262
|
new URL("./internal/dev-toolbar.ts", import.meta.url),
|
|
261
263
|
),
|
|
262
264
|
});
|
|
@@ -324,15 +326,15 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
324
326
|
"config",
|
|
325
327
|
"site.yml",
|
|
326
328
|
);
|
|
327
|
-
const siteYaml =
|
|
328
|
-
const urlMatch = siteYaml.match(/^url\s*:\s*(.+)$/
|
|
329
|
+
const siteYaml = await Bun.file(siteYamlPath).text();
|
|
330
|
+
const urlMatch = siteYaml.match(/^url\s*:\s*(.+)$/mu);
|
|
329
331
|
const rawYamlUrl = urlMatch?.[1];
|
|
330
332
|
const yamlUrl = rawYamlUrl
|
|
331
333
|
?.trim()
|
|
332
|
-
.replace(/^["']|["']$/
|
|
333
|
-
.replace(
|
|
334
|
+
.replace(/^["']|["']$/gu, "")
|
|
335
|
+
.replace(/\/+$/u, "");
|
|
334
336
|
if (yamlUrl) {
|
|
335
|
-
const configuredSite = config.site?.replace(
|
|
337
|
+
const configuredSite = config.site?.replace(/\/+$/u, "");
|
|
336
338
|
if (!configuredSite || configuredSite === "https://example.com") {
|
|
337
339
|
// 未配置或为占位符 → 直接以 site.yml 为准
|
|
338
340
|
updateConfig({ site: yamlUrl });
|
|
@@ -348,7 +350,7 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
348
350
|
}
|
|
349
351
|
}
|
|
350
352
|
} catch (error) {
|
|
351
|
-
if ((error as
|
|
353
|
+
if ((error as { code?: string }).code === "ENOENT") {
|
|
352
354
|
configLogger.debug("site.yml not found; site sync skipped");
|
|
353
355
|
} else {
|
|
354
356
|
configLogger.warn(`site sync failed: ${describeError(error)}`);
|
|
@@ -361,7 +363,11 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
361
363
|
if (sliced) {
|
|
362
364
|
pageFontData = {
|
|
363
365
|
fontBuffer: sliced.fontBuffer,
|
|
364
|
-
outputDir: path.join(
|
|
366
|
+
outputDir: path.join(
|
|
367
|
+
Bun.fileURLToPath(config.outDir),
|
|
368
|
+
"_astro",
|
|
369
|
+
"fonts",
|
|
370
|
+
),
|
|
365
371
|
cacheDir: sliced.pageSubsetDir,
|
|
366
372
|
};
|
|
367
373
|
updateConfig({
|
|
@@ -379,27 +385,27 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
379
385
|
|
|
380
386
|
// Read final paths after every integration has finished updating configuration.
|
|
381
387
|
// Public assets must exist before Astro creates its Vite server/build asset pipeline.
|
|
382
|
-
"astro:config:done": ({ config, logger }) => {
|
|
383
|
-
const srcDir = fileURLToPath(new URL(".", import.meta.url));
|
|
384
|
-
const publicDir = fileURLToPath(config.publicDir);
|
|
388
|
+
"astro:config:done": async ({ config, logger }) => {
|
|
389
|
+
const srcDir = Bun.fileURLToPath(new URL(".", import.meta.url));
|
|
390
|
+
const publicDir = Bun.fileURLToPath(config.publicDir);
|
|
385
391
|
Object.assign(socialCardPaths, {
|
|
386
392
|
publicDir,
|
|
387
|
-
cacheDir: fileURLToPath(new URL("stalux-social-cards/", config.cacheDir)),
|
|
393
|
+
cacheDir: Bun.fileURLToPath(new URL("stalux-social-cards/", config.cacheDir)),
|
|
388
394
|
base: config.base,
|
|
389
395
|
fontPath: path.join(srcDir, "assets/fonts/LXGWWenKai-Regular.ttf"),
|
|
390
396
|
});
|
|
391
|
-
coverCacheDir = fileURLToPath(new URL("stalux-post-covers/", config.cacheDir));
|
|
397
|
+
coverCacheDir = Bun.fileURLToPath(new URL("stalux-post-covers/", config.cacheDir));
|
|
392
398
|
const assetsLogger = logger.fork("stalux/assets");
|
|
393
399
|
if (pageFontData) {
|
|
394
400
|
pageFontData.outputDir = path.join(
|
|
395
|
-
fileURLToPath(config.outDir),
|
|
401
|
+
Bun.fileURLToPath(config.outDir),
|
|
396
402
|
"_astro",
|
|
397
403
|
"fonts",
|
|
398
404
|
);
|
|
399
405
|
}
|
|
400
406
|
try {
|
|
401
|
-
syncFontAssets(srcDir, publicDir);
|
|
402
|
-
const { copied, skipped } = syncBackgroundSvgs(srcDir, publicDir);
|
|
407
|
+
await syncFontAssets(srcDir, publicDir);
|
|
408
|
+
const { copied, skipped } = await syncBackgroundSvgs(srcDir, publicDir);
|
|
403
409
|
if (copied > 0) {
|
|
404
410
|
assetsLogger.info(`同步背景 SVG ${copied} 个(跳过 ${skipped} 个不变)`);
|
|
405
411
|
} else if (skipped > 0) {
|
|
@@ -415,13 +421,16 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
415
421
|
const started = performance.now();
|
|
416
422
|
const outDir = pageFontData
|
|
417
423
|
? path.dirname(path.dirname(pageFontData.outputDir))
|
|
418
|
-
: fileURLToPath(new URL(String(dir)));
|
|
424
|
+
: Bun.fileURLToPath(new URL(String(dir)));
|
|
419
425
|
|
|
420
426
|
// Astro's Markdown image transform drops arbitrary lazy-loading hints
|
|
421
427
|
// added during HAST processing. Reapply the policy to emitted HTML.
|
|
422
|
-
const htmlFiles =
|
|
423
|
-
|
|
424
|
-
|
|
428
|
+
const htmlFiles = [
|
|
429
|
+
...new Bun.Glob("**/*").scanSync({ cwd: outDir, onlyFiles: true }),
|
|
430
|
+
].sort();
|
|
431
|
+
const sourceDir = Bun.fileURLToPath(new URL(".", import.meta.url));
|
|
432
|
+
const moduleDir = Bun.fileURLToPath(new URL(".", import.meta.url));
|
|
433
|
+
const resolveModule = (id: string) => Bun.resolveSync(id, moduleDir);
|
|
425
434
|
const requireModule = createRequire(import.meta.url);
|
|
426
435
|
const coverEncoder = coverCacheDir
|
|
427
436
|
? loadCoverEncoder(requireModule, logger)
|
|
@@ -434,12 +443,10 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
434
443
|
let sharedFontKey = "";
|
|
435
444
|
if (pageFontData) {
|
|
436
445
|
const sharedGlyphs = createSharedFontGlyphs();
|
|
437
|
-
await fs.mkdir(pageFontData.outputDir, { recursive: true });
|
|
438
|
-
await fs.mkdir(pageFontData.cacheDir, { recursive: true });
|
|
439
446
|
// The common shell is static across soft navigations; scan once, then
|
|
440
447
|
// reuse each component's content-addressed subset on every route.
|
|
441
448
|
await forEachBounded(pageEntries, 4, async (entry) => {
|
|
442
|
-
const html = await
|
|
449
|
+
const html = await Bun.file(path.join(outDir, entry)).text();
|
|
443
450
|
collectSharedFontGlyphs(parse(html), sharedGlyphs);
|
|
444
451
|
});
|
|
445
452
|
sharedFonts = await writeSharedFontSubsetsAsync(
|
|
@@ -478,13 +485,11 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
478
485
|
let optimizedImagePages = 0;
|
|
479
486
|
let pageFontPages = 0;
|
|
480
487
|
let pageFontSubsetCacheHits = 0;
|
|
481
|
-
if (pageFontData) await fs.mkdir(pageFontData.outputDir, { recursive: true });
|
|
482
|
-
if (pageFontData) await fs.mkdir(pageFontData.cacheDir, { recursive: true });
|
|
483
488
|
const referencedPageFonts = new Set<string>();
|
|
484
489
|
for (const sharedFont of sharedFonts) {
|
|
485
|
-
|
|
486
|
-
sharedFont.sourcePath,
|
|
490
|
+
await Bun.write(
|
|
487
491
|
path.join(pageFontData?.outputDir ?? outDir, sharedFont.filename),
|
|
492
|
+
Bun.file(sharedFont.sourcePath),
|
|
488
493
|
);
|
|
489
494
|
referencedPageFonts.add(sharedFont.filename);
|
|
490
495
|
}
|
|
@@ -492,21 +497,21 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
492
497
|
const dimensionsCache = new Map<string, Promise<ImageDimensions | undefined>>();
|
|
493
498
|
const processPage = async (entry: string) => {
|
|
494
499
|
const output = path.join(outDir, entry);
|
|
495
|
-
const before = await
|
|
500
|
+
const before = await Bun.file(output).text();
|
|
496
501
|
const cached = await outputCache.get(entry, before);
|
|
497
502
|
if (cached) {
|
|
498
503
|
if (cached.font && pageFontData) {
|
|
499
504
|
if (!referencedPageFonts.has(cached.font.filename)) {
|
|
500
|
-
|
|
501
|
-
cached.font.sourcePath,
|
|
505
|
+
await Bun.write(
|
|
502
506
|
path.join(pageFontData.outputDir, cached.font.filename),
|
|
507
|
+
Bun.file(cached.font.sourcePath),
|
|
503
508
|
);
|
|
504
509
|
}
|
|
505
510
|
referencedPageFonts.add(cached.font.filename);
|
|
506
511
|
pageFontPages++;
|
|
507
512
|
}
|
|
508
513
|
if (cached.html !== before) {
|
|
509
|
-
await
|
|
514
|
+
await Bun.write(output, cached.html);
|
|
510
515
|
optimizedImagePages++;
|
|
511
516
|
}
|
|
512
517
|
cacheHits++;
|
|
@@ -542,7 +547,7 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
542
547
|
}
|
|
543
548
|
}
|
|
544
549
|
if (pageFontData) {
|
|
545
|
-
const linkedStylesheetText = readLinkedStylesheetText(
|
|
550
|
+
const linkedStylesheetText = await readLinkedStylesheetText(
|
|
546
551
|
"",
|
|
547
552
|
output,
|
|
548
553
|
outDir,
|
|
@@ -566,9 +571,9 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
566
571
|
sourcePath: pageFont.sourcePath,
|
|
567
572
|
};
|
|
568
573
|
if (!referencedPageFonts.has(pageFont.filename)) {
|
|
569
|
-
|
|
570
|
-
pageFont.sourcePath,
|
|
574
|
+
await Bun.write(
|
|
571
575
|
path.join(pageFontData.outputDir, pageFont.filename),
|
|
576
|
+
Bun.file(pageFont.sourcePath),
|
|
572
577
|
);
|
|
573
578
|
}
|
|
574
579
|
referencedPageFonts.add(pageFont.filename);
|
|
@@ -576,7 +581,7 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
576
581
|
}
|
|
577
582
|
const after = emitHtml();
|
|
578
583
|
if (after !== before) {
|
|
579
|
-
await
|
|
584
|
+
await Bun.write(output, after);
|
|
580
585
|
optimizedImagePages++;
|
|
581
586
|
}
|
|
582
587
|
await outputCache.set(
|
|
@@ -592,7 +597,7 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
592
597
|
// Bound I/O concurrency without retaining the entire site's HTML in memory.
|
|
593
598
|
await forEachBounded(pageEntries, 4, processPage);
|
|
594
599
|
if (pageFontData) {
|
|
595
|
-
clearPageFontSubsets(pageFontData.outputDir, referencedPageFonts);
|
|
600
|
+
await clearPageFontSubsets(pageFontData.outputDir, referencedPageFonts);
|
|
596
601
|
}
|
|
597
602
|
logger.info(`applied image loading hints to ${optimizedImagePages} HTML pages`);
|
|
598
603
|
logger.info(
|
|
@@ -614,7 +619,7 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
614
619
|
// post-processed: a cached page skips that pass entirely but its
|
|
615
620
|
// HTML already points at these files, so they must always exist.
|
|
616
621
|
if (coverCacheDir) {
|
|
617
|
-
const restored = restoreCoverFiles({ outDir, cacheDir: coverCacheDir });
|
|
622
|
+
const restored = await restoreCoverFiles({ outDir, cacheDir: coverCacheDir });
|
|
618
623
|
if (restored > 0) logger.debug(`restored ${restored} card covers`);
|
|
619
624
|
}
|
|
620
625
|
|
|
@@ -738,14 +743,15 @@ export function stalux(options: StaluxOptions = {}): AstroIntegration[] {
|
|
|
738
743
|
"astro:build:done": async ({ dir, logger }) => {
|
|
739
744
|
const base = userSitemap?.filenameBase ?? "sitemap";
|
|
740
745
|
const source = new URL(`${base}-0.xml`, dir);
|
|
741
|
-
if (!
|
|
746
|
+
if (!(await Bun.file(source).exists())) return;
|
|
742
747
|
// Keep every URL discoverable when a site uses multiple chunks.
|
|
743
748
|
const index = new URL(`${base}-index.xml`, dir);
|
|
744
|
-
const indexXml = await
|
|
745
|
-
const hasMultipleSitemaps =
|
|
746
|
-
|
|
747
|
-
|
|
749
|
+
const indexXml = await Bun.file(index).text();
|
|
750
|
+
const hasMultipleSitemaps =
|
|
751
|
+
(indexXml.match(/<sitemap>/gu)?.length ?? 0) > 1;
|
|
752
|
+
await Bun.write(
|
|
748
753
|
new URL("sitemap.xml", dir),
|
|
754
|
+
Bun.file(hasMultipleSitemaps ? index : source),
|
|
749
755
|
);
|
|
750
756
|
logger.info("Generated sitemap.xml");
|
|
751
757
|
},
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
1
|
import path from "node:path";
|
|
4
2
|
import { type DefaultTreeAdapterMap, parse } from "parse5";
|
|
5
3
|
import { type CjkDocument, cjkGrams, gramShard, normalizeSearch } from "../utils/cjk-search";
|
|
@@ -92,17 +90,16 @@ export function extractCjkDocument(html: string, url: string) {
|
|
|
92
90
|
/** Content-addressed shards prevent mixed old/new deployments and keep downloads query-specific. */
|
|
93
91
|
export async function writeCjkSearchIndex(outDir: string, htmlFiles: string[], base: string) {
|
|
94
92
|
const directory = path.join(outDir, "pagefind", "cjk");
|
|
95
|
-
await mkdir(directory, { recursive: true });
|
|
96
93
|
async function asset(value: unknown) {
|
|
97
94
|
const json = JSON.stringify(value);
|
|
98
|
-
const hash =
|
|
95
|
+
const hash = new Bun.CryptoHasher("sha256").update(json).digest("hex").slice(0, 20);
|
|
99
96
|
const file = `${hash}.json`;
|
|
100
|
-
await
|
|
97
|
+
await Bun.write(path.join(directory, file), json);
|
|
101
98
|
return file;
|
|
102
99
|
}
|
|
103
100
|
const extracted = new Map<string, NonNullable<ReturnType<typeof extractCjkDocument>>>();
|
|
104
101
|
await forEachBounded(htmlFiles, 6, async (file) => {
|
|
105
|
-
const html = await
|
|
102
|
+
const html = await Bun.file(path.join(outDir, file)).text();
|
|
106
103
|
// Most output pages are archives/tags: don't parse pages without indexed sections.
|
|
107
104
|
if (!html.includes("data-pagefind-body")) return;
|
|
108
105
|
const url = `${base.replace(/\/$/u, "")}/${file.replace(/index\.html$/u, "")}`;
|
|
@@ -125,6 +122,6 @@ export async function writeCjkSearchIndex(outDir: string, htmlFiles: string[], b
|
|
|
125
122
|
}
|
|
126
123
|
const shards: Record<string, string> = {};
|
|
127
124
|
for (const [key, value] of Object.entries(postings)) shards[key] = await asset(value);
|
|
128
|
-
await
|
|
125
|
+
await Bun.write(path.join(directory, "manifest.json"), JSON.stringify({ documents, shards }));
|
|
129
126
|
return documents.length;
|
|
130
127
|
}
|
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
* 插件自动将别名解析为用户提供的路径或包内默认路径。
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { fileURLToPath } from "node:url";
|
|
20
|
-
|
|
21
19
|
import type { AstroIntegrationLogger } from "astro";
|
|
22
20
|
import type { Plugin } from "vite";
|
|
23
21
|
|
|
@@ -34,7 +32,7 @@ export function staluxComponentsAlias(
|
|
|
34
32
|
logger?: AstroIntegrationLogger,
|
|
35
33
|
): Plugin {
|
|
36
34
|
// 包内 components 目录的绝对路径
|
|
37
|
-
const componentsDir = fileURLToPath(new URL("../components", import.meta.url));
|
|
35
|
+
const componentsDir = Bun.fileURLToPath(new URL("../components", import.meta.url));
|
|
38
36
|
|
|
39
37
|
return {
|
|
40
38
|
name: "stalux:components-alias",
|