prev-cli 0.16.3 → 0.17.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
@@ -859,6 +859,28 @@ async function createViteConfig(options) {
859
859
  });
860
860
  }
861
861
  },
862
+ {
863
+ name: "prev-spa-fallback",
864
+ configureServer(server) {
865
+ return () => {
866
+ server.middlewares.use((req, res, next) => {
867
+ const urlPath = req.url?.split("?")[0] || "";
868
+ if (urlPath.startsWith("/__") || urlPath.startsWith("/@") || urlPath.startsWith("/node_modules") || urlPath.includes(".")) {
869
+ return next();
870
+ }
871
+ const indexPath = path7.join(srcRoot2, "theme/index.html");
872
+ if (existsSync5(indexPath)) {
873
+ server.transformIndexHtml(req.url, readFileSync4(indexPath, "utf-8")).then((html) => {
874
+ res.setHeader("Content-Type", "text/html");
875
+ res.end(html);
876
+ }).catch(next);
877
+ return;
878
+ }
879
+ next();
880
+ });
881
+ };
882
+ }
883
+ },
862
884
  {
863
885
  name: "prev-preview-server",
864
886
  resolveId(id) {
@@ -1171,6 +1193,7 @@ async function previewSite(rootDir, options = {}) {
1171
1193
  }
1172
1194
 
1173
1195
  // src/cli.ts
1196
+ import yaml2 from "js-yaml";
1174
1197
  function getVersion() {
1175
1198
  try {
1176
1199
  let dir = path9.dirname(fileURLToPath3(import.meta.url));
@@ -1209,9 +1232,16 @@ Usage:
1209
1232
  prev build [options] Build for production
1210
1233
  prev preview [options] Preview production build
1211
1234
  prev create [name] Create preview in previews/<name>/ (default: "example")
1235
+ prev config [subcommand] Manage configuration
1212
1236
  prev clearcache Clear Vite cache (.vite directory)
1213
1237
  prev clean [options] Remove old prev-cli caches
1214
1238
 
1239
+ Config subcommands:
1240
+ prev config Show current configuration
1241
+ prev config show Show current configuration (same as above)
1242
+ prev config init Create .prev.yaml with defaults
1243
+ prev config path Show config file path
1244
+
1215
1245
  Options:
1216
1246
  -c, --cwd <path> Set working directory
1217
1247
  -p, --port <port> Specify port (dev/preview)
@@ -1318,6 +1348,92 @@ async function clearViteCache(rootDir2) {
1318
1348
  Cleared ${cleared} cache director${cleared === 1 ? "y" : "ies"}`);
1319
1349
  }
1320
1350
  }
1351
+ function handleConfig(rootDir2, subcommand) {
1352
+ const configPath = findConfigFile(rootDir2);
1353
+ const config = loadConfig(rootDir2);
1354
+ switch (subcommand) {
1355
+ case undefined:
1356
+ case "show": {
1357
+ console.log(`
1358
+ \uD83D\uDCC4 Configuration
1359
+ `);
1360
+ if (configPath) {
1361
+ console.log(` File: ${configPath}
1362
+ `);
1363
+ } else {
1364
+ console.log(` File: (none - using defaults)
1365
+ `);
1366
+ }
1367
+ const yamlOutput = yaml2.dump(config, {
1368
+ indent: 2,
1369
+ lineWidth: -1,
1370
+ quotingType: '"',
1371
+ forceQuotes: false
1372
+ });
1373
+ const indented = yamlOutput.split(`
1374
+ `).map((line) => ` ${line}`).join(`
1375
+ `);
1376
+ console.log(indented);
1377
+ if (!configPath) {
1378
+ console.log(`
1379
+ Run 'prev config init' to create a config file.
1380
+ `);
1381
+ }
1382
+ break;
1383
+ }
1384
+ case "init": {
1385
+ const targetPath = path9.join(rootDir2, ".prev.yaml");
1386
+ if (configPath) {
1387
+ console.log(`
1388
+ Config already exists: ${configPath}
1389
+ `);
1390
+ process.exit(1);
1391
+ }
1392
+ const configContent = `# prev-cli configuration
1393
+ # See: https://github.com/lagz0ne/prev-cli
1394
+
1395
+ # Theme: light | dark | system
1396
+ theme: system
1397
+
1398
+ # Content width: constrained | full
1399
+ contentWidth: constrained
1400
+
1401
+ # Port for dev server (optional, can be overridden with -p flag)
1402
+ # port: 3000
1403
+
1404
+ # Hidden pages (glob patterns)
1405
+ hidden: []
1406
+ # - "internal/**"
1407
+ # - "wip-*.md"
1408
+
1409
+ # Custom page ordering per directory
1410
+ order: {}
1411
+ # "/":
1412
+ # - "getting-started.md"
1413
+ # - "guides/"
1414
+ `;
1415
+ writeFileSync4(targetPath, configContent, "utf-8");
1416
+ console.log(`
1417
+ ✨ Created ${targetPath}
1418
+ `);
1419
+ break;
1420
+ }
1421
+ case "path": {
1422
+ if (configPath) {
1423
+ console.log(configPath);
1424
+ } else {
1425
+ console.log(`(no config file found in ${rootDir2})`);
1426
+ process.exit(1);
1427
+ }
1428
+ break;
1429
+ }
1430
+ default:
1431
+ console.error(`Unknown config subcommand: ${subcommand}`);
1432
+ console.log(`
1433
+ Available subcommands: show, init, path`);
1434
+ process.exit(1);
1435
+ }
1436
+ }
1321
1437
  function createPreview(rootDir2, name) {
1322
1438
  const previewDir = path9.join(rootDir2, "previews", name);
1323
1439
  if (existsSync7(previewDir)) {
@@ -1497,6 +1613,9 @@ async function main() {
1497
1613
  case "clearcache":
1498
1614
  await clearViteCache(rootDir);
1499
1615
  break;
1616
+ case "config":
1617
+ handleConfig(rootDir, positionals[1]);
1618
+ break;
1500
1619
  default:
1501
1620
  console.error(`Unknown command: ${command}`);
1502
1621
  printHelp();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prev-cli",
3
- "version": "0.16.3",
3
+ "version": "0.17.0",
4
4
  "description": "Transform MDX directories into beautiful documentation websites",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -32,8 +32,8 @@ export function Preview({ src, height = 400, title, mode = 'wasm', showHeader =
32
32
 
33
33
  const iframeRef = useRef<HTMLIFrameElement>(null)
34
34
 
35
- // URL depends on mode
36
- const previewUrl = mode === 'wasm' ? '/_preview-runtime' : `/_preview/${src}`
35
+ // URL depends on mode - wasm mode needs src param
36
+ const previewUrl = mode === 'wasm' ? `/_preview-runtime?src=${src}` : `/_preview/${src}`
37
37
  const displayTitle = title || src
38
38
 
39
39
  // Calculate current width
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useRef, useEffect } from 'react'
2
- import { Link } from '@tanstack/react-router'
2
+ import { Link, useLocation } from '@tanstack/react-router'
3
3
  import type { PageTree } from 'fumadocs-core/server'
4
4
  import { previews } from 'virtual:prev-previews'
5
5
  import { IconMenu2, IconLayoutGrid, IconSun, IconMoon, IconArrowsMaximize, IconArrowsMinimize } from '@tabler/icons-react'
@@ -20,6 +20,8 @@ export function Toolbar({ tree, onThemeToggle, onWidthToggle, isDark, isFullWidt
20
20
  const [dragging, setDragging] = useState(false)
21
21
  const dragStart = useRef({ x: 0, y: 0 })
22
22
  const toolbarRef = useRef<HTMLDivElement>(null)
23
+ const location = useLocation()
24
+ const isOnPreviews = location.pathname.startsWith('/previews')
23
25
 
24
26
  const handleMouseDown = (e: React.MouseEvent) => {
25
27
  if ((e.target as HTMLElement).closest('button, a')) return
@@ -63,7 +65,7 @@ export function Toolbar({ tree, onThemeToggle, onWidthToggle, isDark, isFullWidt
63
65
  </button>
64
66
 
65
67
  {previews && previews.length > 0 && (
66
- <Link to="/previews" className="toolbar-btn" title="Previews">
68
+ <Link to="/previews" className={`toolbar-btn ${isOnPreviews ? 'active' : ''}`} title="Previews">
67
69
  <IconLayoutGrid size={18} />
68
70
  </Link>
69
71
  )}
@@ -6,6 +6,8 @@ import {
6
6
  createRootRoute,
7
7
  createRoute,
8
8
  Outlet,
9
+ redirect,
10
+ Navigate,
9
11
  } from '@tanstack/react-router'
10
12
  import { MDXProvider } from '@mdx-js/react'
11
13
  import { pages, sidebar } from 'virtual:prev-pages'
@@ -257,6 +259,10 @@ const previewDetailRoute = createRoute({
257
259
  component: PreviewPage,
258
260
  })
259
261
 
262
+ // Check if we have an index page (route '/')
263
+ const hasIndexPage = pages.some((page: { route: string }) => page.route === '/')
264
+ const firstPage = pages[0] as { route: string; file: string; title?: string; description?: string; frontmatter?: Record<string, unknown> } | undefined
265
+
260
266
  // Create routes from pages
261
267
  const pageRoutes = pages.map((page: { route: string; file: string; title?: string; description?: string; frontmatter?: Record<string, unknown> }) => {
262
268
  const Component = getPageComponent(page.file)
@@ -272,9 +278,30 @@ const pageRoutes = pages.map((page: { route: string; file: string; title?: strin
272
278
  })
273
279
  })
274
280
 
275
- // Create router
276
- const routeTree = rootRoute.addChildren([previewsRoute, previewDetailRoute, ...pageRoutes])
277
- const router = createRouter({ routeTree })
281
+ // If no index page exists, create a redirect from '/' to the first page
282
+ const indexRedirectRoute = !hasIndexPage && firstPage ? createRoute({
283
+ getParentRoute: () => rootRoute,
284
+ path: '/',
285
+ component: () => <Navigate to={firstPage.route} />,
286
+ }) : null
287
+
288
+ // Not found component - redirect to first page or index
289
+ function NotFoundPage() {
290
+ const targetRoute = firstPage?.route || '/'
291
+ return <Navigate to={targetRoute} />
292
+ }
293
+
294
+ // Create router with notFoundRoute
295
+ const routeTree = rootRoute.addChildren([
296
+ previewsRoute,
297
+ previewDetailRoute,
298
+ ...(indexRedirectRoute ? [indexRedirectRoute] : []),
299
+ ...pageRoutes,
300
+ ])
301
+ const router = createRouter({
302
+ routeTree,
303
+ defaultNotFoundComponent: NotFoundPage,
304
+ })
278
305
 
279
306
  // Mount app - RouterProvider must be outermost so TanStack Router context is available
280
307
  const container = document.getElementById('root')