@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.
Files changed (42) hide show
  1. package/.release/cli.mjs +16 -20
  2. package/package.json +48 -45
  3. package/src/components/stalux/footer/FooterBadges.astro +2 -2
  4. package/src/components/stalux/layout/head.astro +1 -1
  5. package/src/components/stalux/posts/postContent.astro +1 -1
  6. package/src/index.ts +57 -51
  7. package/src/internal/cjk-search-index.ts +4 -7
  8. package/src/internal/components-plugin.ts +1 -3
  9. package/src/internal/font-slices.ts +36 -29
  10. package/src/internal/html-images.ts +1 -1
  11. package/src/internal/html-styles.ts +1 -1
  12. package/src/internal/image-dimensions.ts +3 -8
  13. package/src/internal/injected-routes.ts +1 -3
  14. package/src/internal/page-output-cache.ts +11 -15
  15. package/src/internal/post-cover.ts +22 -26
  16. package/src/internal/project-mode.ts +3 -2
  17. package/src/internal/public-assets.ts +42 -23
  18. package/src/internal/runtime-cache-key.ts +42 -27
  19. package/src/internal/satteri-config.ts +2 -2
  20. package/src/internal/search-style.ts +6 -7
  21. package/src/internal/social-card-cover.ts +12 -10
  22. package/src/internal/social-card-renderer.ts +8 -11
  23. package/src/internal/webmcp-mutations.ts +11 -5
  24. package/src/layouts/Stalux.astro +4 -4
  25. package/src/pages/openapi.json.ts +1 -1
  26. package/src/plugins/feature-flags.ts +2 -2
  27. package/src/schemas/collections.ts +1 -1
  28. package/src/schemas/config.ts +4 -1
  29. package/src/scripts/list-reveal.ts +7 -5
  30. package/src/scripts/swup-router.ts +1 -1
  31. package/src/scripts/weather.ts +2 -2
  32. package/src/scripts/webmcp.ts +4 -4
  33. package/src/utils/accent-color.ts +1 -1
  34. package/src/utils/ai-discovery.ts +6 -6
  35. package/src/utils/badge-generator.ts +1 -2
  36. package/src/utils/cc.ts +2 -2
  37. package/src/utils/dayjs.ts +5 -5
  38. package/src/utils/diagnostics.ts +3 -3
  39. package/src/utils/public-routes.ts +3 -3
  40. package/src/utils/seo-document.ts +2 -2
  41. package/src/utils/site-path.ts +1 -1
  42. package/src/utils/social-image.ts +1 -3
package/.release/cli.mjs CHANGED
@@ -1,8 +1,8 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
+ // @bun
2
3
 
3
4
  // scripts/cli.ts
4
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
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 — each page loads only needed characters
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️ PhotoSwipe lightbox
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 (existsSync(filePath)) {
187
- console.log(` ⏭ ${label} (exists, skipped)`);
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
- mkdirSync(dirname(filePath), { recursive: true });
191
- writeFileSync(filePath, content);
192
- console.log(` ✅ ${label}`);
190
+ await Bun.write(filePath, content);
191
+ console.log(` \u2705 ${label}`);
193
192
  }
194
- function main([command, targetArg = "."] = process.argv.slice(2)) {
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(`❌ Unknown command: ${command}`);
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
- ✅ Done! Stalux content initialized.
219
+ \u2705 Done! Stalux content initialized.
224
220
  `);
225
221
  printNextSteps();
226
222
  }
227
223
  function printHelp() {
228
224
  console.log(`
229
- Stalux — Modern Astro Blog Theme
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.51.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
- "node": ">=26"
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 && node scripts/dev.ts",
74
- "build": "bun run styles:build && astro build --timings && node scripts/verify-build.ts",
75
- "preview": "astro preview",
76
- "astro": "astro",
77
- "init": "node scripts/cli.ts init",
78
- "node:check": "tsc6 --project tsconfig.node.json",
79
- "build:cli": "bun build scripts/cli.ts --target=node --outfile .release/cli.mjs",
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": "node scripts/verify-build.ts",
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 && node scripts/verify-build.ts",
97
- "styles:build": "node scripts/build-styles.ts && node scripts/build-style-names.ts",
98
- "styles:watch": "node scripts/build-styles.ts --watch",
99
- "benchmark:performance": "node scripts/benchmark-performance.ts",
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 run scripts/build-backgrounds.ts"
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.3",
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.2",
110
- "@lucide/astro": "^1.56.0",
111
- "@mcp-b/webmcp-polyfill": "^5.1.0",
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.5.2",
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.17.1",
120
- "@waline/client": "^3.16.1",
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.2",
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.3",
131
- "pagefind": "^1.5.2",
132
- "parse5": "^8.0.1",
130
+ "magic-string": "^1.4.5",
131
+ "pagefind": "^1.6.0",
132
+ "parse5": "^8.1.0",
133
133
  "photoswipe": "^5.4.4",
134
- "satteri": "^0.10.5",
135
- "simple-icons": "^16.35.0",
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.10",
142
- "@biomejs/biome": "^2.5.15",
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
- "@types/node": "^26.6.5",
145
- "@unocss/astro": "^66.11.0",
146
- "@vitest/coverage-v8": "^5.0.4",
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.41.0",
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.11.0",
154
- "vite": "^8.3.5",
155
- "vitest": "^5.0.4"
153
+ "unocss": "^66.12.0",
154
+ "vite": "^8.3.6",
155
+ "vitest": "^5.1.0"
156
156
  },
157
157
  "peerDependencies": {
158
- "astro": "^7.4.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(/<\//g, "<\\/");
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(/\/$/, "")}.md` : "";
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 { copyFileSync, existsSync, promises as fs, readdirSync, readFileSync } from "node:fs";
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
- readFileSync(path.join(srcDir, "internal/compact-styles.ts"), "utf8") +
161
- readFileSync(path.join(srcDir, "internal/offscreen-layout.ts"), "utf8"),
162
- path.resolve(fileURLToPath(config.root), opt.contentDir ?? "stalux", "config"),
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
- process.env.STALUX_DEBUG === "1" ||
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 = readFileSync(siteYamlPath, "utf-8");
328
- const urlMatch = siteYaml.match(/^url\s*:\s*(.+)$/m);
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(/^["']|["']$/g, "")
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 NodeJS.ErrnoException).code === "ENOENT") {
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(fileURLToPath(config.outDir), "_astro", "fonts"),
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 = await fs.readdir(outDir, { recursive: true });
423
- const sourceDir = fileURLToPath(new URL(".", import.meta.url));
424
- const resolveModule = createRequire(import.meta.url).resolve;
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 fs.readFile(path.join(outDir, entry), "utf8");
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
- copyFileSync(
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 fs.readFile(output, "utf8");
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
- copyFileSync(
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 fs.writeFile(output, cached.html);
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
- copyFileSync(
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 fs.writeFile(output, after);
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 (!existsSync(source)) return;
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 fs.readFile(index, "utf8");
745
- const hasMultipleSitemaps = (indexXml.match(/<sitemap>/g)?.length ?? 0) > 1;
746
- await fs.copyFile(
747
- hasMultipleSitemaps ? index : source,
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 = createHash("sha256").update(json).digest("hex").slice(0, 20);
95
+ const hash = new Bun.CryptoHasher("sha256").update(json).digest("hex").slice(0, 20);
99
96
  const file = `${hash}.json`;
100
- await writeFile(path.join(directory, file), json);
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 readFile(path.join(outDir, file), "utf8");
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 writeFile(path.join(directory, "manifest.json"), JSON.stringify({ documents, shards }));
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",