prev-cli 0.15.0 → 0.16.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.
package/dist/cli.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { parseArgs } from "util";
5
- import path8 from "path";
6
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, rmSync as rmSync3, 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",
@@ -829,6 +961,12 @@ async function createViteConfig(options) {
829
961
  "mermaid",
830
962
  "dayjs",
831
963
  "@terrastruct/d2"
964
+ ],
965
+ exclude: [
966
+ "virtual:prev-config",
967
+ "virtual:prev-previews",
968
+ "virtual:prev-pages",
969
+ "@prev/theme"
832
970
  ]
833
971
  },
834
972
  ssr: {
@@ -842,8 +980,8 @@ async function createViteConfig(options) {
842
980
  },
843
981
  warmup: {
844
982
  clientFiles: [
845
- path6.join(srcRoot2, "theme/entry.tsx"),
846
- path6.join(srcRoot2, "theme/styles.css")
983
+ path7.join(srcRoot2, "theme/entry.tsx"),
984
+ path7.join(srcRoot2, "theme/styles.css")
847
985
  ]
848
986
  }
849
987
  },
@@ -852,12 +990,12 @@ async function createViteConfig(options) {
852
990
  strictPort: false
853
991
  },
854
992
  build: {
855
- outDir: path6.join(rootDir, "dist"),
993
+ outDir: path7.join(rootDir, "dist"),
856
994
  reportCompressedSize: false,
857
995
  chunkSizeWarningLimit: 1e4,
858
996
  rollupOptions: {
859
997
  input: {
860
- main: path6.join(srcRoot2, "theme/index.html")
998
+ main: path7.join(srcRoot2, "theme/index.html")
861
999
  }
862
1000
  }
863
1001
  }
@@ -897,8 +1035,8 @@ async function findAvailablePort(minPort, maxPort) {
897
1035
 
898
1036
  // src/vite/start.ts
899
1037
  import { exec as exec2 } from "child_process";
900
- import { existsSync as existsSync5, rmSync as rmSync2 } from "fs";
901
- import path7 from "path";
1038
+ import { existsSync as existsSync6, rmSync as rmSync2 } from "fs";
1039
+ import path8 from "path";
902
1040
  function printWelcome(type) {
903
1041
  console.log();
904
1042
  console.log(" ✨ prev");
@@ -931,14 +1069,14 @@ function openBrowser(url) {
931
1069
  console.log(` ↗ Opened ${url}`);
932
1070
  }
933
1071
  function clearCache(rootDir) {
934
- const viteCacheDir = path7.join(rootDir, ".vite");
935
- const nodeModulesVite = path7.join(rootDir, "node_modules", ".vite");
1072
+ const viteCacheDir = path8.join(rootDir, ".vite");
1073
+ const nodeModulesVite = path8.join(rootDir, "node_modules", ".vite");
936
1074
  let cleared = 0;
937
- if (existsSync5(viteCacheDir)) {
1075
+ if (existsSync6(viteCacheDir)) {
938
1076
  rmSync2(viteCacheDir, { recursive: true });
939
1077
  cleared++;
940
1078
  }
941
- if (existsSync5(nodeModulesVite)) {
1079
+ if (existsSync6(nodeModulesVite)) {
942
1080
  rmSync2(nodeModulesVite, { recursive: true });
943
1081
  cleared++;
944
1082
  }
@@ -1031,15 +1169,15 @@ async function previewSite(rootDir, options = {}) {
1031
1169
  // src/cli.ts
1032
1170
  function getVersion() {
1033
1171
  try {
1034
- let dir = path8.dirname(fileURLToPath3(import.meta.url));
1172
+ let dir = path9.dirname(fileURLToPath3(import.meta.url));
1035
1173
  for (let i = 0;i < 5; i++) {
1036
- const pkgPath = path8.join(dir, "package.json");
1037
- if (existsSync6(pkgPath)) {
1038
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1174
+ const pkgPath = path9.join(dir, "package.json");
1175
+ if (existsSync7(pkgPath)) {
1176
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
1039
1177
  if (pkg.name === "prev-cli")
1040
1178
  return pkg.version;
1041
1179
  }
1042
- dir = path8.dirname(dir);
1180
+ dir = path9.dirname(dir);
1043
1181
  }
1044
1182
  } catch {}
1045
1183
  return "unknown";
@@ -1057,7 +1195,7 @@ var { values, positionals } = parseArgs({
1057
1195
  allowPositionals: true
1058
1196
  });
1059
1197
  var command = positionals[0] || "dev";
1060
- var rootDir = path8.resolve(values.cwd || positionals[1] || ".");
1198
+ var rootDir = path9.resolve(values.cwd || positionals[1] || ".");
1061
1199
  function printHelp() {
1062
1200
  console.log(`
1063
1201
  prev - Zero-config documentation site generator
@@ -1078,6 +1216,33 @@ Options:
1078
1216
  -h, --help Show this help message
1079
1217
  -v, --version Show version number
1080
1218
 
1219
+ Floating Toolbar:
1220
+ A draggable pill at the bottom of the screen with:
1221
+ - TOC button: Opens navigation panel (dropdown on desktop, overlay on mobile)
1222
+ - Previews button: Links to /previews catalog (if previews exist)
1223
+ - Width toggle: Switch between constrained and full-width content
1224
+ - Theme toggle: Switch between light and dark mode
1225
+
1226
+ Configuration (.prev.yaml):
1227
+ Create a .prev.yaml file in your docs root to customize behavior:
1228
+
1229
+ theme: system # light | dark | system (default: system)
1230
+ contentWidth: constrained # constrained | full (default: constrained)
1231
+ hidden: # Glob patterns for pages to hide
1232
+ - "internal/**"
1233
+ - "wip-*.md"
1234
+ order: # Custom page ordering
1235
+ "/":
1236
+ - "getting-started.md"
1237
+ - "guides/"
1238
+
1239
+ Pages can also be hidden via frontmatter:
1240
+ ---
1241
+ hidden: true
1242
+ ---
1243
+
1244
+ Drag pages in the TOC panel to reorder - changes auto-save to config.
1245
+
1081
1246
  Previews:
1082
1247
  Previews must be in the previews/ directory at your project root.
1083
1248
  Each preview is a subfolder with React components:
@@ -1120,16 +1285,24 @@ Examples:
1120
1285
  prev clean -d 7 Remove caches older than 7 days
1121
1286
  `);
1122
1287
  }
1123
- function clearViteCache(rootDir2) {
1124
- const viteCacheDir = path8.join(rootDir2, ".vite");
1125
- const nodeModulesVite = path8.join(rootDir2, "node_modules", ".vite");
1288
+ async function clearViteCache(rootDir2) {
1126
1289
  let cleared = 0;
1127
- if (existsSync6(viteCacheDir)) {
1290
+ try {
1291
+ const prevCacheDir = await getCacheDir(rootDir2);
1292
+ if (existsSync7(prevCacheDir)) {
1293
+ rmSync3(prevCacheDir, { recursive: true });
1294
+ cleared++;
1295
+ console.log(` ✓ Removed ${prevCacheDir}`);
1296
+ }
1297
+ } catch {}
1298
+ const viteCacheDir = path9.join(rootDir2, ".vite");
1299
+ const nodeModulesVite = path9.join(rootDir2, "node_modules", ".vite");
1300
+ if (existsSync7(viteCacheDir)) {
1128
1301
  rmSync3(viteCacheDir, { recursive: true });
1129
1302
  cleared++;
1130
1303
  console.log(` ✓ Removed .vite/`);
1131
1304
  }
1132
- if (existsSync6(nodeModulesVite)) {
1305
+ if (existsSync7(nodeModulesVite)) {
1133
1306
  rmSync3(nodeModulesVite, { recursive: true });
1134
1307
  cleared++;
1135
1308
  console.log(` ✓ Removed node_modules/.vite/`);
@@ -1142,8 +1315,8 @@ function clearViteCache(rootDir2) {
1142
1315
  }
1143
1316
  }
1144
1317
  function createPreview(rootDir2, name) {
1145
- const previewDir = path8.join(rootDir2, "previews", name);
1146
- if (existsSync6(previewDir)) {
1318
+ const previewDir = path9.join(rootDir2, "previews", name);
1319
+ if (existsSync7(previewDir)) {
1147
1320
  console.error(`Preview "${name}" already exists at: ${previewDir}`);
1148
1321
  process.exit(1);
1149
1322
  }
@@ -1268,8 +1441,8 @@ export default function App() {
1268
1441
  .dark\\:text-white { color: #fff; }
1269
1442
  }
1270
1443
  `;
1271
- writeFileSync3(path8.join(previewDir, "App.tsx"), appTsx);
1272
- writeFileSync3(path8.join(previewDir, "styles.css"), stylesCss);
1444
+ writeFileSync4(path9.join(previewDir, "App.tsx"), appTsx);
1445
+ writeFileSync4(path9.join(previewDir, "styles.css"), stylesCss);
1273
1446
  console.log(`
1274
1447
  ✨ Created preview: previews/${name}/
1275
1448
 
@@ -1317,7 +1490,7 @@ async function main() {
1317
1490
  createPreview(rootDir, previewName);
1318
1491
  break;
1319
1492
  case "clearcache":
1320
- clearViteCache(rootDir);
1493
+ await clearViteCache(rootDir);
1321
1494
  break;
1322
1495
  default:
1323
1496
  console.error(`Unknown command: ${command}`);
@@ -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.15.0",
3
+ "version": "0.16.1",
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",