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 +240 -67
- package/dist/config/index.d.ts +2 -0
- package/dist/config/loader.d.ts +5 -0
- package/dist/config/schema.d.ts +8 -0
- package/dist/vite/pages.d.ts +3 -0
- package/dist/vite/plugins/config-plugin.d.ts +3 -0
- package/package.json +6 -1
- package/src/theme/Layout.tsx +39 -434
- package/src/theme/TOCPanel.css +153 -0
- package/src/theme/TOCPanel.tsx +183 -0
- package/src/theme/Toolbar.css +58 -0
- package/src/theme/Toolbar.tsx +91 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/styles.css +11 -218
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { parseArgs } from "util";
|
|
5
|
-
import
|
|
6
|
-
import { existsSync as
|
|
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
|
|
18
|
+
import path7 from "path";
|
|
19
19
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
20
|
-
import { existsSync as
|
|
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
|
|
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
|
-
|
|
389
|
+
const previewDirs = new Map;
|
|
390
|
+
for (const file of entryFiles) {
|
|
386
391
|
const dir = path4.dirname(file);
|
|
387
|
-
|
|
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 =
|
|
773
|
+
let dir = path7.dirname(fileURLToPath2(import.meta.url));
|
|
671
774
|
for (let i = 0;i < 10; i++) {
|
|
672
|
-
const pkgPath =
|
|
673
|
-
if (
|
|
775
|
+
const pkgPath = path7.join(dir, "package.json");
|
|
776
|
+
if (existsSync5(pkgPath)) {
|
|
674
777
|
try {
|
|
675
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
784
|
+
const parent = path7.dirname(dir);
|
|
682
785
|
if (parent === dir)
|
|
683
786
|
break;
|
|
684
787
|
dir = parent;
|
|
685
788
|
}
|
|
686
|
-
return
|
|
789
|
+
return path7.dirname(path7.dirname(fileURLToPath2(import.meta.url)));
|
|
687
790
|
}
|
|
688
791
|
function findNodeModules(cliRoot2) {
|
|
689
|
-
const localNodeModules =
|
|
690
|
-
if (
|
|
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 =
|
|
798
|
+
const parent = path7.dirname(dir);
|
|
696
799
|
if (parent === dir)
|
|
697
800
|
break;
|
|
698
|
-
if (
|
|
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 =
|
|
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 =
|
|
732
|
-
const resolved =
|
|
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 =
|
|
743
|
-
if (
|
|
744
|
-
const html =
|
|
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 =
|
|
753
|
-
const previewDir =
|
|
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 (
|
|
891
|
+
if (existsSync5(previewDir)) {
|
|
760
892
|
try {
|
|
761
|
-
const
|
|
893
|
+
const config2 = await buildPreviewConfig(previewDir);
|
|
762
894
|
res.setHeader("Content-Type", "application/json");
|
|
763
|
-
res.end(JSON.stringify(
|
|
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 = !
|
|
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 =
|
|
778
|
-
const htmlPath =
|
|
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 (
|
|
914
|
+
if (existsSync5(htmlPath)) {
|
|
783
915
|
try {
|
|
784
|
-
let html =
|
|
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":
|
|
806
|
-
"@prev/theme":
|
|
807
|
-
react:
|
|
808
|
-
"react-dom":
|
|
809
|
-
"@tanstack/react-router":
|
|
810
|
-
mermaid:
|
|
811
|
-
dayjs:
|
|
812
|
-
"@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
|
-
|
|
846
|
-
|
|
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:
|
|
993
|
+
outDir: path7.join(rootDir, "dist"),
|
|
856
994
|
reportCompressedSize: false,
|
|
857
995
|
chunkSizeWarningLimit: 1e4,
|
|
858
996
|
rollupOptions: {
|
|
859
997
|
input: {
|
|
860
|
-
main:
|
|
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
|
|
901
|
-
import
|
|
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 =
|
|
935
|
-
const nodeModulesVite =
|
|
1072
|
+
const viteCacheDir = path8.join(rootDir, ".vite");
|
|
1073
|
+
const nodeModulesVite = path8.join(rootDir, "node_modules", ".vite");
|
|
936
1074
|
let cleared = 0;
|
|
937
|
-
if (
|
|
1075
|
+
if (existsSync6(viteCacheDir)) {
|
|
938
1076
|
rmSync2(viteCacheDir, { recursive: true });
|
|
939
1077
|
cleared++;
|
|
940
1078
|
}
|
|
941
|
-
if (
|
|
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 =
|
|
1172
|
+
let dir = path9.dirname(fileURLToPath3(import.meta.url));
|
|
1035
1173
|
for (let i = 0;i < 5; i++) {
|
|
1036
|
-
const pkgPath =
|
|
1037
|
-
if (
|
|
1038
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
1146
|
-
if (
|
|
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
|
-
|
|
1272
|
-
|
|
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,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;
|
package/dist/vite/pages.d.ts
CHANGED
|
@@ -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[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prev-cli",
|
|
3
|
-
"version": "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",
|