prev-cli 0.24.6 → 0.24.8

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
@@ -15,68 +15,18 @@ import react from "@vitejs/plugin-react";
15
15
  import mdx from "@mdx-js/rollup";
16
16
  import remarkGfm from "remark-gfm";
17
17
  import rehypeHighlight from "rehype-highlight";
18
- import path9 from "path";
18
+ import path8 from "path";
19
+ import os from "os";
19
20
  import { fileURLToPath as fileURLToPath2 } from "url";
20
21
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
21
22
 
22
- // src/utils/cache.ts
23
- import { createHash } from "crypto";
24
- import { readdir, rm, stat, mkdir } from "fs/promises";
25
- import { exec } from "child_process";
26
- import { promisify } from "util";
27
- import path from "path";
28
- import os from "os";
29
- var execAsync = promisify(exec);
30
- var DEFAULT_CACHE_ROOT = path.join(os.homedir(), ".cache/prev");
31
- async function getCacheDir(rootDir, branch) {
32
- const resolvedBranch = branch ?? await getCurrentBranch(rootDir);
33
- const hash = createHash("sha1").update(`${rootDir}:${resolvedBranch}`).digest("hex").slice(0, 12);
34
- return path.join(DEFAULT_CACHE_ROOT, hash);
35
- }
36
- async function getCurrentBranch(rootDir) {
37
- try {
38
- const { stdout } = await execAsync("git branch --show-current", { cwd: rootDir });
39
- return stdout.trim() || "detached";
40
- } catch {
41
- return "no-git";
42
- }
43
- }
44
- async function cleanCache(options) {
45
- const cacheRoot = options.cacheRoot ?? DEFAULT_CACHE_ROOT;
46
- const maxAge = options.maxAgeDays * 24 * 60 * 60 * 1000;
47
- const now = Date.now();
48
- let dirs;
49
- try {
50
- dirs = await readdir(cacheRoot);
51
- } catch {
52
- return 0;
53
- }
54
- let removed = 0;
55
- for (const dir of dirs) {
56
- const fullPath = path.join(cacheRoot, dir);
57
- try {
58
- const info = await stat(fullPath);
59
- if (info.isDirectory() && now - info.mtimeMs > maxAge) {
60
- await rm(fullPath, { recursive: true });
61
- removed++;
62
- }
63
- } catch {}
64
- }
65
- return removed;
66
- }
67
- async function ensureCacheDir(rootDir) {
68
- const cacheDir = await getCacheDir(rootDir);
69
- await mkdir(cacheDir, { recursive: true });
70
- return cacheDir;
71
- }
72
-
73
23
  // src/vite/plugins/pages-plugin.ts
74
- import path3 from "path";
24
+ import path2 from "path";
75
25
 
76
26
  // src/vite/pages.ts
77
27
  import fg from "fast-glob";
78
28
  import { readFile } from "fs/promises";
79
- import path2 from "path";
29
+ import path from "path";
80
30
  import picomatch from "picomatch";
81
31
  function parseValue(value) {
82
32
  const trimmed = value.trim();
@@ -138,9 +88,9 @@ function fileToRoute(file) {
138
88
  normalizedFile = normalizedFile.slice(firstDir.length + 1) || normalizedFile;
139
89
  }
140
90
  const withoutExt = normalizedFile.replace(/\.mdx?$/, "");
141
- const basename = path2.basename(withoutExt).toLowerCase();
91
+ const basename = path.basename(withoutExt).toLowerCase();
142
92
  if (basename === "index" || basename === "readme") {
143
- const dir = path2.dirname(withoutExt);
93
+ const dir = path.dirname(withoutExt);
144
94
  if (dir === "." || dir === "") {
145
95
  return "/";
146
96
  }
@@ -156,9 +106,30 @@ async function scanPages(rootDir, options = {}) {
156
106
  patterns.push(`${dir}/**/*.{md,mdx}`);
157
107
  }
158
108
  const ignore = [
159
- "node_modules/**",
160
- "dist/**",
161
- ".cache/**",
109
+ "**/node_modules/**",
110
+ "**/.worktrees/**",
111
+ "**/.claude/**",
112
+ "**/dist/**",
113
+ "**/.cache/**",
114
+ "**/.git/**",
115
+ "**/coverage/**",
116
+ "**/build/**",
117
+ "**/.next/**",
118
+ "**/.nuxt/**",
119
+ "**/.turbo/**",
120
+ "**/.vercel/**",
121
+ "**/.svelte-kit/**",
122
+ "**/vendor/**",
123
+ "**/target/**",
124
+ "apps/**",
125
+ "packages/**",
126
+ "services/**",
127
+ "libs/**",
128
+ "src/**",
129
+ "test/**",
130
+ "tests/**",
131
+ "__tests__/**",
132
+ "e2e/**",
162
133
  "CLAUDE.md",
163
134
  "CHANGELOG.md",
164
135
  "CONTRIBUTING.md",
@@ -182,16 +153,16 @@ async function scanPages(rootDir, options = {}) {
182
153
  const routeMap = new Map;
183
154
  for (const file of filteredFiles) {
184
155
  const route = fileToRoute(file);
185
- const basename = path2.basename(file, path2.extname(file)).toLowerCase();
156
+ const basename = path.basename(file, path.extname(file)).toLowerCase();
186
157
  const priority = basename === "index" ? 1 : basename === "readme" ? 2 : 0;
187
158
  const existing = routeMap.get(route);
188
159
  if (!existing || priority > 0 && priority < existing.priority) {
189
160
  routeMap.set(route, { file, priority });
190
161
  }
191
162
  }
192
- const pages = [];
193
- for (const { file } of routeMap.values()) {
194
- const fullPath = path2.join(rootDir, file);
163
+ const fileEntries = Array.from(routeMap.values());
164
+ const pages = await Promise.all(fileEntries.map(async ({ file }) => {
165
+ const fullPath = path.join(rootDir, file);
195
166
  const rawContent = await readFile(fullPath, "utf-8");
196
167
  const { frontmatter, content } = parseFrontmatter(rawContent);
197
168
  const title = extractTitle(content, file, frontmatter);
@@ -209,8 +180,8 @@ async function scanPages(rootDir, options = {}) {
209
180
  if (frontmatter.hidden === true) {
210
181
  page.hidden = true;
211
182
  }
212
- pages.push(page);
213
- }
183
+ return page;
184
+ }));
214
185
  return pages.sort((a, b) => a.route.localeCompare(b.route));
215
186
  }
216
187
  function extractTitle(content, file, frontmatter) {
@@ -221,12 +192,12 @@ function extractTitle(content, file, frontmatter) {
221
192
  if (match) {
222
193
  return match[1].trim();
223
194
  }
224
- const basename = path2.basename(file, path2.extname(file)).toLowerCase();
195
+ const basename = path.basename(file, path.extname(file)).toLowerCase();
225
196
  if (isIndexFile(basename)) {
226
- const dirname = path2.dirname(file);
227
- return dirname === "." ? "Home" : capitalize(path2.basename(dirname));
197
+ const dirname = path.dirname(file);
198
+ return dirname === "." ? "Home" : capitalize(path.basename(dirname));
228
199
  }
229
- return capitalize(path2.basename(file, path2.extname(file)));
200
+ return capitalize(path.basename(file, path.extname(file)));
230
201
  }
231
202
  function capitalize(str) {
232
203
  return str.charAt(0).toUpperCase() + str.slice(1).replace(/-/g, " ");
@@ -295,7 +266,7 @@ function pagesPlugin(rootDir, options = {}) {
295
266
  if (id === RESOLVED_VIRTUAL_MODULES_ID) {
296
267
  const pages = await getPages();
297
268
  const imports = pages.map((page, i) => {
298
- const absolutePath = path3.join(rootDir, page.file);
269
+ const absolutePath = path2.join(rootDir, page.file);
299
270
  return `import * as _page${i} from ${JSON.stringify(absolutePath)};`;
300
271
  }).join(`
301
272
  `);
@@ -331,13 +302,13 @@ ${entries}
331
302
  }
332
303
 
333
304
  // src/vite/plugins/entry-plugin.ts
334
- import path4 from "path";
305
+ import path3 from "path";
335
306
  import { fileURLToPath } from "url";
336
307
  import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
337
308
  function findCliRoot() {
338
- let dir = path4.dirname(fileURLToPath(import.meta.url));
309
+ let dir = path3.dirname(fileURLToPath(import.meta.url));
339
310
  for (let i = 0;i < 10; i++) {
340
- const pkgPath = path4.join(dir, "package.json");
311
+ const pkgPath = path3.join(dir, "package.json");
341
312
  if (existsSync(pkgPath)) {
342
313
  try {
343
314
  const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
@@ -346,15 +317,15 @@ function findCliRoot() {
346
317
  }
347
318
  } catch {}
348
319
  }
349
- const parent = path4.dirname(dir);
320
+ const parent = path3.dirname(dir);
350
321
  if (parent === dir)
351
322
  break;
352
323
  dir = parent;
353
324
  }
354
- return path4.dirname(path4.dirname(fileURLToPath(import.meta.url)));
325
+ return path3.dirname(path3.dirname(fileURLToPath(import.meta.url)));
355
326
  }
356
327
  var cliRoot = findCliRoot();
357
- var srcRoot = path4.join(cliRoot, "src");
328
+ var srcRoot = path3.join(cliRoot, "src");
358
329
  function getHtml(entryPath, forBuild = false) {
359
330
  const scriptSrc = forBuild ? entryPath : `/@fs${entryPath}`;
360
331
  return `<!DOCTYPE html>
@@ -377,13 +348,13 @@ function getHtml(entryPath, forBuild = false) {
377
348
  </html>`;
378
349
  }
379
350
  function entryPlugin(rootDir) {
380
- const entryPath = path4.join(srcRoot, "theme/entry.tsx");
351
+ const entryPath = path3.join(srcRoot, "theme/entry.tsx");
381
352
  let tempHtmlPath = null;
382
353
  return {
383
354
  name: "prev-entry",
384
355
  config(config, { command }) {
385
356
  if (command === "build" && rootDir) {
386
- tempHtmlPath = path4.join(rootDir, "index.html");
357
+ tempHtmlPath = path3.join(rootDir, "index.html");
387
358
  writeFileSync(tempHtmlPath, getHtml(entryPath, true));
388
359
  const existingInput = config.build?.rollupOptions?.input || {};
389
360
  const inputObj = typeof existingInput === "string" ? { _original: existingInput } : Array.isArray(existingInput) ? Object.fromEntries(existingInput.map((f, i) => [`entry${i}`, f])) : existingInput;
@@ -430,10 +401,10 @@ function entryPlugin(rootDir) {
430
401
 
431
402
  // src/vite/previews.ts
432
403
  import fg2 from "fast-glob";
433
- import path5 from "path";
404
+ import path4 from "path";
434
405
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
435
406
  async function scanPreviews(rootDir) {
436
- const previewsDir = path5.join(rootDir, "previews");
407
+ const previewsDir = path4.join(rootDir, "previews");
437
408
  if (!existsSync2(previewsDir)) {
438
409
  return [];
439
410
  }
@@ -443,17 +414,17 @@ async function scanPreviews(rootDir) {
443
414
  });
444
415
  const previewDirs = new Map;
445
416
  for (const file of entryFiles) {
446
- const dir = path5.dirname(file);
417
+ const dir = path4.dirname(file);
447
418
  if (!previewDirs.has(dir)) {
448
419
  previewDirs.set(dir, file);
449
420
  }
450
421
  }
451
422
  return Array.from(previewDirs.entries()).map(([dir, file]) => {
452
- const name = dir === "." ? path5.basename(previewsDir) : dir;
423
+ const name = dir === "." ? path4.basename(previewsDir) : dir;
453
424
  return {
454
425
  name,
455
426
  route: `/_preview/${name}`,
456
- htmlPath: path5.join(previewsDir, file)
427
+ htmlPath: path4.join(previewsDir, file)
457
428
  };
458
429
  });
459
430
  }
@@ -463,8 +434,8 @@ async function scanPreviewFiles(previewDir) {
463
434
  ignore: ["node_modules/**", "dist/**"]
464
435
  });
465
436
  return files.map((file) => {
466
- const content = readFileSync2(path5.join(previewDir, file), "utf-8");
467
- const ext = path5.extname(file).slice(1);
437
+ const content = readFileSync2(path4.join(previewDir, file), "utf-8");
438
+ const ext = path4.extname(file).slice(1);
468
439
  return {
469
440
  path: file,
470
441
  content,
@@ -608,7 +579,7 @@ async function buildPreviewHtml(config) {
608
579
 
609
580
  // src/vite/plugins/previews-plugin.ts
610
581
  import { existsSync as existsSync3, mkdirSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
611
- import path6 from "path";
582
+ import path5 from "path";
612
583
  var VIRTUAL_MODULE_ID2 = "virtual:prev-previews";
613
584
  var RESOLVED_VIRTUAL_MODULE_ID2 = "\x00" + VIRTUAL_MODULE_ID2;
614
585
  function previewsPlugin(rootDir) {
@@ -641,10 +612,10 @@ function previewsPlugin(rootDir) {
641
612
  async closeBundle() {
642
613
  if (!isBuild)
643
614
  return;
644
- const distDir = path6.join(rootDir, "dist");
645
- const targetDir = path6.join(distDir, "_preview");
646
- const previewsDir = path6.join(rootDir, "previews");
647
- const oldPreviewsDir = path6.join(distDir, "previews");
615
+ const distDir = path5.join(rootDir, "dist");
616
+ const targetDir = path5.join(distDir, "_preview");
617
+ const previewsDir = path5.join(rootDir, "previews");
618
+ const oldPreviewsDir = path5.join(distDir, "previews");
648
619
  if (existsSync3(oldPreviewsDir)) {
649
620
  rmSync(oldPreviewsDir, { recursive: true });
650
621
  }
@@ -657,7 +628,7 @@ function previewsPlugin(rootDir) {
657
628
  console.log(`
658
629
  Building ${previews.length} preview(s)...`);
659
630
  for (const preview of previews) {
660
- const previewDir = path6.join(previewsDir, preview.name);
631
+ const previewDir = path5.join(previewsDir, preview.name);
661
632
  try {
662
633
  const config = await buildPreviewConfig(previewDir);
663
634
  const result = await buildPreviewHtml(config);
@@ -665,9 +636,9 @@ function previewsPlugin(rootDir) {
665
636
  console.error(` ✗ ${preview.name}: ${result.error}`);
666
637
  continue;
667
638
  }
668
- const outputDir = path6.join(targetDir, preview.name);
639
+ const outputDir = path5.join(targetDir, preview.name);
669
640
  mkdirSync(outputDir, { recursive: true });
670
- writeFileSync2(path6.join(outputDir, "index.html"), result.html);
641
+ writeFileSync2(path5.join(outputDir, "index.html"), result.html);
671
642
  console.log(` ✓ ${preview.name}`);
672
643
  } catch (err) {
673
644
  console.error(` ✗ ${preview.name}: ${err}`);
@@ -788,11 +759,11 @@ function validateConfig(raw) {
788
759
  }
789
760
  // src/config/loader.ts
790
761
  import { readFileSync as readFileSync3, existsSync as existsSync4, writeFileSync as writeFileSync3 } from "fs";
791
- import path7 from "path";
762
+ import path6 from "path";
792
763
  import yaml from "js-yaml";
793
764
  function findConfigFile(rootDir) {
794
- const yamlPath = path7.join(rootDir, ".prev.yaml");
795
- const ymlPath = path7.join(rootDir, ".prev.yml");
765
+ const yamlPath = path6.join(rootDir, ".prev.yaml");
766
+ const ymlPath = path6.join(rootDir, ".prev.yml");
796
767
  if (existsSync4(yamlPath))
797
768
  return yamlPath;
798
769
  if (existsSync4(ymlPath))
@@ -814,7 +785,7 @@ function loadConfig(rootDir) {
814
785
  }
815
786
  }
816
787
  function saveConfig(rootDir, config) {
817
- const configPath = findConfigFile(rootDir) || path7.join(rootDir, ".prev.yaml");
788
+ const configPath = findConfigFile(rootDir) || path6.join(rootDir, ".prev.yaml");
818
789
  const content = yaml.dump(config, {
819
790
  indent: 2,
820
791
  lineWidth: -1,
@@ -830,7 +801,7 @@ function updateOrder(rootDir, pathKey, order) {
830
801
  }
831
802
  // src/utils/debug.ts
832
803
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
833
- import path8 from "path";
804
+ import path7 from "path";
834
805
 
835
806
  class DebugCollector {
836
807
  startTime;
@@ -889,7 +860,7 @@ class DebugCollector {
889
860
  }
890
861
  byDirectory[dir] = (byDirectory[dir] || 0) + 1;
891
862
  }
892
- const slowest = Object.entries(fileTimeAccum).map(([path9, totalMs]) => ({ path: path9, totalMs })).sort((a, b) => b.totalMs - a.totalMs).slice(0, 20);
863
+ const slowest = Object.entries(fileTimeAccum).map(([path8, totalMs]) => ({ path: path8, totalMs })).sort((a, b) => b.totalMs - a.totalMs).slice(0, 20);
893
864
  return {
894
865
  totalFiles: this.files.length,
895
866
  byDirectory,
@@ -908,11 +879,11 @@ class DebugCollector {
908
879
  files: this.files,
909
880
  summary: this.generateSummary()
910
881
  };
911
- const debugDir = path8.join(this.rootDir, ".prev-debug");
882
+ const debugDir = path7.join(this.rootDir, ".prev-debug");
912
883
  mkdirSync2(debugDir, { recursive: true });
913
884
  const date = new Date;
914
885
  const filename = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}-${String(date.getHours()).padStart(2, "0")}-${String(date.getMinutes()).padStart(2, "0")}-${String(date.getSeconds()).padStart(2, "0")}.json`;
915
- const filepath = path8.join(debugDir, filename);
886
+ const filepath = path7.join(debugDir, filename);
916
887
  writeFileSync4(filepath, JSON.stringify(report, null, 2));
917
888
  return filepath;
918
889
  }
@@ -981,9 +952,9 @@ function createFriendlyLogger() {
981
952
  };
982
953
  }
983
954
  function findCliRoot2() {
984
- let dir = path9.dirname(fileURLToPath2(import.meta.url));
955
+ let dir = path8.dirname(fileURLToPath2(import.meta.url));
985
956
  for (let i = 0;i < 10; i++) {
986
- const pkgPath = path9.join(dir, "package.json");
957
+ const pkgPath = path8.join(dir, "package.json");
987
958
  if (existsSync5(pkgPath)) {
988
959
  try {
989
960
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
@@ -992,24 +963,24 @@ function findCliRoot2() {
992
963
  }
993
964
  } catch {}
994
965
  }
995
- const parent = path9.dirname(dir);
966
+ const parent = path8.dirname(dir);
996
967
  if (parent === dir)
997
968
  break;
998
969
  dir = parent;
999
970
  }
1000
- return path9.dirname(path9.dirname(fileURLToPath2(import.meta.url)));
971
+ return path8.dirname(path8.dirname(fileURLToPath2(import.meta.url)));
1001
972
  }
1002
973
  function findNodeModules(cliRoot2) {
1003
- const localNodeModules = path9.join(cliRoot2, "node_modules");
1004
- if (existsSync5(path9.join(localNodeModules, "react"))) {
974
+ const localNodeModules = path8.join(cliRoot2, "node_modules");
975
+ if (existsSync5(path8.join(localNodeModules, "react"))) {
1005
976
  return localNodeModules;
1006
977
  }
1007
978
  let dir = cliRoot2;
1008
979
  for (let i = 0;i < 10; i++) {
1009
- const parent = path9.dirname(dir);
980
+ const parent = path8.dirname(dir);
1010
981
  if (parent === dir)
1011
982
  break;
1012
- if (path9.basename(parent) === "node_modules" && existsSync5(path9.join(parent, "react"))) {
983
+ if (path8.basename(parent) === "node_modules" && existsSync5(path8.join(parent, "react"))) {
1013
984
  return parent;
1014
985
  }
1015
986
  dir = parent;
@@ -1018,19 +989,19 @@ function findNodeModules(cliRoot2) {
1018
989
  }
1019
990
  var cliRoot2 = findCliRoot2();
1020
991
  var cliNodeModules = findNodeModules(cliRoot2);
1021
- var srcRoot2 = path9.join(cliRoot2, "src");
992
+ var srcRoot2 = path8.join(cliRoot2, "src");
1022
993
  async function createViteConfig(options) {
1023
994
  const { rootDir, mode, port, include, base, debug } = options;
1024
- const cacheDir = await ensureCacheDir(rootDir);
1025
995
  const config = loadConfig(rootDir);
1026
996
  const debugCollector = debug ? createDebugCollector(rootDir) : null;
1027
997
  if (debugCollector) {
1028
998
  debugCollector.startPhase("configLoad");
1029
999
  }
1000
+ const globalCacheDir = path8.join(os.homedir(), ".cache/prev/deps");
1030
1001
  return {
1031
1002
  root: rootDir,
1032
1003
  mode,
1033
- cacheDir,
1004
+ cacheDir: globalCacheDir,
1034
1005
  base: base || "/",
1035
1006
  customLogger: createFriendlyLogger(),
1036
1007
  logLevel: mode === "production" ? "silent" : "info",
@@ -1043,8 +1014,8 @@ async function createViteConfig(options) {
1043
1014
  rehypePlugins: [rehypeHighlight],
1044
1015
  providerImportSource: "@mdx-js/react",
1045
1016
  include: [
1046
- path9.join(rootDir, "**/*.md"),
1047
- path9.join(rootDir, "**/*.mdx")
1017
+ path8.join(rootDir, "**/*.md"),
1018
+ path8.join(rootDir, "**/*.mdx")
1048
1019
  ],
1049
1020
  exclude: [
1050
1021
  "**/node_modules/**",
@@ -1092,7 +1063,7 @@ async function createViteConfig(options) {
1092
1063
  if (urlPath.startsWith("/__") || urlPath.startsWith("/@") || urlPath.startsWith("/node_modules") || urlPath.includes(".")) {
1093
1064
  return next();
1094
1065
  }
1095
- const indexPath = path9.join(srcRoot2, "theme/index.html");
1066
+ const indexPath = path8.join(srcRoot2, "theme/index.html");
1096
1067
  if (existsSync5(indexPath)) {
1097
1068
  server.transformIndexHtml(req.url, readFileSync4(indexPath, "utf-8")).then((html) => {
1098
1069
  res.setHeader("Content-Type", "text/html");
@@ -1110,8 +1081,8 @@ async function createViteConfig(options) {
1110
1081
  resolveId(id) {
1111
1082
  if (id.startsWith("/_preview/")) {
1112
1083
  const relativePath = id.slice("/_preview/".length);
1113
- const previewsDir = path9.join(rootDir, "previews");
1114
- const resolved = path9.resolve(previewsDir, relativePath);
1084
+ const previewsDir = path8.join(rootDir, "previews");
1085
+ const resolved = path8.resolve(previewsDir, relativePath);
1115
1086
  if (resolved.startsWith(previewsDir)) {
1116
1087
  return resolved;
1117
1088
  }
@@ -1121,7 +1092,7 @@ async function createViteConfig(options) {
1121
1092
  server.middlewares.use(async (req, res, next) => {
1122
1093
  const urlPath = req.url?.split("?")[0] || "";
1123
1094
  if (urlPath === "/_preview-runtime") {
1124
- const templatePath = path9.join(srcRoot2, "preview-runtime/template.html");
1095
+ const templatePath = path8.join(srcRoot2, "preview-runtime/template.html");
1125
1096
  if (existsSync5(templatePath)) {
1126
1097
  const html = readFileSync4(templatePath, "utf-8");
1127
1098
  res.setHeader("Content-Type", "text/html");
@@ -1131,8 +1102,8 @@ async function createViteConfig(options) {
1131
1102
  }
1132
1103
  if (urlPath.startsWith("/_preview-config/")) {
1133
1104
  const previewName = decodeURIComponent(urlPath.slice("/_preview-config/".length));
1134
- const previewsDir = path9.join(rootDir, "previews");
1135
- const previewDir = path9.resolve(previewsDir, previewName);
1105
+ const previewsDir = path8.join(rootDir, "previews");
1106
+ const previewDir = path8.resolve(previewsDir, previewName);
1136
1107
  if (!previewDir.startsWith(previewsDir)) {
1137
1108
  res.statusCode = 403;
1138
1109
  res.end("Forbidden");
@@ -1153,11 +1124,11 @@ async function createViteConfig(options) {
1153
1124
  }
1154
1125
  }
1155
1126
  if (urlPath.startsWith("/_preview/")) {
1156
- const isHtmlRequest = !path9.extname(urlPath) || urlPath.endsWith("/");
1127
+ const isHtmlRequest = !path8.extname(urlPath) || urlPath.endsWith("/");
1157
1128
  if (isHtmlRequest) {
1158
1129
  const previewName = decodeURIComponent(urlPath.slice("/_preview/".length).replace(/\/$/, ""));
1159
- const previewsDir = path9.join(rootDir, "previews");
1160
- const htmlPath = path9.resolve(previewsDir, previewName, "index.html");
1130
+ const previewsDir = path8.join(rootDir, "previews");
1131
+ const htmlPath = path8.resolve(previewsDir, previewName, "index.html");
1161
1132
  if (!htmlPath.startsWith(previewsDir)) {
1162
1133
  return next();
1163
1134
  }
@@ -1184,15 +1155,15 @@ async function createViteConfig(options) {
1184
1155
  ],
1185
1156
  resolve: {
1186
1157
  alias: {
1187
- "@prev/ui": path9.join(srcRoot2, "ui"),
1188
- "@prev/theme": path9.join(srcRoot2, "theme"),
1189
- react: path9.join(cliNodeModules, "react"),
1190
- "react-dom": path9.join(cliNodeModules, "react-dom"),
1191
- "@tanstack/react-router": path9.join(cliNodeModules, "@tanstack/react-router"),
1192
- "@mdx-js/react": path9.join(cliNodeModules, "@mdx-js/react"),
1193
- mermaid: path9.join(cliNodeModules, "mermaid"),
1194
- dayjs: path9.join(cliNodeModules, "dayjs"),
1195
- "@terrastruct/d2": path9.join(cliNodeModules, "@terrastruct/d2")
1158
+ "@prev/ui": path8.join(srcRoot2, "ui"),
1159
+ "@prev/theme": path8.join(srcRoot2, "theme"),
1160
+ react: path8.join(cliNodeModules, "react"),
1161
+ "react-dom": path8.join(cliNodeModules, "react-dom"),
1162
+ "@tanstack/react-router": path8.join(cliNodeModules, "@tanstack/react-router"),
1163
+ "@mdx-js/react": path8.join(cliNodeModules, "@mdx-js/react"),
1164
+ mermaid: path8.join(cliNodeModules, "mermaid"),
1165
+ dayjs: path8.join(cliNodeModules, "dayjs"),
1166
+ "@terrastruct/d2": path8.join(cliNodeModules, "@terrastruct/d2")
1196
1167
  },
1197
1168
  dedupe: [
1198
1169
  "react",
@@ -1202,16 +1173,11 @@ async function createViteConfig(options) {
1202
1173
  },
1203
1174
  optimizeDeps: {
1204
1175
  noDiscovery: true,
1176
+ holdUntilCrawlEnd: false,
1205
1177
  include: [
1206
- "react",
1207
- "react-dom",
1208
1178
  "react-dom/client",
1209
- "react/jsx-runtime",
1210
- "react/jsx-dev-runtime",
1211
- "@tanstack/react-router",
1212
1179
  "use-sync-external-store",
1213
- "use-sync-external-store/shim/with-selector.js",
1214
- "@mdx-js/react"
1180
+ "use-sync-external-store/shim/with-selector.js"
1215
1181
  ],
1216
1182
  exclude: [
1217
1183
  "virtual:prev-config",
@@ -1232,8 +1198,8 @@ async function createViteConfig(options) {
1232
1198
  },
1233
1199
  warmup: {
1234
1200
  clientFiles: [
1235
- path9.join(srcRoot2, "theme/entry.tsx"),
1236
- path9.join(srcRoot2, "theme/styles.css")
1201
+ path8.join(srcRoot2, "theme/entry.tsx"),
1202
+ path8.join(srcRoot2, "theme/styles.css")
1237
1203
  ]
1238
1204
  }
1239
1205
  },
@@ -1242,12 +1208,12 @@ async function createViteConfig(options) {
1242
1208
  strictPort: false
1243
1209
  },
1244
1210
  build: {
1245
- outDir: path9.join(rootDir, "dist"),
1211
+ outDir: path8.join(rootDir, "dist"),
1246
1212
  reportCompressedSize: false,
1247
1213
  chunkSizeWarningLimit: 1e4,
1248
1214
  rollupOptions: {
1249
1215
  input: {
1250
- main: path9.join(srcRoot2, "theme/index.html")
1216
+ main: path8.join(srcRoot2, "theme/index.html")
1251
1217
  }
1252
1218
  }
1253
1219
  },
@@ -1290,9 +1256,9 @@ async function findAvailablePort(minPort, maxPort) {
1290
1256
  }
1291
1257
 
1292
1258
  // src/vite/start.ts
1293
- import { exec as exec2 } from "child_process";
1259
+ import { exec } from "child_process";
1294
1260
  import { existsSync as existsSync6, rmSync as rmSync2, copyFileSync } from "fs";
1295
- import path10 from "path";
1261
+ import path9 from "path";
1296
1262
  function printWelcome(type) {
1297
1263
  console.log();
1298
1264
  console.log(" ✨ prev");
@@ -1321,12 +1287,12 @@ function printReady() {
1321
1287
  function openBrowser(url) {
1322
1288
  const platform = process.platform;
1323
1289
  const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
1324
- exec2(`${cmd} ${url}`);
1290
+ exec(`${cmd} ${url}`);
1325
1291
  console.log(` ↗ Opened ${url}`);
1326
1292
  }
1327
1293
  function clearCache(rootDir) {
1328
- const viteCacheDir = path10.join(rootDir, ".vite");
1329
- const nodeModulesVite = path10.join(rootDir, "node_modules", ".vite");
1294
+ const viteCacheDir = path9.join(rootDir, ".vite");
1295
+ const nodeModulesVite = path9.join(rootDir, "node_modules", ".vite");
1330
1296
  let cleared = 0;
1331
1297
  if (existsSync6(viteCacheDir)) {
1332
1298
  rmSync2(viteCacheDir, { recursive: true });
@@ -1385,6 +1351,8 @@ async function startDev(rootDir, options = {}) {
1385
1351
  });
1386
1352
  const server = await createServer2(config);
1387
1353
  await server.listen();
1354
+ const warmupPort = server.config.server.port;
1355
+ fetch(`http://localhost:${warmupPort}/`).catch(() => {});
1388
1356
  const debugCollector = getDebugCollector();
1389
1357
  if (debugCollector) {
1390
1358
  debugCollector.startPhase("serverReady");
@@ -1449,9 +1417,9 @@ async function buildSite(rootDir, options = {}) {
1449
1417
  const reportPath = debugCollector.writeReport();
1450
1418
  console.log(` \uD83D\uDCCA Debug trace written to: ${reportPath}`);
1451
1419
  }
1452
- const distDir = path10.join(rootDir, "dist");
1453
- const indexPath = path10.join(distDir, "index.html");
1454
- const notFoundPath = path10.join(distDir, "404.html");
1420
+ const distDir = path9.join(rootDir, "dist");
1421
+ const indexPath = path9.join(distDir, "index.html");
1422
+ const notFoundPath = path9.join(distDir, "404.html");
1455
1423
  if (existsSync6(indexPath)) {
1456
1424
  copyFileSync(indexPath, notFoundPath);
1457
1425
  }
@@ -1484,6 +1452,52 @@ async function previewSite(rootDir, options = {}) {
1484
1452
  return server;
1485
1453
  }
1486
1454
 
1455
+ // src/utils/cache.ts
1456
+ import { createHash } from "crypto";
1457
+ import { readdir, rm, stat, mkdir } from "fs/promises";
1458
+ import { exec as exec2 } from "child_process";
1459
+ import { promisify } from "util";
1460
+ import path10 from "path";
1461
+ import os2 from "os";
1462
+ var execAsync = promisify(exec2);
1463
+ var DEFAULT_CACHE_ROOT = path10.join(os2.homedir(), ".cache/prev");
1464
+ async function getCacheDir(rootDir, branch) {
1465
+ const resolvedBranch = branch ?? await getCurrentBranch(rootDir);
1466
+ const hash = createHash("sha1").update(`${rootDir}:${resolvedBranch}`).digest("hex").slice(0, 12);
1467
+ return path10.join(DEFAULT_CACHE_ROOT, hash);
1468
+ }
1469
+ async function getCurrentBranch(rootDir) {
1470
+ try {
1471
+ const { stdout } = await execAsync("git branch --show-current", { cwd: rootDir });
1472
+ return stdout.trim() || "detached";
1473
+ } catch {
1474
+ return "no-git";
1475
+ }
1476
+ }
1477
+ async function cleanCache(options) {
1478
+ const cacheRoot = options.cacheRoot ?? DEFAULT_CACHE_ROOT;
1479
+ const maxAge = options.maxAgeDays * 24 * 60 * 60 * 1000;
1480
+ const now = Date.now();
1481
+ let dirs;
1482
+ try {
1483
+ dirs = await readdir(cacheRoot);
1484
+ } catch {
1485
+ return 0;
1486
+ }
1487
+ let removed = 0;
1488
+ for (const dir of dirs) {
1489
+ const fullPath = path10.join(cacheRoot, dir);
1490
+ try {
1491
+ const info = await stat(fullPath);
1492
+ if (info.isDirectory() && now - info.mtimeMs > maxAge) {
1493
+ await rm(fullPath, { recursive: true });
1494
+ removed++;
1495
+ }
1496
+ } catch {}
1497
+ }
1498
+ return removed;
1499
+ }
1500
+
1487
1501
  // src/cli.ts
1488
1502
  import yaml2 from "js-yaml";
1489
1503
  function getVersion() {
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import { type VariantProps } from 'class-variance-authority';
3
3
  declare const buttonVariants: (props?: ({
4
- variant?: "default" | "link" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
4
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
5
5
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prev-cli",
3
- "version": "0.24.6",
3
+ "version": "0.24.8",
4
4
  "description": "Transform MDX directories into beautiful documentation websites",
5
5
  "type": "module",
6
6
  "license": "MIT",