prev-cli 0.15.0 → 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 +224 -65
- 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",
|
|
@@ -842,8 +974,8 @@ async function createViteConfig(options) {
|
|
|
842
974
|
},
|
|
843
975
|
warmup: {
|
|
844
976
|
clientFiles: [
|
|
845
|
-
|
|
846
|
-
|
|
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:
|
|
987
|
+
outDir: path7.join(rootDir, "dist"),
|
|
856
988
|
reportCompressedSize: false,
|
|
857
989
|
chunkSizeWarningLimit: 1e4,
|
|
858
990
|
rollupOptions: {
|
|
859
991
|
input: {
|
|
860
|
-
main:
|
|
992
|
+
main: path7.join(srcRoot2, "theme/index.html")
|
|
861
993
|
}
|
|
862
994
|
}
|
|
863
995
|
}
|
|
@@ -897,8 +1029,8 @@ async function findAvailablePort(minPort, maxPort) {
|
|
|
897
1029
|
|
|
898
1030
|
// src/vite/start.ts
|
|
899
1031
|
import { exec as exec2 } from "child_process";
|
|
900
|
-
import { existsSync as
|
|
901
|
-
import
|
|
1032
|
+
import { existsSync as existsSync6, rmSync as rmSync2 } from "fs";
|
|
1033
|
+
import path8 from "path";
|
|
902
1034
|
function printWelcome(type) {
|
|
903
1035
|
console.log();
|
|
904
1036
|
console.log(" ✨ prev");
|
|
@@ -931,14 +1063,14 @@ function openBrowser(url) {
|
|
|
931
1063
|
console.log(` ↗ Opened ${url}`);
|
|
932
1064
|
}
|
|
933
1065
|
function clearCache(rootDir) {
|
|
934
|
-
const viteCacheDir =
|
|
935
|
-
const nodeModulesVite =
|
|
1066
|
+
const viteCacheDir = path8.join(rootDir, ".vite");
|
|
1067
|
+
const nodeModulesVite = path8.join(rootDir, "node_modules", ".vite");
|
|
936
1068
|
let cleared = 0;
|
|
937
|
-
if (
|
|
1069
|
+
if (existsSync6(viteCacheDir)) {
|
|
938
1070
|
rmSync2(viteCacheDir, { recursive: true });
|
|
939
1071
|
cleared++;
|
|
940
1072
|
}
|
|
941
|
-
if (
|
|
1073
|
+
if (existsSync6(nodeModulesVite)) {
|
|
942
1074
|
rmSync2(nodeModulesVite, { recursive: true });
|
|
943
1075
|
cleared++;
|
|
944
1076
|
}
|
|
@@ -1031,15 +1163,15 @@ async function previewSite(rootDir, options = {}) {
|
|
|
1031
1163
|
// src/cli.ts
|
|
1032
1164
|
function getVersion() {
|
|
1033
1165
|
try {
|
|
1034
|
-
let dir =
|
|
1166
|
+
let dir = path9.dirname(fileURLToPath3(import.meta.url));
|
|
1035
1167
|
for (let i = 0;i < 5; i++) {
|
|
1036
|
-
const pkgPath =
|
|
1037
|
-
if (
|
|
1038
|
-
const pkg = JSON.parse(
|
|
1168
|
+
const pkgPath = path9.join(dir, "package.json");
|
|
1169
|
+
if (existsSync7(pkgPath)) {
|
|
1170
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
1039
1171
|
if (pkg.name === "prev-cli")
|
|
1040
1172
|
return pkg.version;
|
|
1041
1173
|
}
|
|
1042
|
-
dir =
|
|
1174
|
+
dir = path9.dirname(dir);
|
|
1043
1175
|
}
|
|
1044
1176
|
} catch {}
|
|
1045
1177
|
return "unknown";
|
|
@@ -1057,7 +1189,7 @@ var { values, positionals } = parseArgs({
|
|
|
1057
1189
|
allowPositionals: true
|
|
1058
1190
|
});
|
|
1059
1191
|
var command = positionals[0] || "dev";
|
|
1060
|
-
var rootDir =
|
|
1192
|
+
var rootDir = path9.resolve(values.cwd || positionals[1] || ".");
|
|
1061
1193
|
function printHelp() {
|
|
1062
1194
|
console.log(`
|
|
1063
1195
|
prev - Zero-config documentation site generator
|
|
@@ -1078,6 +1210,33 @@ Options:
|
|
|
1078
1210
|
-h, --help Show this help message
|
|
1079
1211
|
-v, --version Show version number
|
|
1080
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
|
+
|
|
1081
1240
|
Previews:
|
|
1082
1241
|
Previews must be in the previews/ directory at your project root.
|
|
1083
1242
|
Each preview is a subfolder with React components:
|
|
@@ -1121,15 +1280,15 @@ Examples:
|
|
|
1121
1280
|
`);
|
|
1122
1281
|
}
|
|
1123
1282
|
function clearViteCache(rootDir2) {
|
|
1124
|
-
const viteCacheDir =
|
|
1125
|
-
const nodeModulesVite =
|
|
1283
|
+
const viteCacheDir = path9.join(rootDir2, ".vite");
|
|
1284
|
+
const nodeModulesVite = path9.join(rootDir2, "node_modules", ".vite");
|
|
1126
1285
|
let cleared = 0;
|
|
1127
|
-
if (
|
|
1286
|
+
if (existsSync7(viteCacheDir)) {
|
|
1128
1287
|
rmSync3(viteCacheDir, { recursive: true });
|
|
1129
1288
|
cleared++;
|
|
1130
1289
|
console.log(` ✓ Removed .vite/`);
|
|
1131
1290
|
}
|
|
1132
|
-
if (
|
|
1291
|
+
if (existsSync7(nodeModulesVite)) {
|
|
1133
1292
|
rmSync3(nodeModulesVite, { recursive: true });
|
|
1134
1293
|
cleared++;
|
|
1135
1294
|
console.log(` ✓ Removed node_modules/.vite/`);
|
|
@@ -1142,8 +1301,8 @@ function clearViteCache(rootDir2) {
|
|
|
1142
1301
|
}
|
|
1143
1302
|
}
|
|
1144
1303
|
function createPreview(rootDir2, name) {
|
|
1145
|
-
const previewDir =
|
|
1146
|
-
if (
|
|
1304
|
+
const previewDir = path9.join(rootDir2, "previews", name);
|
|
1305
|
+
if (existsSync7(previewDir)) {
|
|
1147
1306
|
console.error(`Preview "${name}" already exists at: ${previewDir}`);
|
|
1148
1307
|
process.exit(1);
|
|
1149
1308
|
}
|
|
@@ -1268,8 +1427,8 @@ export default function App() {
|
|
|
1268
1427
|
.dark\\:text-white { color: #fff; }
|
|
1269
1428
|
}
|
|
1270
1429
|
`;
|
|
1271
|
-
|
|
1272
|
-
|
|
1430
|
+
writeFileSync4(path9.join(previewDir, "App.tsx"), appTsx);
|
|
1431
|
+
writeFileSync4(path9.join(previewDir, "styles.css"), stylesCss);
|
|
1273
1432
|
console.log(`
|
|
1274
1433
|
✨ Created preview: previews/${name}/
|
|
1275
1434
|
|
|
@@ -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.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",
|