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 +289 -62
- 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
|
}
|
|
@@ -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
|
|
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 =
|
|
1166
|
+
let dir = path9.dirname(fileURLToPath3(import.meta.url));
|
|
967
1167
|
for (let i = 0;i < 5; i++) {
|
|
968
|
-
const pkgPath =
|
|
969
|
-
if (
|
|
970
|
-
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"));
|
|
971
1171
|
if (pkg.name === "prev-cli")
|
|
972
1172
|
return pkg.version;
|
|
973
1173
|
}
|
|
974
|
-
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 =
|
|
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 =
|
|
1057
|
-
const nodeModulesVite =
|
|
1283
|
+
const viteCacheDir = path9.join(rootDir2, ".vite");
|
|
1284
|
+
const nodeModulesVite = path9.join(rootDir2, "node_modules", ".vite");
|
|
1058
1285
|
let cleared = 0;
|
|
1059
|
-
if (
|
|
1060
|
-
|
|
1286
|
+
if (existsSync7(viteCacheDir)) {
|
|
1287
|
+
rmSync3(viteCacheDir, { recursive: true });
|
|
1061
1288
|
cleared++;
|
|
1062
1289
|
console.log(` ✓ Removed .vite/`);
|
|
1063
1290
|
}
|
|
1064
|
-
if (
|
|
1065
|
-
|
|
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 =
|
|
1078
|
-
if (
|
|
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
|
-
|
|
1204
|
-
|
|
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,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",
|