prev-cli 0.14.1 → 0.16.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.
package/dist/cli.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { parseArgs } from "util";
5
- import path7 from "path";
6
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, rmSync as rmSync2, readFileSync as readFileSync4 } from "fs";
5
+ import path9 from "path";
6
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4, rmSync as rmSync3, readFileSync as readFileSync5 } from "fs";
7
7
  import { fileURLToPath as fileURLToPath3 } from "url";
8
8
 
9
9
  // src/vite/start.ts
@@ -15,9 +15,9 @@ import react from "@vitejs/plugin-react-swc";
15
15
  import mdx from "@mdx-js/rollup";
16
16
  import remarkGfm from "remark-gfm";
17
17
  import rehypeHighlight from "rehype-highlight";
18
- import path6 from "path";
18
+ import path7 from "path";
19
19
  import { fileURLToPath as fileURLToPath2 } from "url";
20
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
20
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
21
21
 
22
22
  // src/utils/cache.ts
23
23
  import { createHash } from "crypto";
@@ -74,6 +74,7 @@ async function ensureCacheDir(rootDir) {
74
74
  import fg from "fast-glob";
75
75
  import { readFile } from "fs/promises";
76
76
  import path2 from "path";
77
+ import picomatch from "picomatch";
77
78
  function parseValue(value) {
78
79
  const trimmed = value.trim();
79
80
  if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
@@ -187,6 +188,9 @@ async function scanPages(rootDir, options = {}) {
187
188
  if (Object.keys(frontmatter).length > 0) {
188
189
  page.frontmatter = frontmatter;
189
190
  }
191
+ if (frontmatter.hidden === true) {
192
+ page.hidden = true;
193
+ }
190
194
  pages.push(page);
191
195
  }
192
196
  return pages.sort((a, b) => a.route.localeCompare(b.route));
@@ -378,13 +382,19 @@ async function scanPreviews(rootDir) {
378
382
  if (!existsSync2(previewsDir)) {
379
383
  return [];
380
384
  }
381
- const htmlFiles = await fg2.glob("**/index.html", {
385
+ const entryFiles = await fg2.glob("**/{index.html,App.tsx,App.jsx,index.tsx,index.jsx}", {
382
386
  cwd: previewsDir,
383
387
  ignore: ["node_modules/**"]
384
388
  });
385
- return htmlFiles.map((file) => {
389
+ const previewDirs = new Map;
390
+ for (const file of entryFiles) {
386
391
  const dir = path4.dirname(file);
387
- const name = dir === "." ? path4.basename(path4.dirname(path4.join(previewsDir, file))) : dir;
392
+ if (!previewDirs.has(dir)) {
393
+ previewDirs.set(dir, file);
394
+ }
395
+ }
396
+ return Array.from(previewDirs.entries()).map(([dir, file]) => {
397
+ const name = dir === "." ? path4.basename(previewsDir) : dir;
388
398
  return {
389
399
  name,
390
400
  route: `/_preview/${name}`,
@@ -612,6 +622,99 @@ function previewsPlugin(rootDir) {
612
622
  };
613
623
  }
614
624
 
625
+ // src/vite/plugins/config-plugin.ts
626
+ var VIRTUAL_CONFIG_ID = "virtual:prev-config";
627
+ var RESOLVED_CONFIG_ID = "\x00" + VIRTUAL_CONFIG_ID;
628
+ function createConfigPlugin(config) {
629
+ return {
630
+ name: "prev-config",
631
+ enforce: "pre",
632
+ resolveId(id) {
633
+ if (id === VIRTUAL_CONFIG_ID) {
634
+ return RESOLVED_CONFIG_ID;
635
+ }
636
+ },
637
+ load(id) {
638
+ if (id === RESOLVED_CONFIG_ID) {
639
+ return `export const config = ${JSON.stringify(config)};`;
640
+ }
641
+ }
642
+ };
643
+ }
644
+
645
+ // src/config/schema.ts
646
+ var defaultConfig = {
647
+ theme: "system",
648
+ contentWidth: "constrained",
649
+ hidden: [],
650
+ order: {}
651
+ };
652
+ function validateConfig(raw) {
653
+ const config = { ...defaultConfig };
654
+ if (raw && typeof raw === "object") {
655
+ const obj = raw;
656
+ if (obj.theme === "light" || obj.theme === "dark" || obj.theme === "system") {
657
+ config.theme = obj.theme;
658
+ }
659
+ if (obj.contentWidth === "constrained" || obj.contentWidth === "full") {
660
+ config.contentWidth = obj.contentWidth;
661
+ }
662
+ if (Array.isArray(obj.hidden)) {
663
+ config.hidden = obj.hidden.filter((h) => typeof h === "string");
664
+ }
665
+ if (obj.order && typeof obj.order === "object") {
666
+ config.order = {};
667
+ for (const [key, value] of Object.entries(obj.order)) {
668
+ if (Array.isArray(value)) {
669
+ config.order[key] = value.filter((v) => typeof v === "string");
670
+ }
671
+ }
672
+ }
673
+ }
674
+ return config;
675
+ }
676
+ // src/config/loader.ts
677
+ import { readFileSync as readFileSync3, existsSync as existsSync4, writeFileSync as writeFileSync3 } from "fs";
678
+ import path6 from "path";
679
+ import yaml from "js-yaml";
680
+ function findConfigFile(rootDir) {
681
+ const yamlPath = path6.join(rootDir, ".prev.yaml");
682
+ const ymlPath = path6.join(rootDir, ".prev.yml");
683
+ if (existsSync4(yamlPath))
684
+ return yamlPath;
685
+ if (existsSync4(ymlPath))
686
+ return ymlPath;
687
+ return null;
688
+ }
689
+ function loadConfig(rootDir) {
690
+ const configPath = findConfigFile(rootDir);
691
+ if (!configPath) {
692
+ return defaultConfig;
693
+ }
694
+ try {
695
+ const content = readFileSync3(configPath, "utf-8");
696
+ const raw = yaml.load(content);
697
+ return validateConfig(raw);
698
+ } catch (error) {
699
+ console.warn(`Warning: Failed to parse ${configPath}:`, error);
700
+ return defaultConfig;
701
+ }
702
+ }
703
+ function saveConfig(rootDir, config) {
704
+ const configPath = findConfigFile(rootDir) || path6.join(rootDir, ".prev.yaml");
705
+ const content = yaml.dump(config, {
706
+ indent: 2,
707
+ lineWidth: -1,
708
+ quotingType: '"',
709
+ forceQuotes: false
710
+ });
711
+ writeFileSync3(configPath, content, "utf-8");
712
+ }
713
+ function updateOrder(rootDir, pathKey, order) {
714
+ const config = loadConfig(rootDir);
715
+ config.order[pathKey] = order;
716
+ saveConfig(rootDir, config);
717
+ }
615
718
  // src/vite/config.ts
616
719
  function createFriendlyLogger() {
617
720
  const logger = createLogger("info", { allowClearScreen: false });
@@ -667,35 +770,35 @@ function createFriendlyLogger() {
667
770
  };
668
771
  }
669
772
  function findCliRoot2() {
670
- let dir = path6.dirname(fileURLToPath2(import.meta.url));
773
+ let dir = path7.dirname(fileURLToPath2(import.meta.url));
671
774
  for (let i = 0;i < 10; i++) {
672
- const pkgPath = path6.join(dir, "package.json");
673
- if (existsSync4(pkgPath)) {
775
+ const pkgPath = path7.join(dir, "package.json");
776
+ if (existsSync5(pkgPath)) {
674
777
  try {
675
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
778
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
676
779
  if (pkg.name === "prev-cli") {
677
780
  return dir;
678
781
  }
679
782
  } catch {}
680
783
  }
681
- const parent = path6.dirname(dir);
784
+ const parent = path7.dirname(dir);
682
785
  if (parent === dir)
683
786
  break;
684
787
  dir = parent;
685
788
  }
686
- return path6.dirname(path6.dirname(fileURLToPath2(import.meta.url)));
789
+ return path7.dirname(path7.dirname(fileURLToPath2(import.meta.url)));
687
790
  }
688
791
  function findNodeModules(cliRoot2) {
689
- const localNodeModules = path6.join(cliRoot2, "node_modules");
690
- if (existsSync4(path6.join(localNodeModules, "react"))) {
792
+ const localNodeModules = path7.join(cliRoot2, "node_modules");
793
+ if (existsSync5(path7.join(localNodeModules, "react"))) {
691
794
  return localNodeModules;
692
795
  }
693
796
  let dir = cliRoot2;
694
797
  for (let i = 0;i < 10; i++) {
695
- const parent = path6.dirname(dir);
798
+ const parent = path7.dirname(dir);
696
799
  if (parent === dir)
697
800
  break;
698
- if (path6.basename(parent) === "node_modules" && existsSync4(path6.join(parent, "react"))) {
801
+ if (path7.basename(parent) === "node_modules" && existsSync5(path7.join(parent, "react"))) {
699
802
  return parent;
700
803
  }
701
804
  dir = parent;
@@ -704,10 +807,11 @@ function findNodeModules(cliRoot2) {
704
807
  }
705
808
  var cliRoot2 = findCliRoot2();
706
809
  var cliNodeModules = findNodeModules(cliRoot2);
707
- var srcRoot2 = path6.join(cliRoot2, "src");
810
+ var srcRoot2 = path7.join(cliRoot2, "src");
708
811
  async function createViteConfig(options) {
709
812
  const { rootDir, mode, port, include } = options;
710
813
  const cacheDir = await ensureCacheDir(rootDir);
814
+ const config = loadConfig(rootDir);
711
815
  return {
712
816
  root: rootDir,
713
817
  mode,
@@ -720,16 +824,44 @@ async function createViteConfig(options) {
720
824
  rehypePlugins: [rehypeHighlight]
721
825
  }),
722
826
  react(),
827
+ createConfigPlugin(config),
723
828
  pagesPlugin(rootDir, { include }),
724
829
  entryPlugin(rootDir),
725
830
  previewsPlugin(rootDir),
831
+ {
832
+ name: "prev-config-api",
833
+ configureServer(server) {
834
+ server.middlewares.use("/__prev/config", async (req, res) => {
835
+ if (req.method === "POST") {
836
+ let body = "";
837
+ req.on("data", (chunk) => {
838
+ body += chunk;
839
+ });
840
+ req.on("end", () => {
841
+ try {
842
+ const { path: pathKey, order } = JSON.parse(body);
843
+ updateOrder(rootDir, pathKey, order);
844
+ res.statusCode = 200;
845
+ res.end(JSON.stringify({ success: true }));
846
+ } catch (e) {
847
+ res.statusCode = 400;
848
+ res.end(JSON.stringify({ error: String(e) }));
849
+ }
850
+ });
851
+ return;
852
+ }
853
+ res.statusCode = 405;
854
+ res.end();
855
+ });
856
+ }
857
+ },
726
858
  {
727
859
  name: "prev-preview-server",
728
860
  resolveId(id) {
729
861
  if (id.startsWith("/_preview/")) {
730
862
  const relativePath = id.slice("/_preview/".length);
731
- const previewsDir = path6.join(rootDir, "previews");
732
- const resolved = path6.resolve(previewsDir, relativePath);
863
+ const previewsDir = path7.join(rootDir, "previews");
864
+ const resolved = path7.resolve(previewsDir, relativePath);
733
865
  if (resolved.startsWith(previewsDir)) {
734
866
  return resolved;
735
867
  }
@@ -739,9 +871,9 @@ async function createViteConfig(options) {
739
871
  server.middlewares.use(async (req, res, next) => {
740
872
  const urlPath = req.url?.split("?")[0] || "";
741
873
  if (urlPath === "/_preview-runtime") {
742
- const templatePath = path6.join(srcRoot2, "preview-runtime/template.html");
743
- if (existsSync4(templatePath)) {
744
- const html = readFileSync3(templatePath, "utf-8");
874
+ const templatePath = path7.join(srcRoot2, "preview-runtime/template.html");
875
+ if (existsSync5(templatePath)) {
876
+ const html = readFileSync4(templatePath, "utf-8");
745
877
  res.setHeader("Content-Type", "text/html");
746
878
  res.end(html);
747
879
  return;
@@ -749,18 +881,18 @@ async function createViteConfig(options) {
749
881
  }
750
882
  if (urlPath.startsWith("/_preview-config/")) {
751
883
  const previewName = decodeURIComponent(urlPath.slice("/_preview-config/".length));
752
- const previewsDir = path6.join(rootDir, "previews");
753
- const previewDir = path6.resolve(previewsDir, previewName);
884
+ const previewsDir = path7.join(rootDir, "previews");
885
+ const previewDir = path7.resolve(previewsDir, previewName);
754
886
  if (!previewDir.startsWith(previewsDir)) {
755
887
  res.statusCode = 403;
756
888
  res.end("Forbidden");
757
889
  return;
758
890
  }
759
- if (existsSync4(previewDir)) {
891
+ if (existsSync5(previewDir)) {
760
892
  try {
761
- const config = await buildPreviewConfig(previewDir);
893
+ const config2 = await buildPreviewConfig(previewDir);
762
894
  res.setHeader("Content-Type", "application/json");
763
- res.end(JSON.stringify(config));
895
+ res.end(JSON.stringify(config2));
764
896
  return;
765
897
  } catch (err) {
766
898
  console.error("Error building preview config:", err);
@@ -771,17 +903,17 @@ async function createViteConfig(options) {
771
903
  }
772
904
  }
773
905
  if (urlPath.startsWith("/_preview/")) {
774
- const isHtmlRequest = !path6.extname(urlPath) || urlPath.endsWith("/");
906
+ const isHtmlRequest = !path7.extname(urlPath) || urlPath.endsWith("/");
775
907
  if (isHtmlRequest) {
776
908
  const previewName = decodeURIComponent(urlPath.slice("/_preview/".length).replace(/\/$/, ""));
777
- const previewsDir = path6.join(rootDir, "previews");
778
- const htmlPath = path6.resolve(previewsDir, previewName, "index.html");
909
+ const previewsDir = path7.join(rootDir, "previews");
910
+ const htmlPath = path7.resolve(previewsDir, previewName, "index.html");
779
911
  if (!htmlPath.startsWith(previewsDir)) {
780
912
  return next();
781
913
  }
782
- if (existsSync4(htmlPath)) {
914
+ if (existsSync5(htmlPath)) {
783
915
  try {
784
- let html = readFileSync3(htmlPath, "utf-8");
916
+ let html = readFileSync4(htmlPath, "utf-8");
785
917
  const previewBase = `/_preview/${previewName}/`;
786
918
  html = html.replace(/(src|href)=["']\.\/([^"']+)["']/g, `$1="${previewBase}$2"`);
787
919
  const transformed = await server.transformIndexHtml(req.url, html);
@@ -802,14 +934,14 @@ async function createViteConfig(options) {
802
934
  ],
803
935
  resolve: {
804
936
  alias: {
805
- "@prev/ui": path6.join(srcRoot2, "ui"),
806
- "@prev/theme": path6.join(srcRoot2, "theme"),
807
- react: path6.join(cliNodeModules, "react"),
808
- "react-dom": path6.join(cliNodeModules, "react-dom"),
809
- "@tanstack/react-router": path6.join(cliNodeModules, "@tanstack/react-router"),
810
- mermaid: path6.join(cliNodeModules, "mermaid"),
811
- dayjs: path6.join(cliNodeModules, "dayjs"),
812
- "@terrastruct/d2": path6.join(cliNodeModules, "@terrastruct/d2")
937
+ "@prev/ui": path7.join(srcRoot2, "ui"),
938
+ "@prev/theme": path7.join(srcRoot2, "theme"),
939
+ react: path7.join(cliNodeModules, "react"),
940
+ "react-dom": path7.join(cliNodeModules, "react-dom"),
941
+ "@tanstack/react-router": path7.join(cliNodeModules, "@tanstack/react-router"),
942
+ mermaid: path7.join(cliNodeModules, "mermaid"),
943
+ dayjs: path7.join(cliNodeModules, "dayjs"),
944
+ "@terrastruct/d2": path7.join(cliNodeModules, "@terrastruct/d2")
813
945
  },
814
946
  dedupe: [
815
947
  "react",
@@ -842,8 +974,8 @@ async function createViteConfig(options) {
842
974
  },
843
975
  warmup: {
844
976
  clientFiles: [
845
- path6.join(srcRoot2, "theme/entry.tsx"),
846
- path6.join(srcRoot2, "theme/styles.css")
977
+ path7.join(srcRoot2, "theme/entry.tsx"),
978
+ path7.join(srcRoot2, "theme/styles.css")
847
979
  ]
848
980
  }
849
981
  },
@@ -852,12 +984,12 @@ async function createViteConfig(options) {
852
984
  strictPort: false
853
985
  },
854
986
  build: {
855
- outDir: path6.join(rootDir, "dist"),
987
+ outDir: path7.join(rootDir, "dist"),
856
988
  reportCompressedSize: false,
857
989
  chunkSizeWarningLimit: 1e4,
858
990
  rollupOptions: {
859
991
  input: {
860
- main: path6.join(srcRoot2, "theme/index.html")
992
+ main: path7.join(srcRoot2, "theme/index.html")
861
993
  }
862
994
  }
863
995
  }
@@ -896,6 +1028,9 @@ async function findAvailablePort(minPort, maxPort) {
896
1028
  }
897
1029
 
898
1030
  // src/vite/start.ts
1031
+ import { exec as exec2 } from "child_process";
1032
+ import { existsSync as existsSync6, rmSync as rmSync2 } from "fs";
1033
+ import path8 from "path";
899
1034
  function printWelcome(type) {
900
1035
  console.log();
901
1036
  console.log(" ✨ prev");
@@ -906,12 +1041,69 @@ function printWelcome(type) {
906
1041
  console.log(" Previewing your production build:");
907
1042
  }
908
1043
  }
1044
+ function printShortcuts() {
1045
+ console.log();
1046
+ console.log(" Shortcuts:");
1047
+ console.log(" o → open in browser");
1048
+ console.log(" c → clear cache");
1049
+ console.log(" h → show this help");
1050
+ console.log(" q → quit");
1051
+ console.log();
1052
+ }
909
1053
  function printReady() {
910
1054
  console.log();
911
1055
  console.log(" Edit your .md/.mdx files and see changes instantly.");
912
- console.log(" Press Ctrl+C to stop.");
1056
+ console.log(" Press h for shortcuts.");
913
1057
  console.log();
914
1058
  }
1059
+ function openBrowser(url) {
1060
+ const platform = process.platform;
1061
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
1062
+ exec2(`${cmd} ${url}`);
1063
+ console.log(` ↗ Opened ${url}`);
1064
+ }
1065
+ function clearCache(rootDir) {
1066
+ const viteCacheDir = path8.join(rootDir, ".vite");
1067
+ const nodeModulesVite = path8.join(rootDir, "node_modules", ".vite");
1068
+ let cleared = 0;
1069
+ if (existsSync6(viteCacheDir)) {
1070
+ rmSync2(viteCacheDir, { recursive: true });
1071
+ cleared++;
1072
+ }
1073
+ if (existsSync6(nodeModulesVite)) {
1074
+ rmSync2(nodeModulesVite, { recursive: true });
1075
+ cleared++;
1076
+ }
1077
+ if (cleared === 0) {
1078
+ console.log(" No cache to clear");
1079
+ } else {
1080
+ console.log(` ✓ Cleared Vite cache`);
1081
+ }
1082
+ }
1083
+ function setupKeyboardShortcuts(rootDir, url, quit) {
1084
+ if (!process.stdin.isTTY)
1085
+ return;
1086
+ process.stdin.setRawMode(true);
1087
+ process.stdin.resume();
1088
+ process.stdin.setEncoding("utf8");
1089
+ process.stdin.on("data", (key) => {
1090
+ switch (key.toLowerCase()) {
1091
+ case "o":
1092
+ openBrowser(url);
1093
+ break;
1094
+ case "c":
1095
+ clearCache(rootDir);
1096
+ break;
1097
+ case "h":
1098
+ printShortcuts();
1099
+ break;
1100
+ case "q":
1101
+ case "\x03":
1102
+ quit();
1103
+ break;
1104
+ }
1105
+ });
1106
+ }
915
1107
  async function startDev(rootDir, options = {}) {
916
1108
  const port = options.port ?? await getRandomPort();
917
1109
  const config = await createViteConfig({
@@ -922,9 +1114,17 @@ async function startDev(rootDir, options = {}) {
922
1114
  });
923
1115
  const server = await createServer2(config);
924
1116
  await server.listen();
1117
+ const actualPort = server.config.server.port || port;
1118
+ const url = `http://localhost:${actualPort}/`;
925
1119
  printWelcome("dev");
926
1120
  server.printUrls();
927
1121
  printReady();
1122
+ setupKeyboardShortcuts(rootDir, url, async () => {
1123
+ console.log(`
1124
+ Shutting down...`);
1125
+ await server.close();
1126
+ process.exit(0);
1127
+ });
928
1128
  return server;
929
1129
  }
930
1130
  async function buildSite(rootDir, options = {}) {
@@ -963,15 +1163,15 @@ async function previewSite(rootDir, options = {}) {
963
1163
  // src/cli.ts
964
1164
  function getVersion() {
965
1165
  try {
966
- let dir = path7.dirname(fileURLToPath3(import.meta.url));
1166
+ let dir = path9.dirname(fileURLToPath3(import.meta.url));
967
1167
  for (let i = 0;i < 5; i++) {
968
- const pkgPath = path7.join(dir, "package.json");
969
- if (existsSync5(pkgPath)) {
970
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1168
+ const pkgPath = path9.join(dir, "package.json");
1169
+ if (existsSync7(pkgPath)) {
1170
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
971
1171
  if (pkg.name === "prev-cli")
972
1172
  return pkg.version;
973
1173
  }
974
- dir = path7.dirname(dir);
1174
+ dir = path9.dirname(dir);
975
1175
  }
976
1176
  } catch {}
977
1177
  return "unknown";
@@ -989,7 +1189,7 @@ var { values, positionals } = parseArgs({
989
1189
  allowPositionals: true
990
1190
  });
991
1191
  var command = positionals[0] || "dev";
992
- var rootDir = path7.resolve(values.cwd || positionals[1] || ".");
1192
+ var rootDir = path9.resolve(values.cwd || positionals[1] || ".");
993
1193
  function printHelp() {
994
1194
  console.log(`
995
1195
  prev - Zero-config documentation site generator
@@ -1010,6 +1210,33 @@ Options:
1010
1210
  -h, --help Show this help message
1011
1211
  -v, --version Show version number
1012
1212
 
1213
+ Floating Toolbar:
1214
+ A draggable pill at the bottom of the screen with:
1215
+ - TOC button: Opens navigation panel (dropdown on desktop, overlay on mobile)
1216
+ - Previews button: Links to /previews catalog (if previews exist)
1217
+ - Width toggle: Switch between constrained and full-width content
1218
+ - Theme toggle: Switch between light and dark mode
1219
+
1220
+ Configuration (.prev.yaml):
1221
+ Create a .prev.yaml file in your docs root to customize behavior:
1222
+
1223
+ theme: system # light | dark | system (default: system)
1224
+ contentWidth: constrained # constrained | full (default: constrained)
1225
+ hidden: # Glob patterns for pages to hide
1226
+ - "internal/**"
1227
+ - "wip-*.md"
1228
+ order: # Custom page ordering
1229
+ "/":
1230
+ - "getting-started.md"
1231
+ - "guides/"
1232
+
1233
+ Pages can also be hidden via frontmatter:
1234
+ ---
1235
+ hidden: true
1236
+ ---
1237
+
1238
+ Drag pages in the TOC panel to reorder - changes auto-save to config.
1239
+
1013
1240
  Previews:
1014
1241
  Previews must be in the previews/ directory at your project root.
1015
1242
  Each preview is a subfolder with React components:
@@ -1053,16 +1280,16 @@ Examples:
1053
1280
  `);
1054
1281
  }
1055
1282
  function clearViteCache(rootDir2) {
1056
- const viteCacheDir = path7.join(rootDir2, ".vite");
1057
- const nodeModulesVite = path7.join(rootDir2, "node_modules", ".vite");
1283
+ const viteCacheDir = path9.join(rootDir2, ".vite");
1284
+ const nodeModulesVite = path9.join(rootDir2, "node_modules", ".vite");
1058
1285
  let cleared = 0;
1059
- if (existsSync5(viteCacheDir)) {
1060
- rmSync2(viteCacheDir, { recursive: true });
1286
+ if (existsSync7(viteCacheDir)) {
1287
+ rmSync3(viteCacheDir, { recursive: true });
1061
1288
  cleared++;
1062
1289
  console.log(` ✓ Removed .vite/`);
1063
1290
  }
1064
- if (existsSync5(nodeModulesVite)) {
1065
- rmSync2(nodeModulesVite, { recursive: true });
1291
+ if (existsSync7(nodeModulesVite)) {
1292
+ rmSync3(nodeModulesVite, { recursive: true });
1066
1293
  cleared++;
1067
1294
  console.log(` ✓ Removed node_modules/.vite/`);
1068
1295
  }
@@ -1074,8 +1301,8 @@ function clearViteCache(rootDir2) {
1074
1301
  }
1075
1302
  }
1076
1303
  function createPreview(rootDir2, name) {
1077
- const previewDir = path7.join(rootDir2, "previews", name);
1078
- if (existsSync5(previewDir)) {
1304
+ const previewDir = path9.join(rootDir2, "previews", name);
1305
+ if (existsSync7(previewDir)) {
1079
1306
  console.error(`Preview "${name}" already exists at: ${previewDir}`);
1080
1307
  process.exit(1);
1081
1308
  }
@@ -1200,8 +1427,8 @@ export default function App() {
1200
1427
  .dark\\:text-white { color: #fff; }
1201
1428
  }
1202
1429
  `;
1203
- writeFileSync3(path7.join(previewDir, "App.tsx"), appTsx);
1204
- writeFileSync3(path7.join(previewDir, "styles.css"), stylesCss);
1430
+ writeFileSync4(path9.join(previewDir, "App.tsx"), appTsx);
1431
+ writeFileSync4(path9.join(previewDir, "styles.css"), stylesCss);
1205
1432
  console.log(`
1206
1433
  ✨ Created preview: previews/${name}/
1207
1434
 
@@ -0,0 +1,2 @@
1
+ export { type PrevConfig, defaultConfig, validateConfig } from './schema';
2
+ export { loadConfig, saveConfig, updateOrder, findConfigFile } from './loader';
@@ -0,0 +1,5 @@
1
+ import type { PrevConfig } from './schema';
2
+ export declare function findConfigFile(rootDir: string): string | null;
3
+ export declare function loadConfig(rootDir: string): PrevConfig;
4
+ export declare function saveConfig(rootDir: string, config: PrevConfig): void;
5
+ export declare function updateOrder(rootDir: string, pathKey: string, order: string[]): void;
@@ -0,0 +1,8 @@
1
+ export interface PrevConfig {
2
+ theme: 'light' | 'dark' | 'system';
3
+ contentWidth: 'constrained' | 'full';
4
+ hidden: string[];
5
+ order: Record<string, string[]>;
6
+ }
7
+ export declare const defaultConfig: PrevConfig;
8
+ export declare function validateConfig(raw: unknown): PrevConfig;
@@ -9,6 +9,7 @@ export interface Page {
9
9
  file: string;
10
10
  description?: string;
11
11
  frontmatter?: Frontmatter;
12
+ hidden?: boolean;
12
13
  }
13
14
  export interface SidebarItem {
14
15
  title: string;
@@ -25,6 +26,8 @@ export declare function parseFrontmatter(content: string): {
25
26
  export declare function fileToRoute(file: string): string;
26
27
  export interface ScanOptions {
27
28
  include?: string[];
29
+ hidden?: string[];
28
30
  }
29
31
  export declare function scanPages(rootDir: string, options?: ScanOptions): Promise<Page[]>;
32
+ export declare function filterVisiblePages(pages: Page[], hiddenPatterns: string[]): Page[];
30
33
  export declare function buildSidebarTree(pages: Page[]): SidebarItem[];
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { PrevConfig } from '../../config';
3
+ export declare function createConfigPlugin(config: PrevConfig): Plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prev-cli",
3
- "version": "0.14.1",
3
+ "version": "0.16.0",
4
4
  "description": "Transform MDX directories into beautiful documentation websites",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,6 +42,7 @@
42
42
  "dependencies": {
43
43
  "@mdx-js/react": "^3.1.1",
44
44
  "@mdx-js/rollup": "^3.0.0",
45
+ "@tabler/icons-react": "^3.36.1",
45
46
  "@tailwindcss/vite": "^4.0.0",
46
47
  "@tanstack/react-router": "^1.145.7",
47
48
  "@terrastruct/d2": "^0.1.33",
@@ -51,8 +52,10 @@
51
52
  "fast-glob": "^3.3.0",
52
53
  "fumadocs-core": "^16.4.3",
53
54
  "fumadocs-ui": "^16.4.3",
55
+ "js-yaml": "^4.1.1",
54
56
  "lucide-react": "^0.460.0",
55
57
  "mermaid": "^11.0.0",
58
+ "picomatch": "^4.0.3",
56
59
  "react": "^19.0.0",
57
60
  "react-dom": "^19.0.0",
58
61
  "react-router-dom": "^7.0.0",
@@ -63,7 +66,9 @@
63
66
  "tailwindcss": "^4.0.0"
64
67
  },
65
68
  "devDependencies": {
69
+ "@types/js-yaml": "^4.0.9",
66
70
  "@types/node": "^22.0.0",
71
+ "@types/picomatch": "^4.0.2",
67
72
  "@types/react": "^19.0.0",
68
73
  "@types/react-dom": "^19.0.0",
69
74
  "bun-types": "^1.3.5",