dommief 0.1.0 → 0.1.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 (46) hide show
  1. package/README.md +120 -0
  2. package/dist/cli.js +168 -143
  3. package/dist/index.js +5 -1
  4. package/package.json +1 -72
  5. package/src/build.ts +290 -0
  6. package/src/cache.ts +172 -0
  7. package/src/cli.ts +87 -0
  8. package/src/config.ts +258 -0
  9. package/src/exclude.ts +38 -0
  10. package/src/index.ts +17 -0
  11. package/src/link-check.ts +101 -0
  12. package/src/log.ts +10 -0
  13. package/src/markdown/errors.ts +28 -0
  14. package/src/markdown/index.ts +96 -0
  15. package/src/markdown/rehype-api.ts +131 -0
  16. package/src/markdown/rehype-code.ts +147 -0
  17. package/src/markdown/rehype-demo.ts +76 -0
  18. package/src/markdown/rehype-desc.ts +30 -0
  19. package/src/markdown/rehype-img.ts +33 -0
  20. package/src/markdown/rehype-link.ts +50 -0
  21. package/src/markdown/rehype-slug.ts +41 -0
  22. package/src/markdown/rehype-text.ts +53 -0
  23. package/src/markdown/remark-container.ts +76 -0
  24. package/src/markdown/remark-meta.ts +73 -0
  25. package/src/markdown/to-module.ts +35 -0
  26. package/src/paths.ts +38 -0
  27. package/src/plugins.ts +131 -0
  28. package/src/prefers-color.ts +28 -0
  29. package/src/preview.ts +127 -0
  30. package/src/routes.ts +232 -0
  31. package/src/search.ts +88 -0
  32. package/src/siteFeatures.ts +123 -0
  33. package/src/siteModule.ts +47 -0
  34. package/src/sitemap.ts +59 -0
  35. package/src/ssr-guard.ts +72 -0
  36. package/src/theme/index.ts +5 -0
  37. package/src/theme/loader.ts +151 -0
  38. package/src/theme/resolver.ts +115 -0
  39. package/src/theme/types.ts +25 -0
  40. package/src/theme-modules.d.ts +52 -0
  41. package/src/utils/path.ts +8 -0
  42. package/src/vite/plugin.ts +182 -0
  43. package/src/vite/routesPlugin.ts +39 -0
  44. package/src/vite/searchIndexPlugin.ts +66 -0
  45. package/src/vite/sitePlugin.ts +97 -0
  46. package/src/vite/state.ts +27 -0
package/dist/cli.js CHANGED
@@ -547,7 +547,7 @@ function buildApiTable(src, filePath = "") {
547
547
  try {
548
548
  docs = parser.parse(abs) ?? [];
549
549
  } catch {
550
- docs = [];
550
+ return null;
551
551
  }
552
552
  const entries = (docs || []).map((d) => ({
553
553
  displayName: d.displayName,
@@ -569,6 +569,7 @@ function rehypeApi() {
569
569
  return (tree, file) => {
570
570
  const apis = Array.isArray(file?.data?.apis) ? file.data.apis : [];
571
571
  const watched = Array.isArray(file?.data?.apiSrcFiles) ? file.data.apiSrcFiles : [];
572
+ const warnings = Array.isArray(file?.data?.warnings) ? file.data.warnings : [];
572
573
  const filePath = String(file?.path ?? "");
573
574
  visit7(tree, "element", (node, index, parent) => {
574
575
  if (!parent || typeof index !== "number") return;
@@ -578,6 +579,7 @@ function rehypeApi() {
578
579
  const abs = resolveApiSrc(src, filePath);
579
580
  const data = buildApiTable(src, filePath);
580
581
  watched.push(abs);
582
+ if (!data?.length) warnings.push(`<API src="${src}">: ${data ? "no component found (syntax error or no exported component)" : "file missing or failed to parse"}: ${abs}`);
581
583
  apis.push({ id: hashFileContent(src + filePath).slice(0, 8), src: abs, data });
582
584
  parent.children[index] = {
583
585
  type: "element",
@@ -592,6 +594,7 @@ function rehypeApi() {
592
594
  if (file?.data) {
593
595
  file.data.apis = apis;
594
596
  file.data.apiSrcFiles = watched;
597
+ file.data.warnings = warnings;
595
598
  }
596
599
  };
597
600
  }
@@ -1010,8 +1013,8 @@ import { cac } from "cac";
1010
1013
  import { createServer } from "vite";
1011
1014
 
1012
1015
  // src/vite/plugin.ts
1013
- import fs9 from "fs";
1014
- import path9 from "path";
1016
+ import fs10 from "fs";
1017
+ import path10 from "path";
1015
1018
 
1016
1019
  // src/config.ts
1017
1020
  init_errors();
@@ -1149,6 +1152,7 @@ function resolveConfig(userConfig, opts) {
1149
1152
  srcDir: c.srcDir,
1150
1153
  docDirs: (c.docDirs ?? [c.srcDir]).map((d) => typeof d === "string" ? { dir: d } : { dir: d.dir }),
1151
1154
  forceKebabCase: c.forceKebabCase,
1155
+ preset: c.preset,
1152
1156
  locales,
1153
1157
  themeConfig: c.themeConfig,
1154
1158
  theme: c.theme,
@@ -1767,15 +1771,89 @@ function createState(cwd, get) {
1767
1771
  // src/vite/plugin.ts
1768
1772
  init_path();
1769
1773
 
1774
+ // src/sitemap.ts
1775
+ import fs6 from "fs";
1776
+ import path6 from "path";
1777
+
1778
+ // src/exclude.ts
1779
+ var cache = /* @__PURE__ */ new Map();
1780
+ function globToRegExp(pattern) {
1781
+ let src = "";
1782
+ for (let i = 0; i < pattern.length; i++) {
1783
+ const c = pattern.charAt(i);
1784
+ if (c === "*" && pattern.charAt(i + 1) === "*") {
1785
+ const slash = pattern.charAt(i + 2) === "/";
1786
+ src += slash ? "(?:.*/)?" : ".*";
1787
+ i += slash ? 2 : 1;
1788
+ } else if (c === "*") {
1789
+ src += "[^/]*";
1790
+ } else if (c === "?") {
1791
+ src += "[^/]";
1792
+ } else {
1793
+ src += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
1794
+ }
1795
+ }
1796
+ return new RegExp(`^${src}$`);
1797
+ }
1798
+ function matcher(pattern) {
1799
+ const hit = cache.get(pattern);
1800
+ if (hit) return hit;
1801
+ const re = globToRegExp(pattern);
1802
+ cache.set(pattern, re);
1803
+ return re;
1804
+ }
1805
+ function matchExclude(path15, patterns) {
1806
+ return patterns.some((p) => matcher(p).test(path15));
1807
+ }
1808
+
1809
+ // src/sitemap.ts
1810
+ function canonicalUrl(hostname, base, routePath) {
1811
+ const host = hostname.replace(/\/$/, "");
1812
+ const b = base.replace(/\/$/, "");
1813
+ const p = routePath === "/" ? "/" : routePath;
1814
+ return host + (b + p).replace(/\/{2,}/g, "/").replace(/^(https?:\/)([^/])/, "$1/$2");
1815
+ }
1816
+ function buildSitemap(routes, config, base) {
1817
+ const urls = routes.filter((r) => r.path !== "/404" && !/[:*]/.test(r.path) && !matchExclude(r.path, config.exclude)).map((r) => ` <url><loc>${escapeXml(canonicalUrl(config.hostname, base, r.path))}</loc></url>`).join("\n");
1818
+ return `<?xml version="1.0" encoding="UTF-8"?>
1819
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
1820
+ ${urls}
1821
+ </urlset>
1822
+ `;
1823
+ }
1824
+ function buildRobots(sitemapUrl) {
1825
+ return `User-agent: *
1826
+ Allow: /
1827
+
1828
+ Sitemap: ${sitemapUrl}
1829
+ `;
1830
+ }
1831
+ function escapeXml(s) {
1832
+ return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&apos;" })[c]);
1833
+ }
1834
+ function writeSitemapAndRobots(opts) {
1835
+ if (opts.sitemap === false) return;
1836
+ if (!opts.sitemap.hostname) {
1837
+ opts.log.warn("sitemap.hostname thi\u1EBFu \u2014 b\u1ECF qua sitemap.xml/robots.txt");
1838
+ return;
1839
+ }
1840
+ const xml = buildSitemap(opts.routes, opts.sitemap, opts.base);
1841
+ fs6.writeFileSync(path6.join(opts.outDir, "sitemap.xml"), xml);
1842
+ if (opts.robots) {
1843
+ const sitemapUrl = canonicalUrl(opts.sitemap.hostname, opts.base, "/").replace(/\/$/, "") + "/sitemap.xml";
1844
+ fs6.writeFileSync(path6.join(opts.outDir, "robots.txt"), buildRobots(sitemapUrl));
1845
+ }
1846
+ }
1847
+
1770
1848
  // src/theme/resolver.ts
1771
1849
  init_errors();
1772
- import fs7 from "fs";
1773
- import path7 from "path";
1850
+ import fs8 from "fs";
1851
+ import path8 from "path";
1774
1852
 
1775
1853
  // src/theme/loader.ts
1776
1854
  init_errors();
1777
- import fs6 from "fs";
1778
- import path6 from "path";
1855
+ import fs7 from "fs";
1856
+ import path7 from "path";
1779
1857
  import { fileURLToPath as fileURLToPath2 } from "url";
1780
1858
  var EXTS = [".tsx", ".ts", ".jsx", ".js"];
1781
1859
  var DEFAULT_THEME = "dommief-theme-default";
@@ -1783,17 +1861,17 @@ function isComponentName(name) {
1783
1861
  return /^[A-Z0-9]/.test(name);
1784
1862
  }
1785
1863
  function repoPackagesDir() {
1786
- let cur = path6.dirname(fileURLToPath2(import.meta.url));
1787
- while (cur !== path6.parse(cur).root) {
1788
- if (fs6.existsSync(path6.join(cur, "pnpm-workspace.yaml"))) return path6.join(cur, "packages");
1789
- cur = path6.dirname(cur);
1864
+ let cur = path7.dirname(fileURLToPath2(import.meta.url));
1865
+ while (cur !== path7.parse(cur).root) {
1866
+ if (fs7.existsSync(path7.join(cur, "pnpm-workspace.yaml"))) return path7.join(cur, "packages");
1867
+ cur = path7.dirname(cur);
1790
1868
  }
1791
1869
  return null;
1792
1870
  }
1793
1871
  function assertInside(cwd, target, theme) {
1794
- const rel = path6.relative(cwd, target);
1795
- const insideCwd = rel === "" || !rel.startsWith("..") && !path6.isAbsolute(rel);
1796
- const insideNodeModules = target.split(path6.sep).includes("node_modules");
1872
+ const rel = path7.relative(cwd, target);
1873
+ const insideCwd = rel === "" || !rel.startsWith("..") && !path7.isAbsolute(rel);
1874
+ const insideNodeModules = target.split(path7.sep).includes("node_modules");
1797
1875
  if (!insideCwd && !insideNodeModules) {
1798
1876
  throw new DommiefError(`\u0111\u01B0\u1EDDng d\u1EABn theme "${theme}" tho\xE1t ra ngo\xE0i th\u01B0 m\u1EE5c d\u1EF1 \xE1n: ${target}`, { code: "theme_path_escape", file: target });
1799
1877
  }
@@ -1802,44 +1880,44 @@ function resolveThemeRoot(cwd, theme) {
1802
1880
  const isPathLike = theme.startsWith(".") || theme.startsWith("/");
1803
1881
  let root = null;
1804
1882
  if (isPathLike) {
1805
- const candidate = path6.resolve(cwd, theme);
1806
- if (fs6.existsSync(candidate) && fs6.statSync(candidate).isDirectory()) root = candidate;
1883
+ const candidate = path7.resolve(cwd, theme);
1884
+ if (fs7.existsSync(candidate) && fs7.statSync(candidate).isDirectory()) root = candidate;
1807
1885
  if (!root) throw new DommiefError(`kh\xF4ng t\xECm th\u1EA5y theme t\u1EA1i \u0111\u01B0\u1EDDng d\u1EABn "${theme}"`, { code: "theme_package_not_found" });
1808
1886
  assertInside(cwd, root, theme);
1809
1887
  } else {
1810
- const nmCandidate = path6.resolve(cwd, "node_modules", theme);
1811
- if (fs6.existsSync(nmCandidate)) root = nmCandidate;
1888
+ const nmCandidate = path7.resolve(cwd, "node_modules", theme);
1889
+ if (fs7.existsSync(nmCandidate)) root = nmCandidate;
1812
1890
  else {
1813
1891
  const packages = repoPackagesDir();
1814
1892
  const pkgDirName = theme === DEFAULT_THEME ? "theme-default" : theme;
1815
- const srcCandidate = packages ? path6.join(packages, pkgDirName) : null;
1816
- if (srcCandidate && fs6.existsSync(srcCandidate)) root = srcCandidate;
1893
+ const srcCandidate = packages ? path7.join(packages, pkgDirName) : null;
1894
+ if (srcCandidate && fs7.existsSync(srcCandidate)) root = srcCandidate;
1817
1895
  }
1818
1896
  if (!root) {
1819
1897
  throw new DommiefError(`kh\xF4ng t\xECm th\u1EA5y theme package "${theme}" (\u0111\xE3 th\u1EED node_modules/${theme})`, { code: "theme_package_not_found" });
1820
1898
  }
1821
1899
  }
1822
- if (fs6.existsSync(path6.join(root, "src", "layouts"))) root = path6.join(root, "src");
1900
+ if (fs7.existsSync(path7.join(root, "src", "layouts"))) root = path7.join(root, "src");
1823
1901
  return root;
1824
1902
  }
1825
1903
  function pickEntry(dir, name) {
1826
1904
  for (const ext of EXTS) {
1827
- const f = path6.join(dir, name + ext);
1828
- if (fs6.existsSync(f) && fs6.statSync(f).isFile()) return f;
1905
+ const f = path7.join(dir, name + ext);
1906
+ if (fs7.existsSync(f) && fs7.statSync(f).isFile()) return f;
1829
1907
  }
1830
- const subIndexDir = path6.join(dir, name);
1831
- if (fs6.existsSync(subIndexDir) && fs6.statSync(subIndexDir).isDirectory()) {
1908
+ const subIndexDir = path7.join(dir, name);
1909
+ if (fs7.existsSync(subIndexDir) && fs7.statSync(subIndexDir).isDirectory()) {
1832
1910
  for (const ext of EXTS) {
1833
- const f = path6.join(subIndexDir, "index" + ext);
1834
- if (fs6.existsSync(f) && fs6.statSync(f).isFile()) return f;
1911
+ const f = path7.join(subIndexDir, "index" + ext);
1912
+ if (fs7.existsSync(f) && fs7.statSync(f).isFile()) return f;
1835
1913
  }
1836
1914
  }
1837
1915
  return null;
1838
1916
  }
1839
1917
  function scanComponents(dir) {
1840
1918
  const out = {};
1841
- if (!fs6.existsSync(dir)) return out;
1842
- for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
1919
+ if (!fs7.existsSync(dir)) return out;
1920
+ for (const entry of fs7.readdirSync(dir, { withFileTypes: true })) {
1843
1921
  const name = entry.name;
1844
1922
  if (!isComponentName(name)) continue;
1845
1923
  if (entry.isFile()) {
@@ -1847,26 +1925,26 @@ function scanComponents(dir) {
1847
1925
  if (!ext) continue;
1848
1926
  const specifier = name.slice(0, -ext.length);
1849
1927
  if (!isComponentName(specifier)) continue;
1850
- out[specifier] ??= path6.resolve(dir, name);
1928
+ out[specifier] ??= path7.resolve(dir, name);
1851
1929
  } else if (entry.isDirectory()) {
1852
1930
  const found = pickEntry(dir, name);
1853
- if (found) out[name] = path6.resolve(found);
1931
+ if (found) out[name] = path7.resolve(found);
1854
1932
  }
1855
1933
  }
1856
1934
  return out;
1857
1935
  }
1858
1936
  function scanLocales(dir) {
1859
1937
  const out = {};
1860
- if (!fs6.existsSync(dir)) return out;
1861
- for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
1938
+ if (!fs7.existsSync(dir)) return out;
1939
+ for (const entry of fs7.readdirSync(dir, { withFileTypes: true })) {
1862
1940
  if (entry.isFile() && entry.name.endsWith(".json")) {
1863
- out[entry.name.slice(0, -".json".length)] = path6.resolve(dir, entry.name);
1941
+ out[entry.name.slice(0, -".json".length)] = path7.resolve(dir, entry.name);
1864
1942
  }
1865
1943
  }
1866
1944
  return out;
1867
1945
  }
1868
1946
  function loadThemeManifest(root, name) {
1869
- const layoutsDir = path6.join(root, "layouts");
1947
+ const layoutsDir = path7.join(root, "layouts");
1870
1948
  const layouts = {};
1871
1949
  const missing = [];
1872
1950
  for (const layoutName of ["GlobalLayout", "DocLayout", "DemoLayout"]) {
@@ -1877,19 +1955,19 @@ function loadThemeManifest(root, name) {
1877
1955
  if (missing.length) {
1878
1956
  throw new DommiefError(`theme "${name}" thi\u1EBFu layout: ${missing.join(", ")}`, { code: "theme_layout_missing", file: layoutsDir, layout: missing.join(",") });
1879
1957
  }
1880
- const pluginJs = ["plugin.ts", "plugin.js", path6.join("plugin", "index.ts"), path6.join("plugin", "index.js")].map((f) => path6.join(root, f)).find((f) => fs6.existsSync(f));
1958
+ const pluginJs = ["plugin.ts", "plugin.js", path7.join("plugin", "index.ts"), path7.join("plugin", "index.js")].map((f) => path7.join(root, f)).find((f) => fs7.existsSync(f));
1881
1959
  return {
1882
1960
  name,
1883
1961
  root,
1884
- builtins: scanComponents(path6.join(root, "builtins")),
1885
- slots: scanComponents(path6.join(root, "slots")),
1962
+ builtins: scanComponents(path7.join(root, "builtins")),
1963
+ slots: scanComponents(path7.join(root, "slots")),
1886
1964
  layouts,
1887
- locales: scanLocales(path6.join(root, "locales")),
1965
+ locales: scanLocales(path7.join(root, "locales")),
1888
1966
  plugin: pluginJs ?? null
1889
1967
  };
1890
1968
  }
1891
1969
  function loadTheme(opts) {
1892
- const cwd = path6.resolve(opts.cwd);
1970
+ const cwd = path7.resolve(opts.cwd);
1893
1971
  const theme = opts.theme ?? DEFAULT_THEME;
1894
1972
  const root = resolveThemeRoot(cwd, theme);
1895
1973
  return loadThemeManifest(root, theme);
@@ -1897,9 +1975,9 @@ function loadTheme(opts) {
1897
1975
 
1898
1976
  // src/theme/resolver.ts
1899
1977
  function tryLoadLocal(cwd) {
1900
- const localRoot = path7.resolve(cwd, ".dommief", "theme");
1901
- if (!fs7.existsSync(localRoot)) return null;
1902
- const layoutsDir = path7.join(localRoot, "layouts");
1978
+ const localRoot = path8.resolve(cwd, ".dommief", "theme");
1979
+ if (!fs8.existsSync(localRoot)) return null;
1980
+ const layoutsDir = path8.join(localRoot, "layouts");
1903
1981
  const layouts = {};
1904
1982
  for (const name of ["GlobalLayout", "DocLayout", "DemoLayout"]) {
1905
1983
  const found = scanComponents(layoutsDir)[name];
@@ -1908,8 +1986,8 @@ function tryLoadLocal(cwd) {
1908
1986
  return {
1909
1987
  name: ".dommief/theme",
1910
1988
  root: localRoot,
1911
- builtins: scanComponents(path7.join(localRoot, "builtins")),
1912
- slots: scanComponents(path7.join(localRoot, "slots")),
1989
+ builtins: scanComponents(path8.join(localRoot, "builtins")),
1990
+ slots: scanComponents(path8.join(localRoot, "slots")),
1913
1991
  layouts,
1914
1992
  locales: {},
1915
1993
  plugin: null
@@ -1955,8 +2033,8 @@ function buildAliasChain(cwd, config) {
1955
2033
  // src/cache.ts
1956
2034
  init_rehype_code();
1957
2035
  import { createHash as createHash5 } from "crypto";
1958
- import fs8 from "fs";
1959
- import path8 from "path";
2036
+ import fs9 from "fs";
2037
+ import path9 from "path";
1960
2038
  var CACHE_VERSION = 3;
1961
2039
  function stableStringify(value) {
1962
2040
  if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
@@ -1983,7 +2061,7 @@ var TransformCache = class {
1983
2061
  this.enabled = enabled;
1984
2062
  if (!enabled) return;
1985
2063
  try {
1986
- this.manifest = JSON.parse(fs8.readFileSync(path8.join(dir, "manifest.json"), "utf8"));
2064
+ this.manifest = JSON.parse(fs9.readFileSync(path9.join(dir, "manifest.json"), "utf8"));
1987
2065
  } catch {
1988
2066
  this.manifest = {};
1989
2067
  }
@@ -1996,12 +2074,12 @@ var TransformCache = class {
1996
2074
  manifest = {};
1997
2075
  nextManifest = {};
1998
2076
  fileOf(key) {
1999
- return path8.join(this.dir, key.slice(0, 2), `${key}.json`);
2077
+ return path9.join(this.dir, key.slice(0, 2), `${key}.json`);
2000
2078
  }
2001
2079
  read(key) {
2002
2080
  if (!this.enabled) return null;
2003
2081
  try {
2004
- const v = JSON.parse(fs8.readFileSync(this.fileOf(key), "utf8"));
2082
+ const v = JSON.parse(fs9.readFileSync(this.fileOf(key), "utf8"));
2005
2083
  return typeof v?.code === "string" && v.meta ? v : null;
2006
2084
  } catch {
2007
2085
  return null;
@@ -2011,19 +2089,19 @@ var TransformCache = class {
2011
2089
  write(key, payload) {
2012
2090
  if (!this.enabled) return;
2013
2091
  const file = this.fileOf(key);
2014
- fs8.mkdirSync(path8.dirname(file), { recursive: true });
2092
+ fs9.mkdirSync(path9.dirname(file), { recursive: true });
2015
2093
  const tmp = `${file}.${process.pid}.tmp`;
2016
- fs8.writeFileSync(tmp, JSON.stringify(payload));
2017
- fs8.renameSync(tmp, file);
2094
+ fs9.writeFileSync(tmp, JSON.stringify(payload));
2095
+ fs9.renameSync(tmp, file);
2018
2096
  }
2019
2097
  /** Xoá file cache không còn dùng (trang bị xoá/đổi tên/đổi nội dung). Best-effort, không throw. */
2020
2098
  gc(keepKeys) {
2021
2099
  if (!this.enabled) return;
2022
2100
  try {
2023
- for (const shard of fs8.readdirSync(this.dir, { withFileTypes: true })) {
2101
+ for (const shard of fs9.readdirSync(this.dir, { withFileTypes: true })) {
2024
2102
  if (!shard.isDirectory()) continue;
2025
- for (const f of fs8.readdirSync(path8.join(this.dir, shard.name))) {
2026
- if (!keepKeys.has(f.replace(/\.json$/, "")) || f.endsWith(".tmp")) fs8.rmSync(path8.join(this.dir, shard.name, f), { force: true });
2103
+ for (const f of fs9.readdirSync(path9.join(this.dir, shard.name))) {
2104
+ if (!keepKeys.has(f.replace(/\.json$/, "")) || f.endsWith(".tmp")) fs9.rmSync(path9.join(this.dir, shard.name, f), { force: true });
2027
2105
  }
2028
2106
  }
2029
2107
  } catch {
@@ -2073,8 +2151,8 @@ var TransformCache = class {
2073
2151
  if (!this.enabled) return;
2074
2152
  this.gc(this.used);
2075
2153
  try {
2076
- fs8.mkdirSync(this.dir, { recursive: true });
2077
- fs8.writeFileSync(path8.join(this.dir, "manifest.json"), JSON.stringify(this.nextManifest));
2154
+ fs9.mkdirSync(this.dir, { recursive: true });
2155
+ fs9.writeFileSync(path9.join(this.dir, "manifest.json"), JSON.stringify(this.nextManifest));
2078
2156
  } catch {
2079
2157
  }
2080
2158
  }
@@ -2095,6 +2173,7 @@ function resolveConfiguredColor(defaultValue) {
2095
2173
 
2096
2174
  // src/vite/plugin.ts
2097
2175
  var demoRegistry = /* @__PURE__ */ new Map();
2176
+ var demoDirs = /* @__PURE__ */ new Map();
2098
2177
  function dommiefPlugin(opts) {
2099
2178
  let loaded = opts.config ? { config: opts.config, host: opts.host ?? createPluginHost(opts.config.plugins ?? []) } : void 0;
2100
2179
  const state = createState(opts.root, () => {
@@ -2122,12 +2201,16 @@ function dommiefPlugin(opts) {
2122
2201
  const bare = id.replace(/^\//, "");
2123
2202
  if (bare === "virtual:dommief/entry") return "\0virtual:dommief/entry";
2124
2203
  if (bare.startsWith("virtual:dommief-demo/")) return "\0" + bare;
2204
+ if (importer?.startsWith("\0virtual:dommief-demo/") && id.startsWith(".")) {
2205
+ const dir = demoDirs.get(importer.slice("\0virtual:dommief-demo/".length));
2206
+ if (dir) return this.resolve(path10.resolve(dir, id), path10.join(dir, "_demo.tsx"), { skipSelf: true });
2207
+ }
2125
2208
  if (bare === "virtual:dommief/routes" || bare === "virtual:dommief/site") return null;
2126
2209
  if (id.endsWith(".md") || id.endsWith(".md?meta")) {
2127
- const base = importer ? path9.dirname(importer.replace(/\?meta$/, "")) : process.cwd();
2210
+ const base = importer ? path10.dirname(importer.replace(/\?meta$/, "")) : process.cwd();
2128
2211
  const q = id.endsWith("?meta") ? "?meta" : "";
2129
2212
  const clean = id.replace(/\?meta$/, "");
2130
- return winPath(path9.isAbsolute(clean) ? clean : path9.resolve(base, clean)) + q;
2213
+ return winPath(path10.isAbsolute(clean) ? clean : path10.resolve(base, clean)) + q;
2131
2214
  }
2132
2215
  return null;
2133
2216
  },
@@ -2143,7 +2226,7 @@ function dommiefPlugin(opts) {
2143
2226
  if (id.endsWith(".md") || id.endsWith(".md?meta")) {
2144
2227
  const isMeta = id.endsWith("?meta");
2145
2228
  const file = isMeta ? id.slice(0, -"?meta".length) : id;
2146
- const code = await fs9.promises.readFile(file, "utf8");
2229
+ const code = await fs10.promises.readFile(file, "utf8");
2147
2230
  const transformOpts = { hooks: state.host(), byFile: state.getTable().byFile, base: state.config().base, code: state.config().markdown };
2148
2231
  let out;
2149
2232
  let meta;
@@ -2168,7 +2251,8 @@ function dommiefPlugin(opts) {
2168
2251
  hitDemos = hit.demos ?? [];
2169
2252
  for (const d of hitDemos) {
2170
2253
  demoRegistry.set(d.id, d.source);
2171
- if (d.src) this.addWatchFile(path9.resolve(path9.dirname(file), d.src));
2254
+ demoDirs.set(d.id, path10.dirname(file));
2255
+ if (d.src) this.addWatchFile(path10.resolve(path10.dirname(file), d.src));
2172
2256
  }
2173
2257
  } else {
2174
2258
  const r = await transformMarkdown(code, file, transformOpts);
@@ -2176,7 +2260,8 @@ function dommiefPlugin(opts) {
2176
2260
  const nonCacheDemos = r.demos ?? [];
2177
2261
  for (const d of nonCacheDemos) {
2178
2262
  demoRegistry.set(d.id, d.source);
2179
- if (d.src) this.addWatchFile(path9.resolve(path9.dirname(file), d.src));
2263
+ demoDirs.set(d.id, path10.dirname(file));
2264
+ if (d.src) this.addWatchFile(path10.resolve(path10.dirname(file), d.src));
2180
2265
  }
2181
2266
  }
2182
2267
  for (const w of warnings) this.warn(w);
@@ -2193,7 +2278,7 @@ function dommiefPlugin(opts) {
2193
2278
  transformIndexHtml: { order: "pre", handler(html) {
2194
2279
  const title = state.config().title;
2195
2280
  let out = html;
2196
- if (!/<title>/.test(out)) out = out.replace("<head>", `<head><title>${title}</title>`);
2281
+ if (!/<title>/.test(out)) out = out.replace("<head>", `<head><title>${escapeXml(title)}</title>`);
2197
2282
  const tags = state.host().collectHtmlHeadTags();
2198
2283
  if (tags.length) out = out.replace("</head>", tags.join("\n") + "</head>");
2199
2284
  const themeConfig = state.config().themeConfig;
@@ -2210,7 +2295,7 @@ function dommiefPlugin(opts) {
2210
2295
  return out;
2211
2296
  } },
2212
2297
  configureServer(server) {
2213
- const localThemeDir = winPath(path9.resolve(opts.root, ".dommief", "theme")) + "/";
2298
+ const localThemeDir = winPath(path10.resolve(opts.root, ".dommief", "theme")) + "/";
2214
2299
  const update = () => {
2215
2300
  const aliases = buildAliasChain(opts.root, loaded.config);
2216
2301
  server.config.resolve.alias = { ...server.config.resolve.alias, ...aliases };
@@ -2251,8 +2336,8 @@ import { build } from "vite";
2251
2336
  init_errors();
2252
2337
 
2253
2338
  // src/link-check.ts
2254
- import fs10 from "fs";
2255
- import path10 from "path";
2339
+ import fs11 from "fs";
2340
+ import path11 from "path";
2256
2341
  var HREF_RE = /<a\b[^>]*\shref="([^"]*)"[^>]*>/g;
2257
2342
  var ASSET_RE = /<(?:img|link)\b[^>]*\s(?:src|href)="([^"]*)"[^>]*>/g;
2258
2343
  var LINE_RE = /\sdata-src-line="(\d+)"/;
@@ -2287,8 +2372,8 @@ function check(refs, opts) {
2287
2372
  const cleanPath = (rawPath ?? "").split("?")[0];
2288
2373
  if (isAsset) {
2289
2374
  const relPath = stripBase(cleanPath, opts.base) || "/";
2290
- const onDisk = path10.join(opts.outDir, decodeURIComponent(relPath));
2291
- if (!fs10.existsSync(onDisk)) issues.push({ code: "link_asset_missing", ref });
2375
+ const onDisk = path11.join(opts.outDir, decodeURIComponent(relPath));
2376
+ if (!fs11.existsSync(onDisk)) issues.push({ code: "link_asset_missing", ref });
2292
2377
  continue;
2293
2378
  }
2294
2379
  const routePath = stripBase(cleanPath, opts.base) || "/";
@@ -2314,78 +2399,6 @@ function formatIssues(issues) {
2314
2399
  return lines.join("\n");
2315
2400
  }
2316
2401
 
2317
- // src/exclude.ts
2318
- var cache = /* @__PURE__ */ new Map();
2319
- function globToRegExp(pattern) {
2320
- let src = "";
2321
- for (let i = 0; i < pattern.length; i++) {
2322
- const c = pattern.charAt(i);
2323
- if (c === "*" && pattern.charAt(i + 1) === "*") {
2324
- const slash = pattern.charAt(i + 2) === "/";
2325
- src += slash ? "(?:.*/)?" : ".*";
2326
- i += slash ? 2 : 1;
2327
- } else if (c === "*") {
2328
- src += "[^/]*";
2329
- } else if (c === "?") {
2330
- src += "[^/]";
2331
- } else {
2332
- src += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
2333
- }
2334
- }
2335
- return new RegExp(`^${src}$`);
2336
- }
2337
- function matcher(pattern) {
2338
- const hit = cache.get(pattern);
2339
- if (hit) return hit;
2340
- const re = globToRegExp(pattern);
2341
- cache.set(pattern, re);
2342
- return re;
2343
- }
2344
- function matchExclude(path15, patterns) {
2345
- return patterns.some((p) => matcher(p).test(path15));
2346
- }
2347
-
2348
- // src/sitemap.ts
2349
- import fs11 from "fs";
2350
- import path11 from "path";
2351
- function canonicalUrl(hostname, base, routePath) {
2352
- const host = hostname.replace(/\/$/, "");
2353
- const b = base.replace(/\/$/, "");
2354
- const p = routePath === "/" ? "/" : routePath;
2355
- return host + (b + p).replace(/\/{2,}/g, "/").replace(/^(https?:\/)([^/])/, "$1/$2");
2356
- }
2357
- function buildSitemap(routes, config, base) {
2358
- const urls = routes.filter((r) => r.path !== "/404" && !/[:*]/.test(r.path) && !matchExclude(r.path, config.exclude)).map((r) => ` <url><loc>${escapeXml(canonicalUrl(config.hostname, base, r.path))}</loc></url>`).join("\n");
2359
- return `<?xml version="1.0" encoding="UTF-8"?>
2360
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
2361
- ${urls}
2362
- </urlset>
2363
- `;
2364
- }
2365
- function buildRobots(sitemapUrl) {
2366
- return `User-agent: *
2367
- Allow: /
2368
-
2369
- Sitemap: ${sitemapUrl}
2370
- `;
2371
- }
2372
- function escapeXml(s) {
2373
- return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&apos;" })[c]);
2374
- }
2375
- function writeSitemapAndRobots(opts) {
2376
- if (opts.sitemap === false) return;
2377
- if (!opts.sitemap.hostname) {
2378
- opts.log.warn("sitemap.hostname thi\u1EBFu \u2014 b\u1ECF qua sitemap.xml/robots.txt");
2379
- return;
2380
- }
2381
- const xml = buildSitemap(opts.routes, opts.sitemap, opts.base);
2382
- fs11.writeFileSync(path11.join(opts.outDir, "sitemap.xml"), xml);
2383
- if (opts.robots) {
2384
- const sitemapUrl = canonicalUrl(opts.sitemap.hostname, opts.base, "/").replace(/\/$/, "") + "/sitemap.xml";
2385
- fs11.writeFileSync(path11.join(opts.outDir, "robots.txt"), buildRobots(sitemapUrl));
2386
- }
2387
- }
2388
-
2389
2402
  // src/build.ts
2390
2403
  var ROOT_PLACEHOLDER = /<div id="root"><\/div>/;
2391
2404
  async function mapLimit(items, limit, fn) {
@@ -2501,7 +2514,12 @@ async function runBuild({ root, config, host, log }) {
2501
2514
  const linkRefs = [];
2502
2515
  await timed("prerender", async () => {
2503
2516
  await mapLimit(plan.prerender, config.ssg.concurrency, async (r) => {
2504
- const html = await render(r.path);
2517
+ let html;
2518
+ try {
2519
+ html = await render(r.path);
2520
+ } catch (e) {
2521
+ throw new DommiefError(`prerender ${r.path}: ${formatError(e)}`, { code: "ssg_render_failed" });
2522
+ }
2505
2523
  if (linkCheckEnabled) linkRefs.push(...collect(r.id, html, fileOf(r.id)));
2506
2524
  writePage(r, wrap(html, r.path));
2507
2525
  });
@@ -2604,7 +2622,14 @@ function spaFallbackPlugin(opts) {
2604
2622
  name: "dommief:preview-spa-fallback",
2605
2623
  configurePreviewServer(s) {
2606
2624
  s.middlewares.use((req, res, next) => {
2607
- const urlPath = decodeURIComponent((req.url ?? "/").split("?")[0] ?? "/");
2625
+ let urlPath;
2626
+ try {
2627
+ urlPath = decodeURIComponent((req.url ?? "/").split("?")[0] ?? "/");
2628
+ } catch {
2629
+ res.statusCode = 400;
2630
+ res.end("Invalid URL");
2631
+ return;
2632
+ }
2608
2633
  if (req.method !== "GET") return next();
2609
2634
  if (base && urlPath !== base && !urlPath.startsWith(base + "/")) {
2610
2635
  res.statusCode = 404;
package/dist/index.js CHANGED
@@ -272,6 +272,7 @@ function resolveConfig(userConfig, opts) {
272
272
  srcDir: c.srcDir,
273
273
  docDirs: (c.docDirs ?? [c.srcDir]).map((d) => typeof d === "string" ? { dir: d } : { dir: d.dir }),
274
274
  forceKebabCase: c.forceKebabCase,
275
+ preset: c.preset,
275
276
  locales,
276
277
  themeConfig: c.themeConfig,
277
278
  theme: c.theme,
@@ -961,7 +962,7 @@ function buildApiTable(src, filePath = "") {
961
962
  try {
962
963
  docs = parser.parse(abs) ?? [];
963
964
  } catch {
964
- docs = [];
965
+ return null;
965
966
  }
966
967
  const entries = (docs || []).map((d) => ({
967
968
  displayName: d.displayName,
@@ -983,6 +984,7 @@ function rehypeApi() {
983
984
  return (tree, file) => {
984
985
  const apis = Array.isArray(file?.data?.apis) ? file.data.apis : [];
985
986
  const watched = Array.isArray(file?.data?.apiSrcFiles) ? file.data.apiSrcFiles : [];
987
+ const warnings = Array.isArray(file?.data?.warnings) ? file.data.warnings : [];
986
988
  const filePath = String(file?.path ?? "");
987
989
  visit7(tree, "element", (node, index, parent) => {
988
990
  if (!parent || typeof index !== "number") return;
@@ -992,6 +994,7 @@ function rehypeApi() {
992
994
  const abs = resolveApiSrc(src, filePath);
993
995
  const data = buildApiTable(src, filePath);
994
996
  watched.push(abs);
997
+ if (!data?.length) warnings.push(`<API src="${src}">: ${data ? "no component found (syntax error or no exported component)" : "file missing or failed to parse"}: ${abs}`);
995
998
  apis.push({ id: hashFileContent(src + filePath).slice(0, 8), src: abs, data });
996
999
  parent.children[index] = {
997
1000
  type: "element",
@@ -1006,6 +1009,7 @@ function rehypeApi() {
1006
1009
  if (file?.data) {
1007
1010
  file.data.apis = apis;
1008
1011
  file.data.apiSrcFiles = watched;
1012
+ file.data.warnings = warnings;
1009
1013
  }
1010
1014
  };
1011
1015
  }