prev-cli 0.3.3 → 0.4.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 CHANGED
@@ -12,9 +12,9 @@ import react from "@vitejs/plugin-react-swc";
12
12
  import mdx from "@mdx-js/rollup";
13
13
  import remarkGfm from "remark-gfm";
14
14
  import rehypeHighlight from "rehype-highlight";
15
- import path5 from "path";
15
+ import path4 from "path";
16
16
  import { fileURLToPath as fileURLToPath2 } from "url";
17
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
17
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
18
18
 
19
19
  // src/utils/cache.ts
20
20
  import { createHash } from "crypto";
@@ -268,72 +268,6 @@ function entryPlugin(rootDir) {
268
268
  };
269
269
  }
270
270
 
271
- // src/vite/plugins/fumadocs-plugin.ts
272
- import path4 from "path";
273
- import { existsSync as existsSync2 } from "fs";
274
- function fumadocsPlugin(nodeModulesPath) {
275
- const fumadocsCorePath = path4.join(nodeModulesPath, "fumadocs-core");
276
- const fumadocsUiPath = path4.join(nodeModulesPath, "fumadocs-ui");
277
- const fumadocsUiScopedPath = path4.join(nodeModulesPath, "@fumadocs/ui");
278
- function resolveSubpath(pkgPath, subpath) {
279
- const directPath = path4.join(pkgPath, "dist", `${subpath}.js`);
280
- if (existsSync2(directPath)) {
281
- return directPath;
282
- }
283
- const indexPath = path4.join(pkgPath, "dist", subpath, "index.js");
284
- if (existsSync2(indexPath)) {
285
- return indexPath;
286
- }
287
- const rawPath = path4.join(pkgPath, "dist", subpath);
288
- if (existsSync2(rawPath)) {
289
- return rawPath;
290
- }
291
- return null;
292
- }
293
- return {
294
- name: "fumadocs-resolver",
295
- enforce: "pre",
296
- resolveId(id, importer) {
297
- if (importer && (id.startsWith("./") || id.startsWith("../"))) {
298
- if (importer.includes("fumadocs-core") || importer.includes("fumadocs-ui") || importer.includes("@fumadocs")) {
299
- const importerDir = path4.dirname(importer);
300
- const resolved = path4.resolve(importerDir, id);
301
- const withExt = resolved.endsWith(".js") ? resolved : `${resolved}.js`;
302
- if (existsSync2(withExt)) {
303
- return withExt;
304
- }
305
- const indexPath = path4.join(resolved, "index.js");
306
- if (existsSync2(indexPath)) {
307
- return indexPath;
308
- }
309
- }
310
- }
311
- if (id.startsWith("fumadocs-core/")) {
312
- const subpath = id.slice("fumadocs-core/".length);
313
- const resolved = resolveSubpath(fumadocsCorePath, subpath);
314
- if (resolved) {
315
- return resolved;
316
- }
317
- }
318
- if (id.startsWith("fumadocs-ui/")) {
319
- const subpath = id.slice("fumadocs-ui/".length);
320
- const resolved = resolveSubpath(fumadocsUiPath, subpath);
321
- if (resolved) {
322
- return resolved;
323
- }
324
- }
325
- if (id.startsWith("@fumadocs/ui/")) {
326
- const subpath = id.slice("@fumadocs/ui/".length);
327
- const resolved = resolveSubpath(fumadocsUiScopedPath, subpath);
328
- if (resolved) {
329
- return resolved;
330
- }
331
- }
332
- return null;
333
- }
334
- };
335
- }
336
-
337
271
  // src/vite/config.ts
338
272
  function createFriendlyLogger() {
339
273
  const logger = createLogger("info", { allowClearScreen: false });
@@ -389,10 +323,10 @@ function createFriendlyLogger() {
389
323
  };
390
324
  }
391
325
  function findCliRoot2() {
392
- let dir = path5.dirname(fileURLToPath2(import.meta.url));
326
+ let dir = path4.dirname(fileURLToPath2(import.meta.url));
393
327
  for (let i = 0;i < 10; i++) {
394
- const pkgPath = path5.join(dir, "package.json");
395
- if (existsSync3(pkgPath)) {
328
+ const pkgPath = path4.join(dir, "package.json");
329
+ if (existsSync2(pkgPath)) {
396
330
  try {
397
331
  const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
398
332
  if (pkg.name === "prev-cli") {
@@ -400,24 +334,24 @@ function findCliRoot2() {
400
334
  }
401
335
  } catch {}
402
336
  }
403
- const parent = path5.dirname(dir);
337
+ const parent = path4.dirname(dir);
404
338
  if (parent === dir)
405
339
  break;
406
340
  dir = parent;
407
341
  }
408
- return path5.dirname(path5.dirname(fileURLToPath2(import.meta.url)));
342
+ return path4.dirname(path4.dirname(fileURLToPath2(import.meta.url)));
409
343
  }
410
344
  function findNodeModules(cliRoot2) {
411
- const localNodeModules = path5.join(cliRoot2, "node_modules");
412
- if (existsSync3(path5.join(localNodeModules, "react"))) {
345
+ const localNodeModules = path4.join(cliRoot2, "node_modules");
346
+ if (existsSync2(path4.join(localNodeModules, "react"))) {
413
347
  return localNodeModules;
414
348
  }
415
349
  let dir = cliRoot2;
416
350
  for (let i = 0;i < 10; i++) {
417
- const parent = path5.dirname(dir);
351
+ const parent = path4.dirname(dir);
418
352
  if (parent === dir)
419
353
  break;
420
- if (path5.basename(parent) === "node_modules" && existsSync3(path5.join(parent, "react"))) {
354
+ if (path4.basename(parent) === "node_modules" && existsSync2(path4.join(parent, "react"))) {
421
355
  return parent;
422
356
  }
423
357
  dir = parent;
@@ -426,7 +360,7 @@ function findNodeModules(cliRoot2) {
426
360
  }
427
361
  var cliRoot2 = findCliRoot2();
428
362
  var cliNodeModules = findNodeModules(cliRoot2);
429
- var srcRoot2 = path5.join(cliRoot2, "src");
363
+ var srcRoot2 = path4.join(cliRoot2, "src");
430
364
  async function createViteConfig(options) {
431
365
  const { rootDir, mode, port } = options;
432
366
  const cacheDir = await ensureCacheDir(rootDir);
@@ -437,7 +371,6 @@ async function createViteConfig(options) {
437
371
  customLogger: createFriendlyLogger(),
438
372
  logLevel: mode === "production" ? "silent" : "info",
439
373
  plugins: [
440
- fumadocsPlugin(cliNodeModules),
441
374
  mdx({
442
375
  remarkPlugins: [remarkGfm],
443
376
  rehypePlugins: [rehypeHighlight]
@@ -448,22 +381,19 @@ async function createViteConfig(options) {
448
381
  ],
449
382
  resolve: {
450
383
  alias: {
451
- "@prev/ui": path5.join(srcRoot2, "ui"),
452
- "@prev/theme": path5.join(srcRoot2, "theme"),
453
- react: path5.join(cliNodeModules, "react"),
454
- "react-dom": path5.join(cliNodeModules, "react-dom"),
455
- "@tanstack/react-router": path5.join(cliNodeModules, "@tanstack/react-router"),
456
- mermaid: path5.join(cliNodeModules, "mermaid"),
457
- dayjs: path5.join(cliNodeModules, "dayjs"),
458
- "@terrastruct/d2": path5.join(cliNodeModules, "@terrastruct/d2")
384
+ "@prev/ui": path4.join(srcRoot2, "ui"),
385
+ "@prev/theme": path4.join(srcRoot2, "theme"),
386
+ react: path4.join(cliNodeModules, "react"),
387
+ "react-dom": path4.join(cliNodeModules, "react-dom"),
388
+ "@tanstack/react-router": path4.join(cliNodeModules, "@tanstack/react-router"),
389
+ mermaid: path4.join(cliNodeModules, "mermaid"),
390
+ dayjs: path4.join(cliNodeModules, "dayjs"),
391
+ "@terrastruct/d2": path4.join(cliNodeModules, "@terrastruct/d2")
459
392
  },
460
393
  dedupe: [
461
394
  "react",
462
395
  "react-dom",
463
- "@tanstack/react-router",
464
- "fumadocs-core",
465
- "fumadocs-ui",
466
- "@fumadocs/ui"
396
+ "@tanstack/react-router"
467
397
  ]
468
398
  },
469
399
  optimizeDeps: {
@@ -478,11 +408,6 @@ async function createViteConfig(options) {
478
408
  "mermaid",
479
409
  "dayjs",
480
410
  "@terrastruct/d2"
481
- ],
482
- exclude: [
483
- "fumadocs-core",
484
- "fumadocs-ui",
485
- "@fumadocs/ui"
486
411
  ]
487
412
  },
488
413
  ssr: {
@@ -493,6 +418,12 @@ async function createViteConfig(options) {
493
418
  strictPort: false,
494
419
  fs: {
495
420
  allow: [rootDir, cliRoot2]
421
+ },
422
+ warmup: {
423
+ clientFiles: [
424
+ path4.join(srcRoot2, "theme/entry.tsx"),
425
+ path4.join(srcRoot2, "theme/styles.css")
426
+ ]
496
427
  }
497
428
  },
498
429
  preview: {
@@ -500,7 +431,7 @@ async function createViteConfig(options) {
500
431
  strictPort: false
501
432
  },
502
433
  build: {
503
- outDir: path5.join(rootDir, "dist"),
434
+ outDir: path4.join(rootDir, "dist"),
504
435
  reportCompressedSize: false,
505
436
  chunkSizeWarningLimit: 1e4
506
437
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prev-cli",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "Transform MDX directories into beautiful documentation websites",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,116 @@
1
+ import React, { useState } from 'react'
2
+ import { Link, useLocation } from '@tanstack/react-router'
3
+ import type { PageTree } from 'fumadocs-core/server'
4
+
5
+ interface LayoutProps {
6
+ tree: PageTree.Root
7
+ children: React.ReactNode
8
+ }
9
+
10
+ interface SidebarItemProps {
11
+ item: PageTree.Item | PageTree.Folder
12
+ depth?: number
13
+ }
14
+
15
+ function SidebarItem({ item, depth = 0 }: SidebarItemProps) {
16
+ const location = useLocation()
17
+ const [isOpen, setIsOpen] = useState(true)
18
+
19
+ if (item.type === 'folder') {
20
+ return (
21
+ <div className="sidebar-folder">
22
+ <button
23
+ className="sidebar-folder-toggle"
24
+ onClick={() => setIsOpen(!isOpen)}
25
+ style={{ paddingLeft: `${depth * 12 + 8}px` }}
26
+ >
27
+ <span className={`folder-icon ${isOpen ? 'open' : ''}`}>
28
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
29
+ <path d="M4.5 2L8.5 6L4.5 10" stroke="currentColor" strokeWidth="1.5" fill="none"/>
30
+ </svg>
31
+ </span>
32
+ {item.name}
33
+ </button>
34
+ {isOpen && (
35
+ <div className="sidebar-folder-children">
36
+ {item.children.map((child, i) => (
37
+ <SidebarItem key={i} item={child} depth={depth + 1} />
38
+ ))}
39
+ </div>
40
+ )}
41
+ </div>
42
+ )
43
+ }
44
+
45
+ const isActive = location.pathname === item.url ||
46
+ (item.url === '/' && location.pathname === '/')
47
+
48
+ return (
49
+ <Link
50
+ to={item.url}
51
+ className={`sidebar-link ${isActive ? 'active' : ''}`}
52
+ style={{ paddingLeft: `${depth * 12 + 16}px` }}
53
+ >
54
+ {item.name}
55
+ </Link>
56
+ )
57
+ }
58
+
59
+ function ThemeToggle() {
60
+ const [isDark, setIsDark] = useState(() => {
61
+ if (typeof window !== 'undefined') {
62
+ return document.documentElement.classList.contains('dark')
63
+ }
64
+ return false
65
+ })
66
+
67
+ const toggle = () => {
68
+ const newDark = !isDark
69
+ setIsDark(newDark)
70
+ document.documentElement.classList.toggle('dark', newDark)
71
+ localStorage.setItem('theme', newDark ? 'dark' : 'light')
72
+ }
73
+
74
+ return (
75
+ <button className="theme-toggle" onClick={toggle} aria-label="Toggle theme">
76
+ {isDark ? (
77
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
78
+ <circle cx="12" cy="12" r="5"/>
79
+ <path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
80
+ </svg>
81
+ ) : (
82
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
83
+ <path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/>
84
+ </svg>
85
+ )}
86
+ </button>
87
+ )
88
+ }
89
+
90
+ export function Layout({ tree, children }: LayoutProps) {
91
+ // Initialize theme from localStorage
92
+ React.useEffect(() => {
93
+ const saved = localStorage.getItem('theme')
94
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
95
+ const isDark = saved === 'dark' || (!saved && prefersDark)
96
+ document.documentElement.classList.toggle('dark', isDark)
97
+ }, [])
98
+
99
+ return (
100
+ <div className="prev-layout">
101
+ <aside className="prev-sidebar">
102
+ <nav className="sidebar-nav">
103
+ {tree.children.map((item, i) => (
104
+ <SidebarItem key={i} item={item} />
105
+ ))}
106
+ </nav>
107
+ <div className="sidebar-footer">
108
+ <ThemeToggle />
109
+ </div>
110
+ </aside>
111
+ <main className="prev-main">
112
+ {children}
113
+ </main>
114
+ </div>
115
+ )
116
+ }
@@ -6,16 +6,30 @@ import {
6
6
  createRootRoute,
7
7
  createRoute,
8
8
  Outlet,
9
- Link,
10
9
  } from '@tanstack/react-router'
11
- import { TanstackProvider } from 'fumadocs-core/framework/tanstack'
12
- import { DocsLayout } from 'fumadocs-ui/layouts/docs'
13
- import type { PageTree } from 'fumadocs-core/server'
14
10
  import { pages, sidebar } from 'virtual:prev-pages'
15
11
  import { useDiagrams } from './diagrams'
16
- import 'fumadocs-ui/style.css'
12
+ import { Layout } from './Layout'
17
13
  import './styles.css'
18
14
 
15
+ // PageTree types (simplified from fumadocs-core)
16
+ namespace PageTree {
17
+ export interface Item {
18
+ type: 'page'
19
+ name: string
20
+ url: string
21
+ }
22
+ export interface Folder {
23
+ type: 'folder'
24
+ name: string
25
+ children: (Item | Folder)[]
26
+ }
27
+ export interface Root {
28
+ name: string
29
+ children: (Item | Folder)[]
30
+ }
31
+ }
32
+
19
33
  // Convert prev-cli sidebar to Fumadocs PageTree format
20
34
  function convertToPageTree(items: any[]): PageTree.Root {
21
35
  function convertItem(item: any): PageTree.Item | PageTree.Folder {
@@ -53,21 +67,16 @@ function PageWrapper({ Component }: { Component: React.ComponentType }) {
53
67
  return <Component />
54
68
  }
55
69
 
56
- // Root layout with Fumadocs DocsLayout - TanstackProvider wraps inside router
70
+ // Root layout with custom lightweight Layout
57
71
  function RootLayout() {
58
72
  const pageTree = convertToPageTree(sidebar)
59
73
 
60
74
  return (
61
- <TanstackProvider>
62
- <DocsLayout
63
- tree={pageTree}
64
- nav={{ enabled: false }}
65
- >
66
- <article className="prev-content">
67
- <Outlet />
68
- </article>
69
- </DocsLayout>
70
- </TanstackProvider>
75
+ <Layout tree={pageTree}>
76
+ <article className="prev-content">
77
+ <Outlet />
78
+ </article>
79
+ </Layout>
71
80
  )
72
81
  }
73
82
 
@@ -1,8 +1,128 @@
1
1
  /*
2
- Prev CLI Theme Customization
3
- Overrides fumadocs-ui's default theme with warm stone palette
2
+ Prev CLI Theme - Lightweight Documentation Layout
4
3
  */
5
4
 
5
+ /* Reset */
6
+ *, *::before, *::after {
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ body {
11
+ margin: 0;
12
+ font-family: var(--fd-font-sans);
13
+ background: var(--fd-background);
14
+ color: var(--fd-foreground);
15
+ }
16
+
17
+ /* Layout structure */
18
+ .prev-layout {
19
+ display: grid;
20
+ grid-template-columns: 260px 1fr;
21
+ min-height: 100vh;
22
+ }
23
+
24
+ /* Sidebar */
25
+ .prev-sidebar {
26
+ position: sticky;
27
+ top: 0;
28
+ height: 100vh;
29
+ background: var(--fd-background);
30
+ border-right: 1px solid var(--fd-border);
31
+ display: flex;
32
+ flex-direction: column;
33
+ overflow: hidden;
34
+ }
35
+
36
+ .sidebar-nav {
37
+ flex: 1;
38
+ overflow-y: auto;
39
+ padding: 1rem 0.5rem;
40
+ }
41
+
42
+ .sidebar-link {
43
+ display: block;
44
+ padding: 0.5rem 1rem;
45
+ color: var(--fd-muted-foreground);
46
+ text-decoration: none;
47
+ font-size: 0.9rem;
48
+ border-radius: 0.375rem;
49
+ transition: all 0.15s ease;
50
+ }
51
+
52
+ .sidebar-link:hover {
53
+ background: var(--fd-muted);
54
+ color: var(--fd-foreground);
55
+ }
56
+
57
+ .sidebar-link.active {
58
+ background: var(--fd-accent);
59
+ color: var(--fd-accent-foreground);
60
+ font-weight: 500;
61
+ }
62
+
63
+ .sidebar-folder-toggle {
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 0.5rem;
67
+ width: 100%;
68
+ padding: 0.5rem 1rem;
69
+ background: none;
70
+ border: none;
71
+ color: var(--fd-foreground);
72
+ font-size: 0.9rem;
73
+ font-weight: 500;
74
+ cursor: pointer;
75
+ border-radius: 0.375rem;
76
+ text-align: left;
77
+ }
78
+
79
+ .sidebar-folder-toggle:hover {
80
+ background: var(--fd-muted);
81
+ }
82
+
83
+ .folder-icon {
84
+ display: flex;
85
+ transition: transform 0.15s ease;
86
+ }
87
+
88
+ .folder-icon.open {
89
+ transform: rotate(90deg);
90
+ }
91
+
92
+ .sidebar-folder-children {
93
+ margin-left: 0.5rem;
94
+ }
95
+
96
+ .sidebar-footer {
97
+ padding: 1rem;
98
+ border-top: 1px solid var(--fd-border);
99
+ }
100
+
101
+ .theme-toggle {
102
+ display: flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ width: 36px;
106
+ height: 36px;
107
+ background: var(--fd-muted);
108
+ border: none;
109
+ border-radius: 0.5rem;
110
+ color: var(--fd-muted-foreground);
111
+ cursor: pointer;
112
+ transition: all 0.15s ease;
113
+ }
114
+
115
+ .theme-toggle:hover {
116
+ background: var(--fd-accent);
117
+ color: var(--fd-accent-foreground);
118
+ }
119
+
120
+ /* Main content area */
121
+ .prev-main {
122
+ padding: 2rem;
123
+ overflow-x: hidden;
124
+ }
125
+
6
126
  /* Custom fonts */
7
127
  :root {
8
128
  --fd-font-sans: "DM Sans", system-ui, sans-serif;
@@ -87,6 +207,10 @@
87
207
  line-height: 1.75;
88
208
  color: var(--fd-foreground);
89
209
  font-size: 1rem;
210
+ padding-left: 2.5rem !important;
211
+ padding-right: 1.5rem !important;
212
+ padding-top: 0.5rem !important;
213
+ padding-bottom: 3rem !important;
90
214
  }
91
215
 
92
216
  /* Heading styles with better spacing */