valaxy 0.28.0-beta.6 → 0.28.0

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.
@@ -1,7 +1,7 @@
1
1
  import { ViteSSGOptions } from 'vite-ssg';
2
2
  import * as vite from 'vite';
3
3
  import { UserConfig, InlineConfig, ViteDevServer, PluginOption, Plugin } from 'vite';
4
- import { D as DefaultTheme, R as RuntimeConfig, a as RedirectItem, V as ValaxyConfig, P as PartialDeep, b as ValaxyAddon, S as SiteConfig, U as UserSiteConfig } from '../shared/valaxy.JIuR8V4d.mjs';
4
+ import { D as DefaultTheme, R as RuntimeConfig, a as RedirectItem, V as ValaxyConfig, P as PartialDeep, b as ValaxyAddon, S as SiteConfig, U as UserSiteConfig } from '../shared/valaxy.6MW2qn5T.mjs';
5
5
  import Vue from '@vitejs/plugin-vue';
6
6
  import { Hookable } from 'hookable';
7
7
  import { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
@@ -694,6 +694,30 @@ interface ValaxyExtendConfig {
694
694
  */
695
695
  maxDuration?: number;
696
696
  };
697
+ /**
698
+ * @en Taxonomy i18n validation during `valaxy dev` / `valaxy build`.
699
+ * Checks whether translated `tag.*` / `category.*` keys are consistently
700
+ * defined across configured languages.
701
+ *
702
+ * @zh `valaxy dev` / `valaxy build` 期间的 taxonomy i18n 校验。
703
+ * 用于检查 `tag.*` / `category.*` 翻译 key 是否在已配置语言中保持一致。
704
+ */
705
+ taxonomyI18n?: {
706
+ /**
707
+ * @en Validation level for taxonomy i18n checks.
708
+ * - `'off'`: disable checks
709
+ * - `'warn'`: print warnings and continue
710
+ * - `'error'`: fail validation after reporting all issues
711
+ *
712
+ * @zh taxonomy i18n 校验级别。
713
+ * - `'off'`:关闭检查
714
+ * - `'warn'`:输出 warning 并继续流程
715
+ * - `'error'`:输出所有问题后以错误结束
716
+ *
717
+ * @default 'warn'
718
+ */
719
+ level?: 'off' | 'warn' | 'error';
720
+ };
697
721
  };
698
722
  /**
699
723
  * @experimental
@@ -744,6 +768,12 @@ interface ValaxyExtendConfig {
744
768
  * @default true
745
769
  */
746
770
  katex: boolean;
771
+ /**
772
+ * @description:en-US Auto-extract the first image from markdown content for Open Graph fallback
773
+ * @description:zh-CN 自动从 Markdown 内容中提取第一张图片,作为 Open Graph 的回退图片
774
+ * @default true
775
+ */
776
+ extractFirstImage: boolean;
747
777
  };
748
778
  /**
749
779
  * Enable MathJax3 math rendering (aligned with VitePress `markdown.math`).
@@ -1,4 +1,4 @@
1
- export { A as ALL_ROUTE, E as EXCERPT_SEPARATOR, G as GLOBAL_STATE, P as PATHNAME_PROTOCOL_RE, V as ViteValaxyPlugins, b as build, c as cli, a as createServer, d as createValaxyPlugin, e as customElements, f as defaultSiteConfig, g as defaultValaxyConfig, h as defaultViteConfig, i as defineAddon, j as defineConfig, k as defineSiteConfig, l as defineTheme, m as defineValaxyAddon, n as defineValaxyConfig, o as defineValaxyTheme, p as encryptContent, q as generateClientRedirects, r as getGitTimestamp, s as getIndexHtml, t as getServerInfoText, u as isExternal, v as isInstalledGlobally, w as isKatexEnabled, x as isKatexPluginNeeded, y as isMathJaxEnabled, z as isPath, B as loadConfigFromFile, C as mergeValaxyConfig, D as mergeViteConfigs, F as postProcessForSSG, H as processValaxyOptions, I as registerDevCommand, J as resolveAddonsConfig, K as resolveImportPath, L as resolveImportUrl, M as resolveOptions, N as resolveSiteConfig, O as resolveSiteConfigFromRoot, Q as resolveThemeConfigFromRoot, R as resolveThemeValaxyConfig, S as resolveUserThemeConfig, T as resolveValaxyConfig, U as resolveValaxyConfigFromRoot, W as run, X as ssgBuild, Y as ssgBuildLegacy, Z as startValaxyDev, _ as toAtFS, $ as transformObject, a0 as version } from '../shared/valaxy.BVsZMcdc.mjs';
1
+ export { A as ALL_ROUTE, E as EXCERPT_SEPARATOR, G as GLOBAL_STATE, P as PATHNAME_PROTOCOL_RE, V as ViteValaxyPlugins, b as build, c as cli, a as createServer, d as createValaxyPlugin, e as customElements, f as defaultSiteConfig, g as defaultValaxyConfig, h as defaultViteConfig, i as defineAddon, j as defineConfig, k as defineSiteConfig, l as defineTheme, m as defineValaxyAddon, n as defineValaxyConfig, o as defineValaxyTheme, p as encryptContent, q as generateClientRedirects, r as getGitTimestamp, s as getIndexHtml, t as getServerInfoText, u as isExternal, v as isInstalledGlobally, w as isKatexEnabled, x as isKatexPluginNeeded, y as isMathJaxEnabled, z as isPath, B as loadConfigFromFile, C as mergeValaxyConfig, D as mergeViteConfigs, F as postProcessForSSG, H as processValaxyOptions, I as registerDevCommand, J as resolveAddonsConfig, K as resolveImportPath, L as resolveImportUrl, M as resolveOptions, N as resolveSiteConfig, O as resolveSiteConfigFromRoot, Q as resolveThemeConfigFromRoot, R as resolveThemeValaxyConfig, S as resolveUserThemeConfig, T as resolveValaxyConfig, U as resolveValaxyConfigFromRoot, W as run, X as ssgBuild, Y as ssgBuildLegacy, Z as startValaxyDev, _ as toAtFS, $ as transformObject, a0 as version } from '../shared/valaxy.DQ6HsU2J.mjs';
2
2
  import 'node:path';
3
3
  import 'fs-extra';
4
4
  import 'consola/utils';
@@ -167,6 +167,18 @@ interface PageFrontMatter extends BaseFrontMatter {
167
167
  * @description 封面图片
168
168
  */
169
169
  cover: string;
170
+ /**
171
+ * @description:en-US Open Graph image for SEO
172
+ * @description:zh-CN Open Graph 图片,用于 SEO
173
+ */
174
+ ogImage: string;
175
+ /**
176
+ * @protected
177
+ * @tutorial ⚠️ DO NOT SET MANUALLY (auto-extracted from markdown content)
178
+ * @description:en-US First image URL extracted from markdown content
179
+ * @description:zh-CN 从 Markdown 内容中自动提取的第一张图片 URL
180
+ */
181
+ firstImage: string;
170
182
  /**
171
183
  * display toc
172
184
  * @description 是否显示目录
@@ -638,10 +650,6 @@ interface SiteConfig {
638
650
  * @zh 是否启用
639
651
  */
640
652
  enable: boolean;
641
- /**
642
- * @deprecated will be deprecated, use search.provider instead
643
- */
644
- type?: SiteConfig['search']['provider'];
645
653
  /**
646
654
  * Search Type
647
655
  * - algolia: Algolia Search
@@ -28,7 +28,7 @@ import { Feed } from 'feed';
28
28
  import MarkdownIt from 'markdown-it';
29
29
  import { table, getBorderCharacters } from 'table';
30
30
  import { createHooks } from 'hookable';
31
- import { execFileSync, exec } from 'node:child_process';
31
+ import { execFileSync, execSync, exec } from 'node:child_process';
32
32
  import v8 from 'node:v8';
33
33
  import generateSitemap from 'vite-ssg-sitemap';
34
34
  import { createMarkdownItAsync, MarkdownItAsync } from 'markdown-it-async';
@@ -854,6 +854,9 @@ const defaultValaxyConfig = {
854
854
  foucGuard: {
855
855
  enabled: true,
856
856
  maxDuration: 5e3
857
+ },
858
+ taxonomyI18n: {
859
+ level: "warn"
857
860
  }
858
861
  },
859
862
  deploy: {},
@@ -875,7 +878,8 @@ const defaultValaxyConfig = {
875
878
  }
876
879
  },
877
880
  features: {
878
- katex: true
881
+ katex: true,
882
+ extractFirstImage: true
879
883
  },
880
884
  math: false,
881
885
  cdn: {
@@ -928,8 +932,9 @@ async function resolveValaxyConfigFromRoot(root, options) {
928
932
  }
929
933
  const mergeValaxyConfig = createDefu((obj, key, value) => {
930
934
  if (isFunction(obj[key]) && isFunction(value)) {
935
+ const original = obj[key];
931
936
  obj[key] = function(...args) {
932
- obj[key].call(this, ...args);
937
+ original.call(this, ...args);
933
938
  value.call(this, ...args);
934
939
  };
935
940
  return true;
@@ -985,6 +990,30 @@ function tObject(data, lang) {
985
990
  }
986
991
  return data;
987
992
  }
993
+ function isLocaleKey(value) {
994
+ return value.startsWith(LOCALE_PREFIX);
995
+ }
996
+ function stripLocalePrefix(value) {
997
+ return isLocaleKey(value) ? value.slice(LOCALE_PREFIX.length) : value;
998
+ }
999
+ function getLocaleMessageValue(messages, key) {
1000
+ return key.split(".").reduce((result, part) => result?.[part], messages);
1001
+ }
1002
+ function hasLocaleMessage(messages, key) {
1003
+ return getLocaleMessageValue(messages, key) !== void 0;
1004
+ }
1005
+ function resolveTaxonomyLocaleKey(namespace, rawValue) {
1006
+ if (isLocaleKey(rawValue)) {
1007
+ return {
1008
+ localeKey: stripLocalePrefix(rawValue),
1009
+ isExplicitLocaleKey: true
1010
+ };
1011
+ }
1012
+ return {
1013
+ localeKey: `${namespace}.${rawValue}`,
1014
+ isExplicitLocaleKey: false
1015
+ };
1016
+ }
988
1017
 
989
1018
  const indexRE = /(^|.*\/)index.md(.*)$/i;
990
1019
  function linkPlugin(md, externalAttrs, base) {
@@ -1692,7 +1721,7 @@ async function setupMarkdownPlugins(md, options, base = "/") {
1692
1721
  return md;
1693
1722
  }
1694
1723
 
1695
- const version = "0.28.0-beta.6";
1724
+ const version = "0.28.0";
1696
1725
 
1697
1726
  const GLOBAL_STATE = {
1698
1727
  valaxyApp: void 0,
@@ -2482,9 +2511,8 @@ function loadLocalesYml(localesPath, force = false) {
2482
2511
  return locales;
2483
2512
  }
2484
2513
  function nodeT(key, lang) {
2485
- if (key.startsWith(LOCALE_PREFIX)) {
2486
- key = key.slice(LOCALE_PREFIX.length);
2487
- }
2514
+ if (isLocaleKey(key))
2515
+ key = stripLocalePrefix(key);
2488
2516
  const data = NODE_I18N.locales[lang] || {};
2489
2517
  const keys = key.split(".");
2490
2518
  let result = data;
@@ -2529,7 +2557,7 @@ function getSiteUrl(options) {
2529
2557
  }
2530
2558
 
2531
2559
  function resolveText(value, lang) {
2532
- if (typeof value === "string" && value.startsWith(LOCALE_PREFIX))
2560
+ if (typeof value === "string" && isLocaleKey(value))
2533
2561
  return nodeT(value, lang);
2534
2562
  return tObject(value, lang);
2535
2563
  }
@@ -3368,10 +3396,24 @@ function inferDescription(frontmatter) {
3368
3396
  return description;
3369
3397
  return head && getHeadMetaContent(head, "description") || "";
3370
3398
  }
3399
+ function extractFirstImage(code) {
3400
+ const mdImageMatch = code.match(/!\[.*?\]\((.+?)\)/);
3401
+ if (mdImageMatch)
3402
+ return mdImageMatch[1];
3403
+ const htmlImageMatch = code.match(/<img\s[^>]*?src=["'](.+?)["']/);
3404
+ if (htmlImageMatch)
3405
+ return htmlImageMatch[1];
3406
+ return void 0;
3407
+ }
3371
3408
  async function generatePageData(code, id, options) {
3372
3409
  const fileInfo = Valaxy.state.idMap.get(id);
3373
3410
  const relativePath = path.relative(options.userRoot, id);
3374
3411
  const fm = JSON.parse(JSON.stringify(fileInfo?.frontmatter));
3412
+ if (options.config.features?.extractFirstImage !== false && !fm.ogImage && !fm.cover) {
3413
+ const firstImage = extractFirstImage(code);
3414
+ if (firstImage)
3415
+ fm.firstImage = firstImage;
3416
+ }
3375
3417
  const pageData = {
3376
3418
  title: fm.title || fileInfo?.title || "",
3377
3419
  titleTemplate: fm.titleTemplate,
@@ -3382,7 +3424,8 @@ async function generatePageData(code, id, options) {
3382
3424
  relativePath,
3383
3425
  filePath: id
3384
3426
  };
3385
- pageData.lastUpdated = await getGitTimestamp(id);
3427
+ if (options.config.siteConfig.lastUpdated)
3428
+ pageData.lastUpdated = await getGitTimestamp(id);
3386
3429
  return pageData;
3387
3430
  }
3388
3431
 
@@ -5375,17 +5418,21 @@ async function getPosts(params, options) {
5375
5418
  return { data, content, excerpt, path: i };
5376
5419
  });
5377
5420
  const rawPosts = await Promise.all(readFilePromises);
5421
+ const draftPosts = [];
5378
5422
  const filteredPosts = rawPosts.filter((p) => {
5379
5423
  const { data } = p;
5380
5424
  if (data.password)
5381
5425
  return false;
5382
5426
  if (data.draft) {
5427
+ draftPosts.push(p.path);
5383
5428
  return false;
5384
5429
  }
5385
5430
  if (data.hide)
5386
5431
  return false;
5387
5432
  return true;
5388
5433
  });
5434
+ if (draftPosts.length)
5435
+ consola.log(`[rss] Skipped ${draftPosts.length} draft post(s): ${draftPosts.join(", ")}`);
5389
5436
  const posts = [];
5390
5437
  for (const rawPost of filteredPosts) {
5391
5438
  const { data, path, content, excerpt } = rawPost;
@@ -5650,6 +5697,162 @@ const content = {
5650
5697
  loadAllContent: loadAllContent
5651
5698
  };
5652
5699
 
5700
+ function getConfiguredLanguages(options) {
5701
+ const configured = options.config.siteConfig.languages?.length ? options.config.siteConfig.languages : [options.config.siteConfig.lang || "en"];
5702
+ return [...new Set(configured.filter(Boolean))];
5703
+ }
5704
+ function normalizeToStringArray(value) {
5705
+ if (typeof value === "string")
5706
+ return value.trim() ? [value] : [];
5707
+ if (Array.isArray(value)) {
5708
+ return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
5709
+ }
5710
+ return [];
5711
+ }
5712
+ async function collectPageFiles(options) {
5713
+ const userPages = await scanPageFiles(options.userRoot, ["**/*.md"]);
5714
+ const userPageFiles = userPages.map((file) => resolve(options.userRoot, "pages", file));
5715
+ const contentDir = resolve(options.tempDir, "content", "pages");
5716
+ if (!await fs.pathExists(contentDir))
5717
+ return userPageFiles;
5718
+ const contentPages = await fg(["**/*.md"], {
5719
+ cwd: contentDir,
5720
+ ignore: ["**/node_modules"]
5721
+ });
5722
+ return [.../* @__PURE__ */ new Set([
5723
+ ...userPageFiles,
5724
+ ...contentPages.map((file) => resolve(contentDir, file))
5725
+ ])];
5726
+ }
5727
+ async function loadMergedLocaleMessages(options) {
5728
+ const languages = getConfiguredLanguages(options);
5729
+ const messages = Object.fromEntries(languages.map((lang) => [lang, {}]));
5730
+ for (const root of options.roots) {
5731
+ for (const lang of languages) {
5732
+ const localeFiles = [
5733
+ resolve(root, "locales", `${lang}.yml`),
5734
+ resolve(root, "locales", `${lang}.yaml`)
5735
+ ];
5736
+ for (const localeFile of localeFiles) {
5737
+ if (!await fs.pathExists(localeFile))
5738
+ continue;
5739
+ const content = await fs.readFile(localeFile, "utf-8");
5740
+ if (!content.trim())
5741
+ continue;
5742
+ const data = yaml.load(content);
5743
+ messages[lang] = replaceArrMerge(data || {}, messages[lang]);
5744
+ }
5745
+ }
5746
+ }
5747
+ return messages;
5748
+ }
5749
+ async function collectTaxonomyUsages(options) {
5750
+ const files = await collectPageFiles(options);
5751
+ const posts = await readPostFiles(files);
5752
+ const usageMap = /* @__PURE__ */ new Map();
5753
+ const addUsage = (namespace, rawValue, file) => {
5754
+ const key = `${namespace}:${rawValue}`;
5755
+ if (!usageMap.has(key)) {
5756
+ usageMap.set(key, {
5757
+ namespace,
5758
+ rawValue,
5759
+ files: /* @__PURE__ */ new Set()
5760
+ });
5761
+ }
5762
+ usageMap.get(key).files.add(file);
5763
+ };
5764
+ for (const post of posts) {
5765
+ const file = relative(options.userRoot, post.filePath);
5766
+ for (const tag of normalizeToStringArray(post.data.tags))
5767
+ addUsage("tag", tag, file);
5768
+ for (const category of normalizeToStringArray(post.data.categories))
5769
+ addUsage("category", category, file);
5770
+ }
5771
+ return Array.from(usageMap.values(), (item) => ({
5772
+ namespace: item.namespace,
5773
+ rawValue: item.rawValue,
5774
+ files: [...item.files].sort()
5775
+ })).sort((a, b) => `${a.namespace}:${a.rawValue}`.localeCompare(`${b.namespace}:${b.rawValue}`));
5776
+ }
5777
+ function findTaxonomyI18nIssues(usages, messages, languages) {
5778
+ const issues = [];
5779
+ for (const usage of usages) {
5780
+ const { namespace, rawValue, files } = usage;
5781
+ const { localeKey, isExplicitLocaleKey } = resolveTaxonomyLocaleKey(namespace, rawValue);
5782
+ const presentLanguages = languages.filter((lang) => hasLocaleMessage(messages[lang], localeKey));
5783
+ const missingLanguages = languages.filter((lang) => !hasLocaleMessage(messages[lang], localeKey));
5784
+ if (isExplicitLocaleKey) {
5785
+ if (missingLanguages.length) {
5786
+ issues.push({
5787
+ type: "explicit-locale-key-missing",
5788
+ namespace,
5789
+ rawValue,
5790
+ localeKey,
5791
+ presentLanguages,
5792
+ missingLanguages,
5793
+ files
5794
+ });
5795
+ }
5796
+ continue;
5797
+ }
5798
+ if (presentLanguages.length === 0 || presentLanguages.length === languages.length)
5799
+ continue;
5800
+ issues.push({
5801
+ type: "partial-locale-coverage",
5802
+ namespace,
5803
+ rawValue,
5804
+ localeKey,
5805
+ presentLanguages,
5806
+ missingLanguages,
5807
+ files
5808
+ });
5809
+ }
5810
+ return issues;
5811
+ }
5812
+ function formatFiles(files) {
5813
+ const preview = files.slice(0, 3).map((file) => colors.dim(file)).join(", ");
5814
+ if (files.length <= 3)
5815
+ return preview;
5816
+ return `${preview}${colors.dim(` and ${files.length - 3} more`)}`;
5817
+ }
5818
+ function logTaxonomyI18nIssues(issues, level) {
5819
+ if (!issues.length)
5820
+ return;
5821
+ const log = level === "error" ? consola.error : consola.warn;
5822
+ const count = level === "error" ? colors.red(String(issues.length)) : colors.yellow(String(issues.length));
5823
+ log(`Detected ${count} taxonomy i18n issue(s).`);
5824
+ for (const issue of issues) {
5825
+ const where = formatFiles(issue.files);
5826
+ if (issue.type === "explicit-locale-key-missing") {
5827
+ log(
5828
+ `[taxonomy-i18n] ${colors.cyan(issue.rawValue)} is an explicit locale key but ${colors.red(`missing in: ${issue.missingLanguages.join(", ")}`)}. Files: ${where}`
5829
+ );
5830
+ continue;
5831
+ }
5832
+ log(
5833
+ `[taxonomy-i18n] ${colors.cyan(issue.rawValue)} resolves to ${colors.cyan(issue.localeKey)} in ${colors.green(issue.presentLanguages.join(", "))}, but is ${colors.red(`missing in: ${issue.missingLanguages.join(", ")}`)}. Files: ${where}`
5834
+ );
5835
+ }
5836
+ }
5837
+ function resolveTaxonomyI18nValidationLevel(options) {
5838
+ return options.config.build.taxonomyI18n?.level || "warn";
5839
+ }
5840
+ async function validateTaxonomyI18n(options) {
5841
+ const level = resolveTaxonomyI18nValidationLevel(options);
5842
+ if (level === "off")
5843
+ return [];
5844
+ const languages = getConfiguredLanguages(options);
5845
+ const [messages, usages] = await Promise.all([
5846
+ loadMergedLocaleMessages(options),
5847
+ collectTaxonomyUsages(options)
5848
+ ]);
5849
+ const issues = findTaxonomyI18nIssues(usages, messages, languages);
5850
+ logTaxonomyI18nIssues(issues, level);
5851
+ if (level === "error" && issues.length)
5852
+ throw new Error(`Taxonomy i18n validation failed with ${issues.length} issue(s).`);
5853
+ return issues;
5854
+ }
5855
+
5653
5856
  function getServerInfoText(msg) {
5654
5857
  return `${valaxyPrefix} ${colors.gray(msg)}`;
5655
5858
  }
@@ -5790,6 +5993,7 @@ async function execBuild({ ssg, ssgEngine, root, output, log }) {
5790
5993
  );
5791
5994
  await callHookWithLog("config:init", valaxyApp);
5792
5995
  await callHookWithLog("build:before", valaxyApp);
5996
+ await validateTaxonomyI18n(options);
5793
5997
  consola.box("\u{1F320} Start building...");
5794
5998
  try {
5795
5999
  if (ssg) {
@@ -5897,14 +6101,108 @@ function registerCleanCommand(cli) {
5897
6101
  );
5898
6102
  }
5899
6103
 
5900
- function registerDebugCommand(cli) {
5901
- cli.command("debug", "Debug your blog", async () => {
5902
- console.log();
5903
- consola.log(" Operating System:", colors.green(os.platform()));
5904
- consola.log(" Node.JS Version:", colors.green(process.version));
5905
- consola.log(" Valaxy Version:", colors.cyan(`v${version}`));
6104
+ function getPnpmVersion() {
6105
+ try {
6106
+ return execSync("pnpm --version", { encoding: "utf-8" }).trim();
6107
+ } catch {
6108
+ return "not found";
6109
+ }
6110
+ }
6111
+ async function collectDebugInfo() {
6112
+ const info = {
6113
+ os: os.platform(),
6114
+ arch: os.arch(),
6115
+ node: process.version,
6116
+ pnpm: getPnpmVersion(),
6117
+ valaxy: version
6118
+ };
6119
+ try {
6120
+ const options = await resolveOptions({ userRoot: process.cwd() });
6121
+ info.userRoot = options.userRoot;
6122
+ info.theme = options.theme;
6123
+ info.themeVersion = options.config.themeConfig?.pkg?.version;
6124
+ info.addons = options.addons.filter((a) => a.enable).map((a) => ({
6125
+ name: a.name,
6126
+ version: a.pkg?.version || "unknown",
6127
+ global: a.global
6128
+ }));
6129
+ info.pages = options.pages.length;
6130
+ } catch {
6131
+ }
6132
+ return info;
6133
+ }
6134
+ function printFancy(info) {
6135
+ const lines = [];
6136
+ lines.push(`${colors.bold(colors.cyan("Environment"))}`);
6137
+ lines.push(` OS: ${colors.green(`${info.os} ${info.arch}`)}`);
6138
+ lines.push(` Node.js: ${colors.green(info.node)}`);
6139
+ lines.push(` Package Manager: ${colors.green(`pnpm ${info.pnpm}`)}`);
6140
+ lines.push(` Valaxy: ${colors.cyan(`v${info.valaxy}`)}`);
6141
+ if (info.theme) {
6142
+ lines.push("");
6143
+ lines.push(`${colors.bold(colors.cyan("Project"))}`);
6144
+ lines.push(` Root: ${colors.dim(info.userRoot)}`);
6145
+ lines.push(` Theme: ${colors.green(info.theme)} ${colors.blue(`v${info.themeVersion || "unknown"}`)}`);
6146
+ if (info.addons && info.addons.length > 0) {
6147
+ lines.push(` Addons:`);
6148
+ info.addons.forEach((addon, i) => {
6149
+ const prefix = i === info.addons.length - 1 ? "\u2514\u2500" : "\u251C\u2500";
6150
+ const globalTag = addon.global ? colors.cyan(" (global)") : "";
6151
+ lines.push(` ${prefix} ${colors.yellow(addon.name)} ${colors.blue(`v${addon.version}`)}${globalTag}`);
6152
+ });
6153
+ } else {
6154
+ lines.push(` Addons: ${colors.dim("none")}`);
6155
+ }
6156
+ lines.push(` Pages: ${colors.green(String(info.pages))}`);
6157
+ }
6158
+ consola.box({
6159
+ title: "\u{1F30C} Valaxy Debug Info",
6160
+ message: lines.join("\n"),
6161
+ style: {
6162
+ borderColor: "cyan"
6163
+ }
5906
6164
  });
5907
6165
  }
6166
+ function printPlain(info) {
6167
+ const lines = [];
6168
+ lines.push("## Environment");
6169
+ lines.push(`- OS: ${info.os} ${info.arch}`);
6170
+ lines.push(`- Node: ${info.node}`);
6171
+ lines.push(`- Package Manager: pnpm ${info.pnpm}`);
6172
+ lines.push(`- Valaxy: v${info.valaxy}`);
6173
+ if (info.theme) {
6174
+ lines.push("");
6175
+ lines.push("## Project");
6176
+ lines.push(`- Root: ${info.userRoot}`);
6177
+ lines.push(`- Theme: ${info.theme} (v${info.themeVersion || "unknown"})`);
6178
+ if (info.addons && info.addons.length > 0) {
6179
+ const addonStr = info.addons.map((a) => `${a.name} (v${a.version})${a.global ? " [global]" : ""}`).join(", ");
6180
+ lines.push(`- Addons: ${addonStr}`);
6181
+ } else {
6182
+ lines.push("- Addons: none");
6183
+ }
6184
+ lines.push(`- Pages: ${info.pages}`);
6185
+ }
6186
+ console.log(lines.join("\n"));
6187
+ }
6188
+ function registerDebugCommand(cli) {
6189
+ cli.command(
6190
+ "debug",
6191
+ "Display debug information for your Valaxy project",
6192
+ (args) => args.option("plain", {
6193
+ type: "boolean",
6194
+ default: false,
6195
+ describe: "Output plain text without colors (for pasting into issues)"
6196
+ }),
6197
+ async (args) => {
6198
+ const info = await collectDebugInfo();
6199
+ if (args.plain)
6200
+ printPlain(info);
6201
+ else
6202
+ printFancy(info);
6203
+ }
6204
+ );
6205
+ }
5908
6206
 
5909
6207
  function registerDeployCommand(cli) {
5910
6208
  cli.command("deploy", "deploy your blog to the cloud", async () => {
@@ -6075,6 +6373,7 @@ async function startValaxyDev({
6075
6373
  await valaxyApp.hooks.callHook("content:before-load");
6076
6374
  await loadAllContent(loaders, ctx);
6077
6375
  await valaxyApp.hooks.callHook("content:loaded");
6376
+ await validateTaxonomyI18n(resolvedOptions);
6078
6377
  for (const loader of loaders) {
6079
6378
  if (loader.devPollInterval) {
6080
6379
  const poll = async () => {
@@ -6082,6 +6381,7 @@ async function startValaxyDev({
6082
6381
  await valaxyApp.hooks.callHook("content:before-load");
6083
6382
  await loadAllContent([loader], ctx);
6084
6383
  await valaxyApp.hooks.callHook("content:loaded");
6384
+ await validateTaxonomyI18n(resolvedOptions);
6085
6385
  } catch (error) {
6086
6386
  consola.error("[content-loader] Error while polling:", error);
6087
6387
  } finally {
@@ -6092,6 +6392,8 @@ async function startValaxyDev({
6092
6392
  setTimeout(poll, loader.devPollInterval);
6093
6393
  }
6094
6394
  }
6395
+ } else {
6396
+ await validateTaxonomyI18n(resolvedOptions);
6095
6397
  }
6096
6398
  const viteConfig = mergeConfig({
6097
6399
  // initial vite config
@@ -1,5 +1,5 @@
1
- import { c as Post } from '../shared/valaxy.JIuR8V4d.mjs';
2
- export { A as Album, B as BaseFrontMatter, D as DefaultTheme, E as ExcerptType, F as FuseListItem, d as Page, e as PageFrontMatter, P as PartialDeep, f as Photo, g as Pkg, h as PostFrontMatter, a as RedirectItem, i as RedirectRule, R as RuntimeConfig, S as SiteConfig, j as SocialLink, U as UserSiteConfig, k as UserValaxyConfig, b as ValaxyAddon, V as ValaxyConfig } from '../shared/valaxy.JIuR8V4d.mjs';
1
+ import { c as Post } from '../shared/valaxy.6MW2qn5T.mjs';
2
+ export { A as Album, B as BaseFrontMatter, D as DefaultTheme, E as ExcerptType, F as FuseListItem, d as Page, e as PageFrontMatter, P as PartialDeep, f as Photo, g as Pkg, h as PostFrontMatter, a as RedirectItem, i as RedirectRule, R as RuntimeConfig, S as SiteConfig, j as SocialLink, U as UserSiteConfig, k as UserValaxyConfig, b as ValaxyAddon, V as ValaxyConfig } from '../shared/valaxy.6MW2qn5T.mjs';
3
3
  import { Header } from '@valaxyjs/utils';
4
4
  import '@vueuse/integrations/useFuse';
5
5
  import 'medium-zoom';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "valaxy",
3
3
  "type": "module",
4
- "version": "0.28.0-beta.6",
4
+ "version": "0.28.0",
5
5
  "description": "📄 Vite & Vue powered static blog generator.",
6
6
  "author": {
7
7
  "email": "me@yunyoujun.cn",
@@ -57,7 +57,7 @@
57
57
  "types"
58
58
  ],
59
59
  "engines": {
60
- "node": "^14.18.0 || >=16.0.0"
60
+ "node": "^18.0.0 || >=20.0.0"
61
61
  },
62
62
  "dependencies": {
63
63
  "@antfu/install-pkg": "^1.1.0",
@@ -95,7 +95,7 @@
95
95
  "jiti": "^2.6.1",
96
96
  "js-base64": "^3.7.8",
97
97
  "js-yaml": "^4.1.1",
98
- "katex": "^0.16.38",
98
+ "katex": "^0.16.42",
99
99
  "lru-cache": "^11.2.7",
100
100
  "markdown-it": "^14.1.1",
101
101
  "markdown-it-anchor": "^9.2.0",
@@ -105,12 +105,12 @@
105
105
  "markdown-it-emoji": "^3.0.0",
106
106
  "markdown-it-footnote": "^4.0.0",
107
107
  "markdown-it-image-figures": "^2.1.1",
108
- "markdown-it-table-of-contents": "^1.1.0",
108
+ "markdown-it-table-of-contents": "^1.2.0",
109
109
  "markdown-it-task-lists": "^2.1.1",
110
110
  "medium-zoom": "^1.1.0",
111
111
  "mermaid": "^11.13.0",
112
112
  "minisearch": "^7.2.0",
113
- "mlly": "^1.8.1",
113
+ "mlly": "^1.8.2",
114
114
  "nprogress": "^0.2.0",
115
115
  "open": "10.1.0",
116
116
  "ora": "^9.3.0",
@@ -129,19 +129,18 @@
129
129
  "unplugin-vue-components": "28.0.0",
130
130
  "unplugin-vue-markdown": "^30.0.0",
131
131
  "vanilla-lazyload": "^19.1.3",
132
- "vite": "^8.0.0",
133
- "vite-dev-rpc": "^1.1.0",
134
- "vite-plugin-vue-devtools": "^8.1.0",
135
- "vite-plugin-vue-layouts-next": "^2.0.1",
132
+ "vite": "^8.0.2",
133
+ "vite-plugin-vue-devtools": "^8.1.1",
134
+ "vite-plugin-vue-layouts-next": "^2.1.0",
136
135
  "vite-ssg": "^28.3.0",
137
136
  "vite-ssg-sitemap": "^0.10.0",
138
137
  "vitepress-plugin-group-icons": "^1.7.1",
139
138
  "vue": "3.5.22",
140
139
  "vue-i18n": "^11.3.0",
141
- "vue-router": "^5.0.3",
140
+ "vue-router": "^5.0.4",
142
141
  "yargs": "^18.0.0",
143
- "@valaxyjs/devtools": "0.28.0-beta.6",
144
- "@valaxyjs/utils": "0.28.0-beta.6"
142
+ "@valaxyjs/devtools": "0.28.0",
143
+ "@valaxyjs/utils": "0.28.0"
145
144
  },
146
145
  "devDependencies": {
147
146
  "@mdit-vue/plugin-component": "^3.0.2",
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs-extra'
2
2
  import yaml from 'js-yaml'
3
- import { LOCALE_PREFIX } from '../constants'
3
+ import { isLocaleKey, stripLocalePrefix } from '../utils/i18n'
4
4
 
5
5
  export const NODE_I18N: {
6
6
  locales: Record<string, any>
@@ -64,9 +64,9 @@ export function loadLocalesYml(localesPath: string, force = false): Record<strin
64
64
  * ```
65
65
  */
66
66
  export function nodeT(key: string, lang: string): string {
67
- if (key.startsWith(LOCALE_PREFIX)) {
68
- key = key.slice(LOCALE_PREFIX.length)
69
- }
67
+ if (isLocaleKey(key))
68
+ key = stripLocalePrefix(key)
69
+
70
70
  const data = NODE_I18N.locales[lang] || {}
71
71
  const keys = key.split('.')
72
72
  let result: any = data