ardo 3.5.0 → 3.6.1

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 (70) hide show
  1. package/README.md +1 -1
  2. package/dist/{DocPage-EIVMae_6.js → DocPage-Yt8MMpBg.js} +351 -67
  3. package/dist/DocPage-Yt8MMpBg.js.map +1 -0
  4. package/dist/assets/src/ui/components/Accordion.css.ts.vanilla-BPEoNgJt.css +103 -0
  5. package/dist/assets/src/ui/components/Badge.css.ts.vanilla-D9jsj0rg.css +44 -0
  6. package/dist/assets/src/ui/components/Card.css.ts.vanilla-CO6uiEzC.css +97 -0
  7. package/dist/assets/src/ui/theme/{dark.css.ts.vanilla-CSJkJvIz.css → dark.css.ts.vanilla-yFCWJiX4.css} +3 -0
  8. package/dist/assets/src/ui/theme/{light.css.ts.vanilla-CFz9jeJK.css → light.css.ts.vanilla-DDKnXFOi.css} +3 -0
  9. package/dist/{brand-icons-Di8w0Nu9.js → brand-icons-DBTSSnty.js} +1 -1
  10. package/dist/{brand-icons-Di8w0Nu9.js.map → brand-icons-DBTSSnty.js.map} +1 -1
  11. package/dist/config/index.d.ts +2 -2
  12. package/dist/config/index.d.ts.map +1 -1
  13. package/dist/config/index.js +5 -0
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/{contract.css-eFQbUr4z.d.ts → contract.css-BwTjjNXQ.d.ts} +4 -1
  16. package/dist/contract.css-BwTjjNXQ.d.ts.map +1 -0
  17. package/dist/{favicon-Cx-inut3.js → favicon-DN3ymUVm.js} +1 -1
  18. package/dist/{favicon-Cx-inut3.js.map → favicon-DN3ymUVm.js.map} +1 -1
  19. package/dist/{generator-CYSyo4Vz.js → generator-Cc4WGRdf.js} +1 -1
  20. package/dist/{generator-CYSyo4Vz.js.map → generator-Cc4WGRdf.js.map} +1 -1
  21. package/dist/icons/index.d.ts +5 -6
  22. package/dist/icons/index.d.ts.map +1 -1
  23. package/dist/icons/index.js +1 -1
  24. package/dist/{index-CuMTHUxX.d.ts → index-BSWG2TdH.d.ts} +6 -8
  25. package/dist/index-BSWG2TdH.d.ts.map +1 -0
  26. package/dist/{index-BcekgOfA.d.ts → index-Dzu13I_t.d.ts} +138 -47
  27. package/dist/index-Dzu13I_t.d.ts.map +1 -0
  28. package/dist/index.d.ts +3 -3
  29. package/dist/index.js +3 -3
  30. package/dist/mdx/provider.d.ts +1 -1
  31. package/dist/mdx/provider.d.ts.map +1 -1
  32. package/dist/mdx/provider.js +6 -1
  33. package/dist/mdx/provider.js.map +1 -1
  34. package/dist/runtime/index.d.ts +2 -2
  35. package/dist/runtime/index.js +1 -1
  36. package/dist/{sidebar-utils-C06DJsx4.js → sidebar-utils-tTBVAPLE.js} +1 -1
  37. package/dist/{sidebar-utils-C06DJsx4.js.map → sidebar-utils-tTBVAPLE.js.map} +1 -1
  38. package/dist/theme/index.d.ts +7 -1
  39. package/dist/theme/index.d.ts.map +1 -1
  40. package/dist/theme/index.js +9 -0
  41. package/dist/theme/index.js.map +1 -1
  42. package/dist/typedoc/components/index.d.ts +8 -8
  43. package/dist/typedoc/components/index.d.ts.map +1 -1
  44. package/dist/typedoc/components/index.js.map +1 -1
  45. package/dist/typedoc/index.d.ts +1 -1
  46. package/dist/typedoc/index.d.ts.map +1 -1
  47. package/dist/typedoc/index.js +3 -3
  48. package/dist/typedoc/index.js.map +1 -1
  49. package/dist/{types-Ck2Vm7NB.d.ts → types-DZKj8kWR.d.ts} +1 -1
  50. package/dist/types-DZKj8kWR.d.ts.map +1 -0
  51. package/dist/{types-B75OhnGa.d.ts → types-iGO1oGpR.d.ts} +49 -4
  52. package/dist/types-iGO1oGpR.d.ts.map +1 -0
  53. package/dist/ui/index.d.ts +2 -2
  54. package/dist/ui/index.js +3 -3
  55. package/dist/ui/styles.css +206 -0
  56. package/dist/ui/styles.js +4 -2
  57. package/dist/{ui-B6X8gAvz.js → ui-mBEFGR-s.js} +316 -138
  58. package/dist/ui-mBEFGR-s.js.map +1 -0
  59. package/dist/vite/index.d.ts +1 -1
  60. package/dist/vite/index.d.ts.map +1 -1
  61. package/dist/vite/index.js +543 -33
  62. package/dist/vite/index.js.map +1 -1
  63. package/package.json +15 -15
  64. package/dist/DocPage-EIVMae_6.js.map +0 -1
  65. package/dist/contract.css-eFQbUr4z.d.ts.map +0 -1
  66. package/dist/index-BcekgOfA.d.ts.map +0 -1
  67. package/dist/index-CuMTHUxX.d.ts.map +0 -1
  68. package/dist/types-B75OhnGa.d.ts.map +0 -1
  69. package/dist/types-Ck2Vm7NB.d.ts.map +0 -1
  70. package/dist/ui-B6X8gAvz.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { defaultMarkdownConfig, resolveConfig } from "../config/index.js";
2
- import { n as ARDO_FAVICON_SVG } from "../favicon-Cx-inut3.js";
3
- import { n as generateApiDocs } from "../generator-CYSyo4Vz.js";
2
+ import { n as ARDO_FAVICON_SVG } from "../favicon-DN3ymUVm.js";
3
+ import { n as generateApiDocs } from "../generator-Cc4WGRdf.js";
4
4
  import path from "node:path";
5
5
  import matter from "gray-matter";
6
6
  import rehypeStringify from "rehype-stringify";
@@ -13,7 +13,7 @@ import { visit } from "unist-util-visit";
13
13
  import { createHighlighter } from "shiki";
14
14
  import fs, { readFile } from "node:fs/promises";
15
15
  import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
16
- import fsSync, { existsSync } from "node:fs";
16
+ import fs$1, { existsSync } from "node:fs";
17
17
  import { execSync } from "node:child_process";
18
18
  import { Resvg } from "@resvg/resvg-js";
19
19
  import mdx from "@mdx-js/rollup";
@@ -84,7 +84,7 @@ function buildCodeBlockHtml(shikiHtml, options) {
84
84
  }
85
85
  function renderTitle(title) {
86
86
  if (title == null || title.length === 0) return "";
87
- return `<div data-title>${escapeHtml(title)}</div>`;
87
+ return `<div data-title>${escapeHtml$1(title)}</div>`;
88
88
  }
89
89
  function renderCodeLines(params) {
90
90
  const { highlightLines, lineNumbers, shikiHtml } = params;
@@ -128,7 +128,7 @@ function stripTags(html) {
128
128
  function decodeCommonEntities(text) {
129
129
  return text.replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&").replaceAll("&quot;", "\"").replaceAll("&#39;", "'");
130
130
  }
131
- function escapeHtml(text) {
131
+ function escapeHtml$1(text) {
132
132
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
133
133
  }
134
134
  //#endregion
@@ -224,11 +224,11 @@ function isElementNode(node) {
224
224
  return isRecord$6(node) && node.type === "element";
225
225
  }
226
226
  function getMetaString(codeNode) {
227
- const properties = toRecord(codeNode.properties);
227
+ const properties = toRecord$1(codeNode.properties);
228
228
  return typeof properties.metastring === "string" ? properties.metastring : "";
229
229
  }
230
230
  function getLanguage(codeNode) {
231
- const languageClass = toClassNameList(toRecord(codeNode.properties).className).find((className) => className.startsWith("language-"));
231
+ const languageClass = toClassNameList(toRecord$1(codeNode.properties).className).find((className) => className.startsWith("language-"));
232
232
  return languageClass == null ? "text" : languageClass.replace("language-", "");
233
233
  }
234
234
  function toClassNameList(className) {
@@ -257,7 +257,7 @@ function replaceNodeWithShikiContainer(parent, index, innerHtml) {
257
257
  function hasChildrenArray(value) {
258
258
  return isRecord$6(value) && Array.isArray(value.children);
259
259
  }
260
- function toRecord(value) {
260
+ function toRecord$1(value) {
261
261
  return isRecord$6(value) ? value : {};
262
262
  }
263
263
  function isRecord$6(value) {
@@ -705,6 +705,230 @@ function normalizePath(p) {
705
705
  return `/${p.replaceAll("\\", "/").replace(/^\/+/u, "")}`;
706
706
  }
707
707
  //#endregion
708
+ //#region src/vite/build-outputs.ts
709
+ function generateSitemap(entries, config) {
710
+ const sitemapConfig = typeof config.seo.sitemap === "object" ? config.seo.sitemap : {};
711
+ const changefreq = sitemapConfig.changefreq ?? "weekly";
712
+ const priority = sitemapConfig.priority ?? .7;
713
+ return [
714
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
715
+ "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">",
716
+ ...entries.filter((entry) => entry.frontmatter.sitemap !== false).map((entry) => {
717
+ return [
718
+ " <url>",
719
+ ` <loc>${escapeXml(toAbsoluteUrl$1(entry.path, config))}</loc>`,
720
+ ` <lastmod>${entry.lastmod.toISOString()}</lastmod>`,
721
+ ` <changefreq>${changefreq}</changefreq>`,
722
+ ` <priority>${priority.toFixed(1)}</priority>`,
723
+ " </url>"
724
+ ].join("\n");
725
+ }),
726
+ "</urlset>",
727
+ ""
728
+ ].join("\n");
729
+ }
730
+ function generateRobots(config) {
731
+ const robotsConfig = typeof config.seo.robots === "object" ? config.seo.robots : {};
732
+ const allowRules = robotsConfig.allow ?? ["/"];
733
+ const disallowRules = robotsConfig.disallow ?? [];
734
+ const lines = ["User-agent: *"];
735
+ for (const rule of allowRules) lines.push(`Allow: ${rule}`);
736
+ for (const rule of disallowRules) lines.push(`Disallow: ${rule}`);
737
+ lines.push(`Sitemap: ${toAbsoluteUrl$1("/sitemap.xml", config)}`);
738
+ return `${lines.join("\n")}\n`;
739
+ }
740
+ function collectRedirects(entries, config) {
741
+ const redirects = [...config.redirects];
742
+ for (const entry of entries) for (const from of entry.frontmatter.redirectFrom ?? []) redirects.push({
743
+ from,
744
+ to: entry.path
745
+ });
746
+ return dedupeRedirects(redirects);
747
+ }
748
+ function generateRedirectHtml(to) {
749
+ const escapedTo = escapeHtml(to);
750
+ return [
751
+ "<!doctype html>",
752
+ "<html lang=\"en\">",
753
+ "<head>",
754
+ " <meta charset=\"utf-8\">",
755
+ ` <meta http-equiv="refresh" content="0; url=${escapedTo}">`,
756
+ ` <link rel="canonical" href="${escapedTo}">`,
757
+ ` <script>location.replace(${JSON.stringify(to)})<\/script>`,
758
+ "</head>",
759
+ `<body><a href="${escapedTo}">Continue</a></body>`,
760
+ "</html>",
761
+ ""
762
+ ].join("\n");
763
+ }
764
+ function generateNetlifyRedirects(redirects) {
765
+ return `${redirects.map((redirect) => `${redirect.from} ${redirect.to} 301`).join("\n")}\n`;
766
+ }
767
+ function generateVercelRedirects(redirects) {
768
+ return `${JSON.stringify({ redirects: redirects.map((redirect) => ({
769
+ source: redirect.from,
770
+ destination: redirect.to,
771
+ permanent: true
772
+ })) }, null, 2)}\n`;
773
+ }
774
+ function checkInternalLinks(entries, config) {
775
+ if (config.linkCheck.enabled === false) return [];
776
+ const excludedPatterns = config.linkCheck.exclude ?? [];
777
+ const routeMap = new Map(entries.map((entry) => [entry.path, entry]));
778
+ const diagnostics = [];
779
+ for (const entry of entries) diagnostics.push(...checkEntryLinks(entry, {
780
+ config,
781
+ excludedPatterns,
782
+ routeMap
783
+ }));
784
+ return diagnostics;
785
+ }
786
+ function createBuildOutputAssets(entries, config) {
787
+ const assets = [];
788
+ if (shouldEmitSitemap(config)) assets.push({
789
+ fileName: "sitemap.xml",
790
+ source: generateSitemap(entries, config)
791
+ });
792
+ if (shouldEmitRobots(config)) assets.push({
793
+ fileName: "robots.txt",
794
+ source: generateRobots(config)
795
+ });
796
+ const redirects = collectRedirects(entries, config);
797
+ if (redirects.length === 0) return assets;
798
+ assets.push({
799
+ fileName: "_redirects",
800
+ source: generateNetlifyRedirects(redirects)
801
+ });
802
+ assets.push({
803
+ fileName: "vercel.json",
804
+ source: generateVercelRedirects(redirects)
805
+ });
806
+ for (const redirect of redirects) assets.push({
807
+ fileName: toRedirectAssetName(redirect.from),
808
+ source: generateRedirectHtml(redirect.to)
809
+ });
810
+ return assets;
811
+ }
812
+ function formatLinkCheckDiagnostics(diagnostics) {
813
+ return diagnostics.map((diagnostic) => `${diagnostic.filePath}: ${diagnostic.message}`).join("\n");
814
+ }
815
+ function shouldEmitSitemap(config) {
816
+ return config.seo.sitemap !== false;
817
+ }
818
+ function shouldEmitRobots(config) {
819
+ return config.seo.robots !== false;
820
+ }
821
+ function extractInternalLinks(content) {
822
+ const links = /* @__PURE__ */ new Set();
823
+ collectMarkdownLinks(content, links);
824
+ collectJsxHrefLinks(content, links);
825
+ return [...links].filter((link) => link !== "" && !link.startsWith("//"));
826
+ }
827
+ function normalizeInternalPath(hrefPath, currentPath) {
828
+ if (hrefPath === "") return currentPath;
829
+ return hrefPath.length > 1 ? hrefPath.replace(/\/$/u, "") : hrefPath;
830
+ }
831
+ function isExcludedLink(href, patterns) {
832
+ return patterns.some((pattern) => matchesPattern(href, pattern));
833
+ }
834
+ function matchesPattern(value, pattern) {
835
+ if (!pattern.includes("*")) return value === pattern;
836
+ const [prefix = "", suffix = ""] = pattern.split("*");
837
+ return value.startsWith(prefix) && value.endsWith(suffix);
838
+ }
839
+ function checkEntryLinks(entry, context) {
840
+ const diagnostics = [];
841
+ for (const href of extractInternalLinks(entry.content)) {
842
+ const diagnostic = checkSingleLink(entry, href, context);
843
+ if (diagnostic != null) diagnostics.push(diagnostic);
844
+ }
845
+ return diagnostics;
846
+ }
847
+ function checkSingleLink(entry, href, context) {
848
+ if (isExcludedLink(href, context.excludedPatterns)) return null;
849
+ const [targetPath = "", targetAnchor = ""] = href.split("#");
850
+ const normalizedPath = normalizeInternalPath(targetPath, entry.path);
851
+ const targetEntry = context.routeMap.get(normalizedPath);
852
+ if (targetEntry == null) return {
853
+ filePath: entry.filePath,
854
+ href,
855
+ message: `Missing internal route: ${href}`
856
+ };
857
+ if (context.config.linkCheck.checkAnchors !== false && targetAnchor !== "" && !targetEntry.anchors.includes(targetAnchor)) return {
858
+ filePath: entry.filePath,
859
+ href,
860
+ message: `Missing anchor "${targetAnchor}" on ${normalizedPath}`
861
+ };
862
+ return null;
863
+ }
864
+ function collectMarkdownLinks(content, links) {
865
+ let offset = 0;
866
+ while (offset < content.length) {
867
+ const start = content.indexOf("](/", offset);
868
+ if (start === -1) return;
869
+ const hrefStart = start + 2;
870
+ const hrefEnd = content.indexOf(")", hrefStart);
871
+ if (hrefEnd === -1) return;
872
+ links.add(content.slice(hrefStart, hrefEnd));
873
+ offset = hrefEnd + 1;
874
+ }
875
+ }
876
+ function collectJsxHrefLinks(content, links) {
877
+ collectQuotedHrefLinks({
878
+ content,
879
+ links,
880
+ marker: "href=\"/",
881
+ quote: "\""
882
+ });
883
+ collectQuotedHrefLinks({
884
+ content,
885
+ links,
886
+ marker: "href='/",
887
+ quote: "'"
888
+ });
889
+ }
890
+ function collectQuotedHrefLinks({ content, links, marker, quote }) {
891
+ let offset = 0;
892
+ while (offset < content.length) {
893
+ const start = content.indexOf(marker, offset);
894
+ if (start === -1) return;
895
+ const hrefStart = start + marker.length - 1;
896
+ const hrefEnd = content.indexOf(quote, hrefStart);
897
+ if (hrefEnd === -1) return;
898
+ links.add(content.slice(hrefStart, hrefEnd));
899
+ offset = hrefEnd + 1;
900
+ }
901
+ }
902
+ function toRedirectAssetName(from) {
903
+ return `${from.replace(/^\//u, "").replace(/\/$/u, "")}/index.html`;
904
+ }
905
+ function dedupeRedirects(redirects) {
906
+ const seen = /* @__PURE__ */ new Set();
907
+ const deduped = [];
908
+ for (const redirect of redirects) {
909
+ const key = `${redirect.from}\n${redirect.to}`;
910
+ if (!seen.has(key)) {
911
+ seen.add(key);
912
+ deduped.push(redirect);
913
+ }
914
+ }
915
+ return deduped;
916
+ }
917
+ function toAbsoluteUrl$1(routePath, config) {
918
+ const basePath = joinUrlPath(config.base, routePath);
919
+ if (config.siteUrl === "") return basePath;
920
+ return `${config.siteUrl.replace(/\/$/u, "")}${basePath}`;
921
+ }
922
+ function joinUrlPath(basePath, routePath) {
923
+ return `${basePath === "/" ? "" : `/${basePath.replaceAll(/^\/|\/$/gu, "")}`}${routePath === "/" ? "/" : `/${routePath.replaceAll(/^\/|\/$/gu, "")}`}` || "/";
924
+ }
925
+ function escapeXml(value) {
926
+ return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
927
+ }
928
+ function escapeHtml(value) {
929
+ return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;");
930
+ }
931
+ //#endregion
708
932
  //#region src/vite/codeblock-scan.ts
709
933
  const OPENING_TAG = "<ArdoCodeBlock";
710
934
  const CLOSING_TAG = "</ArdoCodeBlock>";
@@ -992,7 +1216,7 @@ function findPackageRoot(cwd) {
992
1216
  while (currentDir !== filesystemRoot) {
993
1217
  const parentDir = path.dirname(currentDir);
994
1218
  const packageJsonPath = path.join(parentDir, "package.json");
995
- if (fsSync.existsSync(packageJsonPath)) {
1219
+ if (fs$1.existsSync(packageJsonPath)) {
996
1220
  const relativePath = path.relative(cwd, parentDir);
997
1221
  return relativePath === "" ? "." : relativePath;
998
1222
  }
@@ -1028,12 +1252,12 @@ function detectGitHubBasename(cwd) {
1028
1252
  * Recursively copies files from src to dest, overwriting existing files.
1029
1253
  */
1030
1254
  function copyRecursive(src, dest) {
1031
- if (!fsSync.statSync(src).isDirectory()) {
1032
- fsSync.copyFileSync(src, dest);
1255
+ if (!fs$1.statSync(src).isDirectory()) {
1256
+ fs$1.copyFileSync(src, dest);
1033
1257
  return;
1034
1258
  }
1035
- if (!fsSync.existsSync(dest)) fsSync.mkdirSync(dest, { recursive: true });
1036
- for (const item of fsSync.readdirSync(src)) copyRecursive(path.join(src, item), path.join(dest, item));
1259
+ if (!fs$1.existsSync(dest)) fs$1.mkdirSync(dest, { recursive: true });
1260
+ for (const item of fs$1.readdirSync(src)) copyRecursive(path.join(src, item), path.join(dest, item));
1037
1261
  }
1038
1262
  function runGitCommand(cwd, command) {
1039
1263
  try {
@@ -1077,10 +1301,10 @@ function createFlattenPlugin() {
1077
1301
  if (baseName === "") return;
1078
1302
  const buildDir = path.join(process.cwd(), "build", "client");
1079
1303
  const nestedDir = path.join(buildDir, baseName);
1080
- if (!fsSync.existsSync(nestedDir)) return;
1304
+ if (!fs$1.existsSync(nestedDir)) return;
1081
1305
  console.log(`[ardo] Flattening build/client/${baseName}/ to build/client/ for GitHub Pages`);
1082
1306
  copyRecursive(nestedDir, buildDir);
1083
- fsSync.rmSync(nestedDir, {
1307
+ fs$1.rmSync(nestedDir, {
1084
1308
  recursive: true,
1085
1309
  force: true
1086
1310
  });
@@ -1097,7 +1321,7 @@ function trimSlashes(value) {
1097
1321
  }
1098
1322
  //#endregion
1099
1323
  //#region src/vite/icons.ts
1100
- const ICON_FILES = new Set([
1324
+ const ICON_FILES = /* @__PURE__ */ new Set([
1101
1325
  "favicon.ico",
1102
1326
  "icon.svg",
1103
1327
  "apple-touch-icon.png"
@@ -1236,12 +1460,28 @@ function transformMarkdownMeta(code, id, state) {
1236
1460
  if (!shouldInjectMeta(code, id, state)) return;
1237
1461
  const pageTitle = extractFrontmatterValue(code, "title");
1238
1462
  if (pageTitle == null || pageTitle === "") return;
1463
+ const siteTitle = state.resolvedConfig?.title ?? "Ardo";
1464
+ const titleSeparator = state.resolvedConfig?.titleSeparator ?? " | ";
1465
+ const siteDescription = state.resolvedConfig?.description ?? "";
1466
+ const description = extractFrontmatterValue(code, "description") ?? siteDescription;
1239
1467
  return {
1240
1468
  code: `${code}\nexport const meta = () => [${buildMetaEntries({
1469
+ canonical: extractFrontmatterValue(code, "canonical"),
1241
1470
  pageTitle,
1242
- siteTitle: state.resolvedConfig?.title ?? "Ardo",
1243
- titleSeparator: state.resolvedConfig?.titleSeparator ?? " | ",
1244
- description: extractFrontmatterValue(code, "description")
1471
+ routePath: getRoutePathFromId(id, state.routesDir, state.resolvedConfig?.base ?? "/"),
1472
+ siteMetadata: state.resolvedConfig?.metadata ?? {},
1473
+ siteUrl: state.resolvedConfig?.siteUrl ?? "",
1474
+ siteTitle,
1475
+ titleSeparator,
1476
+ description,
1477
+ ogDescription: extractFrontmatterValue(code, "ogDescription"),
1478
+ ogImage: extractFrontmatterValue(code, "ogImage"),
1479
+ ogTitle: extractFrontmatterValue(code, "ogTitle"),
1480
+ ogType: extractFrontmatterValue(code, "ogType"),
1481
+ twitterCard: extractFrontmatterValue(code, "twitterCard"),
1482
+ twitterDescription: extractFrontmatterValue(code, "twitterDescription"),
1483
+ twitterImage: extractFrontmatterValue(code, "twitterImage"),
1484
+ twitterTitle: extractFrontmatterValue(code, "twitterTitle")
1245
1485
  }).join(", ")}];\n`,
1246
1486
  map: null
1247
1487
  };
@@ -1250,11 +1490,65 @@ function shouldInjectMeta(code, id, state) {
1250
1490
  return isMarkdownFile(id) && id.startsWith(state.routesDir) && !hasMetaExport(code);
1251
1491
  }
1252
1492
  function buildMetaEntries(input) {
1493
+ const meta = resolveSocialMeta(input);
1494
+ return [
1495
+ ...buildBaseMetaEntries(meta),
1496
+ ...buildOpenGraphMetaEntries(meta),
1497
+ ...buildTwitterMetaEntries(meta)
1498
+ ];
1499
+ }
1500
+ function resolveSocialMeta(input) {
1253
1501
  const fullTitle = `${input.pageTitle}${input.titleSeparator}${input.siteTitle}`;
1254
- const entries = [`{ title: ${JSON.stringify(fullTitle)} }`];
1255
- if (input.description != null && input.description !== "") entries.push(`{ name: "description", content: ${JSON.stringify(input.description)} }`);
1502
+ const description = input.description ?? "";
1503
+ const ogTitle = input.ogTitle ?? fullTitle;
1504
+ const ogDescription = input.ogDescription ?? description;
1505
+ const ogImage = toAbsoluteUrl(input.ogImage ?? input.siteMetadata.image, input.siteUrl);
1506
+ return {
1507
+ canonicalUrl: toAbsoluteUrl(input.canonical ?? input.routePath, input.siteUrl),
1508
+ description,
1509
+ fullTitle,
1510
+ ogDescription,
1511
+ ogImage,
1512
+ ogTitle,
1513
+ ogType: input.ogType ?? input.siteMetadata.ogType ?? "article",
1514
+ twitterCard: input.twitterCard ?? input.siteMetadata.twitterCard ?? defaultTwitterCard(ogImage),
1515
+ twitterDescription: input.twitterDescription ?? ogDescription,
1516
+ twitterImage: toAbsoluteUrl(input.twitterImage ?? ogImage, input.siteUrl),
1517
+ twitterSite: input.siteMetadata.twitterSite,
1518
+ twitterTitle: input.twitterTitle ?? ogTitle
1519
+ };
1520
+ }
1521
+ function buildBaseMetaEntries(meta) {
1522
+ const entries = [`{ title: ${JSON.stringify(meta.fullTitle)} }`];
1523
+ if (meta.description !== "") entries.push(`{ name: "description", content: ${JSON.stringify(meta.description)} }`);
1256
1524
  return entries;
1257
1525
  }
1526
+ function buildOpenGraphMetaEntries(meta) {
1527
+ return [
1528
+ `{ property: "og:title", content: ${JSON.stringify(meta.ogTitle)} }`,
1529
+ ...optionalMetaEntry("property", "og:description", meta.ogDescription),
1530
+ `{ property: "og:type", content: ${JSON.stringify(meta.ogType)} }`,
1531
+ ...optionalMetaEntry("property", "og:image", meta.ogImage),
1532
+ ...optionalCanonicalEntries(meta.canonicalUrl)
1533
+ ];
1534
+ }
1535
+ function buildTwitterMetaEntries(meta) {
1536
+ return [
1537
+ `{ name: "twitter:card", content: ${JSON.stringify(meta.twitterCard)} }`,
1538
+ `{ name: "twitter:title", content: ${JSON.stringify(meta.twitterTitle)} }`,
1539
+ ...optionalMetaEntry("name", "twitter:description", meta.twitterDescription),
1540
+ ...optionalMetaEntry("name", "twitter:image", meta.twitterImage),
1541
+ ...optionalMetaEntry("name", "twitter:site", meta.twitterSite)
1542
+ ];
1543
+ }
1544
+ function optionalMetaEntry(attribute, key, value) {
1545
+ if (value == null || value === "") return [];
1546
+ return [`{ ${attribute}: ${JSON.stringify(key)}, content: ${JSON.stringify(value)} }`];
1547
+ }
1548
+ function optionalCanonicalEntries(canonicalUrl) {
1549
+ if (canonicalUrl == null) return [];
1550
+ return [`{ tagName: "link", rel: "canonical", href: ${JSON.stringify(canonicalUrl)} }`, `{ property: "og:url", content: ${JSON.stringify(canonicalUrl)} }`];
1551
+ }
1258
1552
  function isMarkdownFile(id) {
1259
1553
  return id.endsWith(".md") || id.endsWith(".mdx");
1260
1554
  }
@@ -1264,13 +1558,92 @@ function hasMetaExport(code) {
1264
1558
  function extractFrontmatterValue(code, key) {
1265
1559
  const frontmatterStart = code.indexOf("export const frontmatter");
1266
1560
  if (frontmatterStart === -1) return;
1267
- const valuePrefix = `${key}: "`;
1268
- const valueStart = code.indexOf(valuePrefix, frontmatterStart);
1269
- if (valueStart === -1) return;
1270
- const startIndex = valueStart + valuePrefix.length;
1271
- const endIndex = code.indexOf("\"", startIndex);
1272
- if (endIndex === -1) return;
1273
- return code.slice(startIndex, endIndex);
1561
+ return findFrontmatterStringValue(code.slice(frontmatterStart), key);
1562
+ }
1563
+ function findFrontmatterStringValue(code, key) {
1564
+ const keyPrefix = `${key}:`;
1565
+ let searchIndex = 0;
1566
+ while (searchIndex < code.length) {
1567
+ const keyIndex = code.indexOf(keyPrefix, searchIndex);
1568
+ if (keyIndex === -1) return;
1569
+ const result = readFrontmatterStringAtKey(code, keyIndex, keyPrefix);
1570
+ if (result.found) return result.value;
1571
+ searchIndex = result.nextIndex;
1572
+ }
1573
+ }
1574
+ function readFrontmatterStringAtKey(code, keyIndex, keyPrefix) {
1575
+ if (!hasFrontmatterKeyBoundary(code, keyIndex)) return {
1576
+ found: false,
1577
+ nextIndex: keyIndex + keyPrefix.length
1578
+ };
1579
+ const valueIndex = skipWhitespace(code, keyIndex + keyPrefix.length);
1580
+ if (code[valueIndex] !== "\"") return {
1581
+ found: false,
1582
+ nextIndex: valueIndex + 1
1583
+ };
1584
+ return {
1585
+ found: true,
1586
+ value: parseQuotedFrontmatterString(code, valueIndex)
1587
+ };
1588
+ }
1589
+ function hasFrontmatterKeyBoundary(code, keyIndex) {
1590
+ if (keyIndex === 0) return true;
1591
+ const previous = code[keyIndex - 1];
1592
+ return previous === "{" || previous === "," || isWhitespace(previous);
1593
+ }
1594
+ function isWhitespace(value) {
1595
+ return value === " " || value === "\n" || value === "\r" || value === " ";
1596
+ }
1597
+ function skipWhitespace(code, startIndex) {
1598
+ let index = startIndex;
1599
+ while (isWhitespace(code[index])) index += 1;
1600
+ return index;
1601
+ }
1602
+ function parseQuotedFrontmatterString(code, quoteIndex) {
1603
+ let index = quoteIndex + 1;
1604
+ let escaped = false;
1605
+ while (index < code.length) {
1606
+ const char = code[index];
1607
+ if (char === "\"" && !escaped) return parseJsonString(code.slice(quoteIndex, index + 1));
1608
+ escaped = char === "\\" && !escaped;
1609
+ if (char !== "\\") escaped = false;
1610
+ index += 1;
1611
+ }
1612
+ }
1613
+ function parseJsonString(value) {
1614
+ try {
1615
+ const parsedValue = JSON.parse(value);
1616
+ return typeof parsedValue === "string" ? parsedValue : void 0;
1617
+ } catch {
1618
+ return;
1619
+ }
1620
+ }
1621
+ function getRoutePathFromId(id, routesDir, base) {
1622
+ const withoutExtension = id.slice(routesDir.length).replaceAll("\\", "/").replace(/^\//u, "").replace(/\.(?:md|mdx)$/u, "");
1623
+ if (withoutExtension === "index" || withoutExtension === "home") return joinBasePath(base, "/");
1624
+ if (withoutExtension.endsWith("/index") || withoutExtension.endsWith("/home")) return joinBasePath(base, `/${withoutExtension.slice(0, withoutExtension.lastIndexOf("/"))}`);
1625
+ return joinBasePath(base, `/${withoutExtension}`);
1626
+ }
1627
+ function joinBasePath(base, routePath) {
1628
+ const trimmedBase = base.trim();
1629
+ if (trimmedBase === "" || trimmedBase === "/") return routePath;
1630
+ return `${trimTrailingSlashes(trimmedBase.startsWith("/") ? trimmedBase : `/${trimmedBase}`)}${routePath.startsWith("/") ? routePath : `/${routePath}`}`;
1631
+ }
1632
+ function toAbsoluteUrl(value, siteUrl) {
1633
+ const trimmedValue = value?.trim();
1634
+ if (trimmedValue == null || trimmedValue === "") return;
1635
+ if (trimmedValue.startsWith("http://") || trimmedValue.startsWith("https://") || trimmedValue.startsWith("//")) return trimmedValue;
1636
+ const normalizedSiteUrl = trimTrailingSlashes(siteUrl.trim());
1637
+ if (normalizedSiteUrl === "") return;
1638
+ return `${normalizedSiteUrl}${trimmedValue.startsWith("/") ? trimmedValue : `/${trimmedValue}`}`;
1639
+ }
1640
+ function trimTrailingSlashes(value) {
1641
+ let end = value.length;
1642
+ while (end > 0 && value[end - 1] === "/") end -= 1;
1643
+ return value.slice(0, end);
1644
+ }
1645
+ function defaultTwitterCard(image) {
1646
+ return image == null ? "summary" : "summary_large_image";
1274
1647
  }
1275
1648
  //#endregion
1276
1649
  //#region src/markdown/remark-callouts.ts
@@ -2629,7 +3002,7 @@ function isShikiThemeObject(themeConfig) {
2629
3002
  function readProjectMeta(root) {
2630
3003
  const packageJsonPath = path.join(root, "package.json");
2631
3004
  try {
2632
- const rawPackageJson = fsSync.readFileSync(packageJsonPath, "utf8");
3005
+ const rawPackageJson = fs$1.readFileSync(packageJsonPath, "utf8");
2633
3006
  const parsedPackageJson = JSON.parse(rawPackageJson);
2634
3007
  if (!isPackageJsonShape(parsedPackageJson)) return {};
2635
3008
  const repository = extractRepository(parsedPackageJson.repository);
@@ -2663,6 +3036,122 @@ function isPackageJsonShape(value) {
2663
3036
  return typeof value === "object" && value != null;
2664
3037
  }
2665
3038
  //#endregion
3039
+ //#region src/vite/route-manifest.ts
3040
+ async function scanRouteManifest(routesDir) {
3041
+ const entries = [];
3042
+ await scanRouteDirectory(routesDir, routesDir, entries);
3043
+ return entries.sort((left, right) => left.path.localeCompare(right.path));
3044
+ }
3045
+ async function scanRouteDirectory(dir, routesDir, manifestEntries) {
3046
+ let entries;
3047
+ try {
3048
+ entries = await fs.readdir(dir, { withFileTypes: true });
3049
+ } catch {
3050
+ return;
3051
+ }
3052
+ for (const entry of entries) {
3053
+ const fullPath = path.join(dir, entry.name);
3054
+ if (entry.isDirectory()) {
3055
+ await scanRouteDirectory(fullPath, routesDir, manifestEntries);
3056
+ continue;
3057
+ }
3058
+ const manifestEntry = await createManifestEntry(fullPath, routesDir);
3059
+ if (manifestEntry != null) manifestEntries.push(manifestEntry);
3060
+ }
3061
+ }
3062
+ async function createManifestEntry(filePath, routesDir) {
3063
+ const extension = getRouteExtension$1(filePath);
3064
+ if (extension == null || isIgnoredRouteFile$1(path.basename(filePath))) return null;
3065
+ const content = await fs.readFile(filePath, "utf8");
3066
+ const parsed = extension === ".tsx" ? {
3067
+ content,
3068
+ data: {}
3069
+ } : matter(content);
3070
+ const data = toRecord(parsed.data);
3071
+ const stat = await fs.stat(filePath);
3072
+ const relativePath = path.relative(routesDir, filePath);
3073
+ return {
3074
+ anchors: extractAnchors(parsed.content),
3075
+ content: parsed.content,
3076
+ filePath,
3077
+ frontmatter: {
3078
+ redirectFrom: parseRedirectFrom(data.redirectFrom),
3079
+ sitemap: typeof data.sitemap === "boolean" ? data.sitemap : void 0
3080
+ },
3081
+ lastmod: stat.mtime,
3082
+ path: toRoutePath$1(relativePath, extension)
3083
+ };
3084
+ }
3085
+ function getRouteExtension$1(filePath) {
3086
+ if (filePath.endsWith(".mdx")) return ".mdx";
3087
+ if (filePath.endsWith(".md")) return ".md";
3088
+ if (filePath.endsWith(".tsx")) return ".tsx";
3089
+ return null;
3090
+ }
3091
+ function isIgnoredRouteFile$1(entryName) {
3092
+ return entryName === "root.tsx" || entryName.startsWith("_");
3093
+ }
3094
+ function toRoutePath$1(relativePath, extension) {
3095
+ const withoutExtension = relativePath.replaceAll("\\", "/").slice(0, -extension.length);
3096
+ const segments = withoutExtension.split("/");
3097
+ const lastSegment = segments.at(-1);
3098
+ if (lastSegment === "index" || lastSegment === "home") {
3099
+ const parentSegments = segments.slice(0, -1);
3100
+ return parentSegments.length === 0 ? "/" : `/${parentSegments.join("/")}`;
3101
+ }
3102
+ return `/${withoutExtension}`.replaceAll(/\$(\w+)/gu, ":$1");
3103
+ }
3104
+ function parseRedirectFrom(value) {
3105
+ if (typeof value === "string") return [value];
3106
+ if (Array.isArray(value)) {
3107
+ const redirects = value.filter((entry) => typeof entry === "string");
3108
+ return redirects.length === 0 ? void 0 : redirects;
3109
+ }
3110
+ }
3111
+ function extractAnchors(content) {
3112
+ const anchors = /* @__PURE__ */ new Set();
3113
+ for (const line of content.split("\n")) {
3114
+ const headingText = getMarkdownHeadingText(line);
3115
+ if (headingText != null) anchors.add(slugifyHeading(headingText));
3116
+ }
3117
+ return [...anchors];
3118
+ }
3119
+ function getMarkdownHeadingText(line) {
3120
+ const trimmed = line.trimStart();
3121
+ let level = 0;
3122
+ for (const character of trimmed) {
3123
+ if (character !== "#") break;
3124
+ level++;
3125
+ }
3126
+ if (level === 0 || level > 6 || trimmed[level] !== " ") return null;
3127
+ return trimmed.slice(level + 1);
3128
+ }
3129
+ function slugifyHeading(value) {
3130
+ return stripHtmlTags(value).replaceAll(/[`*_~[\]()]/gu, "").trim().toLowerCase().replaceAll(/[^\d\p{Letter}\s-]/gu, "").replaceAll(/\s+/gu, "-");
3131
+ }
3132
+ function stripHtmlTags(value) {
3133
+ let result = "";
3134
+ let isInsideTag = false;
3135
+ for (const character of value) {
3136
+ if (character === "<") {
3137
+ isInsideTag = true;
3138
+ continue;
3139
+ }
3140
+ if (character === ">") {
3141
+ isInsideTag = false;
3142
+ continue;
3143
+ }
3144
+ if (!isInsideTag) result += character;
3145
+ }
3146
+ return result;
3147
+ }
3148
+ function toRecord(value) {
3149
+ if (typeof value !== "object" || value == null || Array.isArray(value)) return {};
3150
+ const record = {};
3151
+ for (const [key, entry] of Object.entries(value)) record[key] = entry;
3152
+ return record;
3153
+ }
3154
+ //#endregion
2666
3155
  //#region src/vite/routes-core.ts
2667
3156
  function scanRoutesSync(params) {
2668
3157
  const { dir, rootDir } = params;
@@ -2688,7 +3177,7 @@ function scanRoutesSync(params) {
2688
3177
  }
2689
3178
  function readDirectoryEntries(dir) {
2690
3179
  try {
2691
- return fsSync.readdirSync(dir, { withFileTypes: true });
3180
+ return fs$1.readdirSync(dir, { withFileTypes: true });
2692
3181
  } catch {
2693
3182
  return [];
2694
3183
  }
@@ -2769,13 +3258,13 @@ function writeRoutesFileSync(params) {
2769
3258
  if (routes.length === 0) return;
2770
3259
  const content = generateRoutesFile(routes);
2771
3260
  if (!hasRoutesContentChangedSync(routesFilePath, content)) return;
2772
- fsSync.mkdirSync(appDir, { recursive: true });
2773
- fsSync.writeFileSync(routesFilePath, content, "utf8");
3261
+ fs$1.mkdirSync(appDir, { recursive: true });
3262
+ fs$1.writeFileSync(routesFilePath, content, "utf8");
2774
3263
  console.log(`[ardo] Generated routes.ts with ${routes.length} routes`);
2775
3264
  }
2776
3265
  function hasRoutesContentChangedSync(routesFilePath, nextContent) {
2777
3266
  try {
2778
- return fsSync.readFileSync(routesFilePath, "utf8") !== nextContent;
3267
+ return fs$1.readFileSync(routesFilePath, "utf8") !== nextContent;
2779
3268
  } catch {
2780
3269
  return true;
2781
3270
  }
@@ -3225,6 +3714,7 @@ function createMainPlugin(state, options) {
3225
3714
  });
3226
3715
  },
3227
3716
  configResolved(config) {
3717
+ state.isSsrBuild = config.build.ssr !== false;
3228
3718
  state.routesDir = resolveRoutesDir(config.root, options.routesDirOption);
3229
3719
  state.resolvedConfig = resolveArdoConfig(config.root, state.routesDir, options.pressConfig);
3230
3720
  },
@@ -3236,9 +3726,29 @@ function createMainPlugin(state, options) {
3236
3726
  },
3237
3727
  transform(code, id) {
3238
3728
  return transformMarkdownMeta(code, id, state);
3729
+ },
3730
+ async generateBundle(outputOptions) {
3731
+ if (state.isSsrBuild || isServerOutput(outputOptions.dir) || state.resolvedConfig == null) return;
3732
+ const manifest = await scanRouteManifest(state.routesDir);
3733
+ reportLinkDiagnostics(this, manifest, state.resolvedConfig);
3734
+ for (const asset of createBuildOutputAssets(manifest, state.resolvedConfig)) this.emitFile({
3735
+ type: "asset",
3736
+ fileName: asset.fileName,
3737
+ source: asset.source
3738
+ });
3239
3739
  }
3240
3740
  };
3241
3741
  }
3742
+ function isServerOutput(outputDir) {
3743
+ return outputDir?.replaceAll("\\", "/").endsWith("/server") === true;
3744
+ }
3745
+ function reportLinkDiagnostics(context, manifest, config) {
3746
+ const diagnostics = checkInternalLinks(manifest, config);
3747
+ if (diagnostics.length === 0) return;
3748
+ const message = `[ardo] Broken internal links found:\n${formatLinkCheckDiagnostics(diagnostics)}`;
3749
+ if (config.linkCheck.level === "error") context.error(message);
3750
+ else context.warn(message);
3751
+ }
3242
3752
  function createMainConfig(state, input) {
3243
3753
  const { command, githubPages, routesDirOption, userConfig } = input;
3244
3754
  const root = userConfig.root ?? process.cwd();