prev-cli 0.24.11 → 0.24.12

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
@@ -3,7 +3,7 @@
3
3
  // src/cli.ts
4
4
  import { parseArgs } from "util";
5
5
  import path11 from "path";
6
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, rmSync as rmSync3, readFileSync as readFileSync5 } from "fs";
6
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5, rmSync as rmSync3, readFileSync as readFileSync6 } from "fs";
7
7
  import { fileURLToPath as fileURLToPath3 } from "url";
8
8
 
9
9
  // src/vite/start.ts
@@ -18,7 +18,7 @@ import rehypeHighlight from "rehype-highlight";
18
18
  import path8 from "path";
19
19
  import os from "os";
20
20
  import { fileURLToPath as fileURLToPath2 } from "url";
21
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
21
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
22
22
 
23
23
  // src/vite/plugins/pages-plugin.ts
24
24
  import path2 from "path";
@@ -402,10 +402,89 @@ function entryPlugin(rootDir) {
402
402
  // src/vite/previews.ts
403
403
  import fg2 from "fast-glob";
404
404
  import path4 from "path";
405
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
406
+
407
+ // src/vite/config-parser.ts
405
408
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
409
+ import * as yaml from "js-yaml";
410
+
411
+ // src/vite/preview-types.ts
412
+ import { z } from "zod";
413
+ var configSchema = z.object({
414
+ tags: z.union([
415
+ z.array(z.string()),
416
+ z.string().transform((s) => [s])
417
+ ]).optional(),
418
+ category: z.string().optional(),
419
+ status: z.enum(["draft", "stable", "deprecated"]).optional(),
420
+ title: z.string().optional(),
421
+ description: z.string().optional(),
422
+ order: z.number().optional()
423
+ });
424
+
425
+ // src/vite/config-parser.ts
426
+ async function parsePreviewConfig(filePath) {
427
+ if (!existsSync2(filePath)) {
428
+ return null;
429
+ }
430
+ try {
431
+ const content = readFileSync2(filePath, "utf-8");
432
+ const parsed = yaml.load(content);
433
+ const result = configSchema.safeParse(parsed);
434
+ if (result.success) {
435
+ return result.data;
436
+ }
437
+ console.warn(`Invalid config at ${filePath}:`, result.error.message);
438
+ return null;
439
+ } catch (err) {
440
+ console.warn(`Error parsing config at ${filePath}:`, err);
441
+ return null;
442
+ }
443
+ }
444
+ async function parseFlowDefinition(filePath) {
445
+ if (!existsSync2(filePath)) {
446
+ return null;
447
+ }
448
+ try {
449
+ const content = readFileSync2(filePath, "utf-8");
450
+ const parsed = yaml.load(content);
451
+ if (!parsed.name || !Array.isArray(parsed.steps)) {
452
+ return null;
453
+ }
454
+ return parsed;
455
+ } catch (err) {
456
+ console.warn(`Error parsing flow at ${filePath}:`, err);
457
+ return null;
458
+ }
459
+ }
460
+ async function parseAtlasDefinition(filePath) {
461
+ if (!existsSync2(filePath)) {
462
+ return null;
463
+ }
464
+ try {
465
+ const content = readFileSync2(filePath, "utf-8");
466
+ const parsed = yaml.load(content);
467
+ if (!parsed.name || !parsed.hierarchy?.root || !parsed.hierarchy?.areas) {
468
+ return null;
469
+ }
470
+ return parsed;
471
+ } catch (err) {
472
+ console.warn(`Error parsing atlas at ${filePath}:`, err);
473
+ return null;
474
+ }
475
+ }
476
+
477
+ // src/vite/previews.ts
478
+ var PREVIEW_TYPE_FOLDERS = ["components", "screens", "flows", "atlas"];
479
+ var TYPE_MAP = {
480
+ components: "component",
481
+ screens: "screen",
482
+ flows: "flow",
483
+ atlas: "atlas"
484
+ };
406
485
  async function scanPreviews(rootDir) {
407
486
  const previewsDir = path4.join(rootDir, "previews");
408
- if (!existsSync2(previewsDir)) {
487
+ if (!existsSync3(previewsDir)) {
409
488
  return [];
410
489
  }
411
490
  const entryFiles = await fg2.glob("**/{index.html,App.tsx,App.jsx,index.tsx,index.jsx}", {
@@ -434,7 +513,7 @@ async function scanPreviewFiles(previewDir) {
434
513
  ignore: ["node_modules/**", "dist/**"]
435
514
  });
436
515
  return files.map((file) => {
437
- const content = readFileSync2(path4.join(previewDir, file), "utf-8");
516
+ const content = readFileSync3(path4.join(previewDir, file), "utf-8");
438
517
  const ext = path4.extname(file).slice(1);
439
518
  return {
440
519
  path: file,
@@ -462,6 +541,78 @@ async function buildPreviewConfig(previewDir) {
462
541
  tailwind: true
463
542
  };
464
543
  }
544
+ async function scanPreviewUnits(rootDir) {
545
+ const previewsDir = path4.join(rootDir, "previews");
546
+ if (!existsSync3(previewsDir)) {
547
+ return [];
548
+ }
549
+ const units = [];
550
+ for (const typeFolder of PREVIEW_TYPE_FOLDERS) {
551
+ const typeDir = path4.join(previewsDir, typeFolder);
552
+ if (!existsSync3(typeDir))
553
+ continue;
554
+ const type = TYPE_MAP[typeFolder];
555
+ const entries = await fg2.glob("*/", {
556
+ cwd: typeDir,
557
+ onlyDirectories: true,
558
+ deep: 1
559
+ });
560
+ for (const entry of entries) {
561
+ const name = entry.replace(/\/$/, "");
562
+ const unitDir = path4.join(typeDir, name);
563
+ const files = await detectUnitFiles(unitDir, type);
564
+ if (!files.index)
565
+ continue;
566
+ const configPath = existsSync3(path4.join(unitDir, "config.yaml")) ? path4.join(unitDir, "config.yaml") : path4.join(unitDir, "config.yml");
567
+ const config = await parsePreviewConfig(configPath);
568
+ units.push({
569
+ type,
570
+ name,
571
+ path: unitDir,
572
+ route: `/_preview/${typeFolder}/${name}`,
573
+ config,
574
+ files
575
+ });
576
+ }
577
+ }
578
+ return units;
579
+ }
580
+ async function detectUnitFiles(unitDir, type) {
581
+ const allFiles = await fg2.glob("*", { cwd: unitDir });
582
+ let index;
583
+ if (type === "flow" || type === "atlas") {
584
+ index = allFiles.find((f) => f === "index.yaml" || f === "index.yml");
585
+ } else {
586
+ const priorities = [
587
+ "index.tsx",
588
+ "index.jsx",
589
+ "index.ts",
590
+ "index.js",
591
+ "App.tsx",
592
+ "App.jsx",
593
+ "index.html"
594
+ ];
595
+ index = priorities.find((p) => allFiles.includes(p));
596
+ }
597
+ const result = {
598
+ index: index || ""
599
+ };
600
+ if (type === "screen" && index) {
601
+ const stateFiles = allFiles.filter((f) => (f.endsWith(".tsx") || f.endsWith(".jsx")) && f !== index).sort();
602
+ if (stateFiles.length > 0) {
603
+ result.states = stateFiles;
604
+ }
605
+ }
606
+ if (type === "component") {
607
+ if (allFiles.includes("schema.ts")) {
608
+ result.schema = "schema.ts";
609
+ }
610
+ }
611
+ if (allFiles.includes("docs.mdx") || allFiles.includes("README.mdx")) {
612
+ result.docs = allFiles.find((f) => f.endsWith(".mdx"));
613
+ }
614
+ return result;
615
+ }
465
616
 
466
617
  // src/preview-runtime/build.ts
467
618
  import { build } from "esbuild";
@@ -578,7 +729,7 @@ async function buildPreviewHtml(config) {
578
729
  }
579
730
 
580
731
  // src/vite/plugins/previews-plugin.ts
581
- import { existsSync as existsSync3, mkdirSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
732
+ import { existsSync as existsSync4, mkdirSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
582
733
  import path5 from "path";
583
734
  var VIRTUAL_MODULE_ID2 = "virtual:prev-previews";
584
735
  var RESOLVED_VIRTUAL_MODULE_ID2 = "\x00" + VIRTUAL_MODULE_ID2;
@@ -596,12 +747,39 @@ function previewsPlugin(rootDir) {
596
747
  },
597
748
  async load(id) {
598
749
  if (id === RESOLVED_VIRTUAL_MODULE_ID2) {
599
- const previews = await scanPreviews(rootDir);
600
- return `export const previews = ${JSON.stringify(previews)};`;
750
+ const units = await scanPreviewUnits(rootDir);
751
+ const legacyPreviews = await scanPreviews(rootDir);
752
+ return `
753
+ // Multi-type preview units
754
+ export const previewUnits = ${JSON.stringify(units)};
755
+
756
+ // Legacy flat previews (backwards compatibility)
757
+ export const previews = ${JSON.stringify(legacyPreviews)};
758
+
759
+ // Filtering helpers
760
+ export function getByType(type) {
761
+ return previewUnits.filter(u => u.type === type);
762
+ }
763
+
764
+ export function getByTags(tags) {
765
+ return previewUnits.filter(u =>
766
+ u.config?.tags?.some(t => tags.includes(t))
767
+ );
768
+ }
769
+
770
+ export function getByCategory(category) {
771
+ return previewUnits.filter(u => u.config?.category === category);
772
+ }
773
+
774
+ export function getByStatus(status) {
775
+ return previewUnits.filter(u => u.config?.status === status);
776
+ }
777
+ `;
601
778
  }
602
779
  },
603
780
  handleHotUpdate({ file, server }) {
604
- if (file.includes("/previews/") && /\.(html|tsx|ts|jsx|js|css)$/.test(file)) {
781
+ const previewsPath = path5.sep + "previews" + path5.sep;
782
+ if ((file.includes(previewsPath) || file.includes("/previews/")) && /\.(html|tsx|ts|jsx|js|css|yaml|yml|mdx)$/.test(file)) {
605
783
  const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID2);
606
784
  if (mod) {
607
785
  server.moduleGraph.invalidateModule(mod);
@@ -616,10 +794,10 @@ function previewsPlugin(rootDir) {
616
794
  const targetDir = path5.join(distDir, "_preview");
617
795
  const previewsDir = path5.join(rootDir, "previews");
618
796
  const oldPreviewsDir = path5.join(distDir, "previews");
619
- if (existsSync3(oldPreviewsDir)) {
797
+ if (existsSync4(oldPreviewsDir)) {
620
798
  rmSync(oldPreviewsDir, { recursive: true });
621
799
  }
622
- if (existsSync3(targetDir)) {
800
+ if (existsSync4(targetDir)) {
623
801
  rmSync(targetDir, { recursive: true });
624
802
  }
625
803
  const previews = await scanPreviews(rootDir);
@@ -758,15 +936,15 @@ function validateConfig(raw) {
758
936
  return config;
759
937
  }
760
938
  // src/config/loader.ts
761
- import { readFileSync as readFileSync3, existsSync as existsSync4, writeFileSync as writeFileSync3 } from "fs";
939
+ import { readFileSync as readFileSync4, existsSync as existsSync5, writeFileSync as writeFileSync3 } from "fs";
762
940
  import path6 from "path";
763
- import yaml from "js-yaml";
941
+ import yaml2 from "js-yaml";
764
942
  function findConfigFile(rootDir) {
765
943
  const yamlPath = path6.join(rootDir, ".prev.yaml");
766
944
  const ymlPath = path6.join(rootDir, ".prev.yml");
767
- if (existsSync4(yamlPath))
945
+ if (existsSync5(yamlPath))
768
946
  return yamlPath;
769
- if (existsSync4(ymlPath))
947
+ if (existsSync5(ymlPath))
770
948
  return ymlPath;
771
949
  return null;
772
950
  }
@@ -776,8 +954,8 @@ function loadConfig(rootDir) {
776
954
  return defaultConfig;
777
955
  }
778
956
  try {
779
- const content = readFileSync3(configPath, "utf-8");
780
- const raw = yaml.load(content);
957
+ const content = readFileSync4(configPath, "utf-8");
958
+ const raw = yaml2.load(content);
781
959
  return validateConfig(raw);
782
960
  } catch (error) {
783
961
  console.warn(`Warning: Failed to parse ${configPath}:`, error);
@@ -786,7 +964,7 @@ function loadConfig(rootDir) {
786
964
  }
787
965
  function saveConfig(rootDir, config) {
788
966
  const configPath = findConfigFile(rootDir) || path6.join(rootDir, ".prev.yaml");
789
- const content = yaml.dump(config, {
967
+ const content = yaml2.dump(config, {
790
968
  indent: 2,
791
969
  lineWidth: -1,
792
970
  quotingType: '"',
@@ -898,6 +1076,12 @@ function getDebugCollector() {
898
1076
  }
899
1077
 
900
1078
  // src/vite/config.ts
1079
+ var TYPE_SINGULAR = {
1080
+ components: "component",
1081
+ screens: "screen",
1082
+ flows: "flow",
1083
+ atlas: "atlas"
1084
+ };
901
1085
  function createFriendlyLogger() {
902
1086
  const logger = createLogger("info", { allowClearScreen: false });
903
1087
  const hiddenPatterns = [
@@ -955,9 +1139,9 @@ function findCliRoot2() {
955
1139
  let dir = path8.dirname(fileURLToPath2(import.meta.url));
956
1140
  for (let i = 0;i < 10; i++) {
957
1141
  const pkgPath = path8.join(dir, "package.json");
958
- if (existsSync5(pkgPath)) {
1142
+ if (existsSync6(pkgPath)) {
959
1143
  try {
960
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1144
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
961
1145
  if (pkg.name === "prev-cli") {
962
1146
  return dir;
963
1147
  }
@@ -972,7 +1156,7 @@ function findCliRoot2() {
972
1156
  }
973
1157
  function findNodeModules(cliRoot2) {
974
1158
  const localNodeModules = path8.join(cliRoot2, "node_modules");
975
- if (existsSync5(path8.join(localNodeModules, "react"))) {
1159
+ if (existsSync6(path8.join(localNodeModules, "react"))) {
976
1160
  return localNodeModules;
977
1161
  }
978
1162
  let dir = cliRoot2;
@@ -980,7 +1164,7 @@ function findNodeModules(cliRoot2) {
980
1164
  const parent = path8.dirname(dir);
981
1165
  if (parent === dir)
982
1166
  break;
983
- if (path8.basename(parent) === "node_modules" && existsSync5(path8.join(parent, "react"))) {
1167
+ if (path8.basename(parent) === "node_modules" && existsSync6(path8.join(parent, "react"))) {
984
1168
  return parent;
985
1169
  }
986
1170
  dir = parent;
@@ -1064,8 +1248,8 @@ async function createViteConfig(options) {
1064
1248
  return next();
1065
1249
  }
1066
1250
  const indexPath = path8.join(srcRoot2, "theme/index.html");
1067
- if (existsSync5(indexPath)) {
1068
- server.transformIndexHtml(req.url, readFileSync4(indexPath, "utf-8")).then((html) => {
1251
+ if (existsSync6(indexPath)) {
1252
+ server.transformIndexHtml(req.url, readFileSync5(indexPath, "utf-8")).then((html) => {
1069
1253
  res.setHeader("Content-Type", "text/html");
1070
1254
  res.end(html);
1071
1255
  }).catch(next);
@@ -1093,34 +1277,110 @@ async function createViteConfig(options) {
1093
1277
  const urlPath = req.url?.split("?")[0] || "";
1094
1278
  if (urlPath === "/_preview-runtime") {
1095
1279
  const templatePath = path8.join(srcRoot2, "preview-runtime/template.html");
1096
- if (existsSync5(templatePath)) {
1097
- const html = readFileSync4(templatePath, "utf-8");
1280
+ if (existsSync6(templatePath)) {
1281
+ const html = readFileSync5(templatePath, "utf-8");
1098
1282
  res.setHeader("Content-Type", "text/html");
1099
1283
  res.end(html);
1100
1284
  return;
1101
1285
  }
1102
1286
  }
1103
1287
  if (urlPath.startsWith("/_preview-config/")) {
1104
- const previewName = decodeURIComponent(urlPath.slice("/_preview-config/".length));
1288
+ const pathAfterConfig = decodeURIComponent(urlPath.slice("/_preview-config/".length));
1105
1289
  const previewsDir = path8.join(rootDir, "previews");
1106
- const previewDir = path8.resolve(previewsDir, previewName);
1107
- if (!previewDir.startsWith(previewsDir)) {
1108
- res.statusCode = 403;
1109
- res.end("Forbidden");
1110
- return;
1111
- }
1112
- if (existsSync5(previewDir)) {
1113
- try {
1114
- const config2 = await buildPreviewConfig(previewDir);
1115
- res.setHeader("Content-Type", "application/json");
1116
- res.end(JSON.stringify(config2));
1290
+ const multiTypeMatch = pathAfterConfig.match(/^(components|screens|flows|atlas)\/(.+)$/);
1291
+ if (multiTypeMatch) {
1292
+ const [, type, name] = multiTypeMatch;
1293
+ const configPathYaml = path8.join(previewsDir, type, name, "index.yaml");
1294
+ const configPathYml = path8.join(previewsDir, type, name, "index.yml");
1295
+ const configPath = existsSync6(configPathYaml) ? configPathYaml : configPathYml;
1296
+ if (!configPath.startsWith(previewsDir)) {
1297
+ res.statusCode = 403;
1298
+ res.end("Forbidden");
1117
1299
  return;
1118
- } catch (err) {
1119
- console.error("Error building preview config:", err);
1120
- res.statusCode = 500;
1121
- res.end(JSON.stringify({ error: String(err) }));
1300
+ }
1301
+ if (existsSync6(configPath)) {
1302
+ try {
1303
+ if (type === "flows") {
1304
+ const flow = await parseFlowDefinition(configPath);
1305
+ if (flow) {
1306
+ res.setHeader("Content-Type", "application/json");
1307
+ res.end(JSON.stringify(flow));
1308
+ return;
1309
+ }
1310
+ } else if (type === "atlas") {
1311
+ const atlas = await parseAtlasDefinition(configPath);
1312
+ if (atlas) {
1313
+ res.setHeader("Content-Type", "application/json");
1314
+ res.end(JSON.stringify(atlas));
1315
+ return;
1316
+ }
1317
+ } else {
1318
+ const previewDir = path8.join(previewsDir, type, name);
1319
+ const config2 = await buildPreviewConfig(previewDir);
1320
+ res.setHeader("Content-Type", "application/json");
1321
+ res.end(JSON.stringify(config2));
1322
+ return;
1323
+ }
1324
+ res.statusCode = 400;
1325
+ res.end(JSON.stringify({ error: "Invalid config format" }));
1326
+ return;
1327
+ } catch (err) {
1328
+ console.error("Error building preview config:", err);
1329
+ res.statusCode = 500;
1330
+ res.end(JSON.stringify({ error: String(err) }));
1331
+ return;
1332
+ }
1333
+ }
1334
+ } else {
1335
+ const previewDir = path8.resolve(previewsDir, pathAfterConfig);
1336
+ if (!previewDir.startsWith(previewsDir)) {
1337
+ res.statusCode = 403;
1338
+ res.end("Forbidden");
1122
1339
  return;
1123
1340
  }
1341
+ if (existsSync6(previewDir)) {
1342
+ try {
1343
+ const config2 = await buildPreviewConfig(previewDir);
1344
+ res.setHeader("Content-Type", "application/json");
1345
+ res.end(JSON.stringify(config2));
1346
+ return;
1347
+ } catch (err) {
1348
+ console.error("Error building preview config:", err);
1349
+ res.statusCode = 500;
1350
+ res.end(JSON.stringify({ error: String(err) }));
1351
+ return;
1352
+ }
1353
+ }
1354
+ }
1355
+ }
1356
+ if (urlPath.match(/^\/_preview\/(components|screens|flows|atlas)\/[^/]+\/?$/)) {
1357
+ const routeMatch = urlPath.match(/^\/_preview\/(\w+)\/([^/]+)\/?$/);
1358
+ if (routeMatch) {
1359
+ const [, type, name] = routeMatch;
1360
+ const singularType = TYPE_SINGULAR[type] || type;
1361
+ const shellHtml = `<!DOCTYPE html>
1362
+ <html>
1363
+ <head>
1364
+ <meta charset="UTF-8">
1365
+ <title>Preview: ${name}</title>
1366
+ <script type="module">
1367
+ import { PreviewRouter } from '@prev/theme/previews'
1368
+ import { createRoot } from 'react-dom/client'
1369
+ import React from 'react'
1370
+
1371
+ createRoot(document.getElementById('root')).render(
1372
+ React.createElement(PreviewRouter, { type: '${singularType}', name: '${name}' })
1373
+ )
1374
+ </script>
1375
+ </head>
1376
+ <body>
1377
+ <div id="root"></div>
1378
+ </body>
1379
+ </html>`;
1380
+ const transformed = await server.transformIndexHtml(req.url, shellHtml);
1381
+ res.setHeader("Content-Type", "text/html");
1382
+ res.end(transformed);
1383
+ return;
1124
1384
  }
1125
1385
  }
1126
1386
  if (urlPath.startsWith("/_preview/")) {
@@ -1132,9 +1392,9 @@ async function createViteConfig(options) {
1132
1392
  if (!htmlPath.startsWith(previewsDir)) {
1133
1393
  return next();
1134
1394
  }
1135
- if (existsSync5(htmlPath)) {
1395
+ if (existsSync6(htmlPath)) {
1136
1396
  try {
1137
- let html = readFileSync4(htmlPath, "utf-8");
1397
+ let html = readFileSync5(htmlPath, "utf-8");
1138
1398
  const previewBase = `/_preview/${previewName}/`;
1139
1399
  html = html.replace(/(src|href)=["']\.\/([^"']+)["']/g, `$1="${previewBase}$2"`);
1140
1400
  const transformed = await server.transformIndexHtml(req.url, html);
@@ -1261,7 +1521,7 @@ async function findAvailablePort(minPort, maxPort) {
1261
1521
 
1262
1522
  // src/vite/start.ts
1263
1523
  import { exec } from "child_process";
1264
- import { existsSync as existsSync6, rmSync as rmSync2, copyFileSync } from "fs";
1524
+ import { existsSync as existsSync7, rmSync as rmSync2, copyFileSync } from "fs";
1265
1525
  import path9 from "path";
1266
1526
  function printWelcome(type) {
1267
1527
  console.log();
@@ -1298,11 +1558,11 @@ function clearCache(rootDir) {
1298
1558
  const viteCacheDir = path9.join(rootDir, ".vite");
1299
1559
  const nodeModulesVite = path9.join(rootDir, "node_modules", ".vite");
1300
1560
  let cleared = 0;
1301
- if (existsSync6(viteCacheDir)) {
1561
+ if (existsSync7(viteCacheDir)) {
1302
1562
  rmSync2(viteCacheDir, { recursive: true });
1303
1563
  cleared++;
1304
1564
  }
1305
- if (existsSync6(nodeModulesVite)) {
1565
+ if (existsSync7(nodeModulesVite)) {
1306
1566
  rmSync2(nodeModulesVite, { recursive: true });
1307
1567
  cleared++;
1308
1568
  }
@@ -1424,7 +1684,7 @@ async function buildSite(rootDir, options = {}) {
1424
1684
  const distDir = path9.join(rootDir, "dist");
1425
1685
  const indexPath = path9.join(distDir, "index.html");
1426
1686
  const notFoundPath = path9.join(distDir, "404.html");
1427
- if (existsSync6(indexPath)) {
1687
+ if (existsSync7(indexPath)) {
1428
1688
  copyFileSync(indexPath, notFoundPath);
1429
1689
  }
1430
1690
  console.log();
@@ -1503,14 +1763,14 @@ async function cleanCache(options) {
1503
1763
  }
1504
1764
 
1505
1765
  // src/cli.ts
1506
- import yaml2 from "js-yaml";
1766
+ import yaml3 from "js-yaml";
1507
1767
  function getVersion() {
1508
1768
  try {
1509
1769
  let dir = path11.dirname(fileURLToPath3(import.meta.url));
1510
1770
  for (let i = 0;i < 5; i++) {
1511
1771
  const pkgPath = path11.join(dir, "package.json");
1512
- if (existsSync7(pkgPath)) {
1513
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
1772
+ if (existsSync8(pkgPath)) {
1773
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
1514
1774
  if (pkg.name === "prev-cli")
1515
1775
  return pkg.version;
1516
1776
  }
@@ -1637,7 +1897,7 @@ async function clearViteCache(rootDir2) {
1637
1897
  let cleared = 0;
1638
1898
  try {
1639
1899
  const prevCacheDir = await getCacheDir(rootDir2);
1640
- if (existsSync7(prevCacheDir)) {
1900
+ if (existsSync8(prevCacheDir)) {
1641
1901
  rmSync3(prevCacheDir, { recursive: true });
1642
1902
  cleared++;
1643
1903
  console.log(` ✓ Removed ${prevCacheDir}`);
@@ -1645,12 +1905,12 @@ async function clearViteCache(rootDir2) {
1645
1905
  } catch {}
1646
1906
  const viteCacheDir = path11.join(rootDir2, ".vite");
1647
1907
  const nodeModulesVite = path11.join(rootDir2, "node_modules", ".vite");
1648
- if (existsSync7(viteCacheDir)) {
1908
+ if (existsSync8(viteCacheDir)) {
1649
1909
  rmSync3(viteCacheDir, { recursive: true });
1650
1910
  cleared++;
1651
1911
  console.log(` ✓ Removed .vite/`);
1652
1912
  }
1653
- if (existsSync7(nodeModulesVite)) {
1913
+ if (existsSync8(nodeModulesVite)) {
1654
1914
  rmSync3(nodeModulesVite, { recursive: true });
1655
1915
  cleared++;
1656
1916
  console.log(` ✓ Removed node_modules/.vite/`);
@@ -1678,7 +1938,7 @@ function handleConfig(rootDir2, subcommand) {
1678
1938
  console.log(` File: (none - using defaults)
1679
1939
  `);
1680
1940
  }
1681
- const yamlOutput = yaml2.dump(config, {
1941
+ const yamlOutput = yaml3.dump(config, {
1682
1942
  indent: 2,
1683
1943
  lineWidth: -1,
1684
1944
  quotingType: '"',
@@ -1756,7 +2016,7 @@ Available subcommands: show, init, path`);
1756
2016
  }
1757
2017
  function createPreview(rootDir2, name) {
1758
2018
  const previewDir = path11.join(rootDir2, "previews", name);
1759
- if (existsSync7(previewDir)) {
2019
+ if (existsSync8(previewDir)) {
1760
2020
  console.error(`Preview "${name}" already exists at: ${previewDir}`);
1761
2021
  process.exit(1);
1762
2022
  }
@@ -0,0 +1,13 @@
1
+ import { type PreviewConfig, type FlowDefinition, type AtlasDefinition } from './preview-types';
2
+ /**
3
+ * Parse a config.yaml or config.yml file and validate against schema
4
+ */
5
+ export declare function parsePreviewConfig(filePath: string): Promise<PreviewConfig | null>;
6
+ /**
7
+ * Parse a flow index.yaml file
8
+ */
9
+ export declare function parseFlowDefinition(filePath: string): Promise<FlowDefinition | null>;
10
+ /**
11
+ * Parse an atlas index.yaml file
12
+ */
13
+ export declare function parseAtlasDefinition(filePath: string): Promise<AtlasDefinition | null>;
@@ -0,0 +1,70 @@
1
+ import { z } from 'zod';
2
+ export type PreviewType = 'component' | 'screen' | 'flow' | 'atlas';
3
+ export declare const configSchema: z.ZodObject<{
4
+ tags: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodPipe<z.ZodString, z.ZodTransform<string[], string>>]>>;
5
+ category: z.ZodOptional<z.ZodString>;
6
+ status: z.ZodOptional<z.ZodEnum<{
7
+ draft: "draft";
8
+ stable: "stable";
9
+ deprecated: "deprecated";
10
+ }>>;
11
+ title: z.ZodOptional<z.ZodString>;
12
+ description: z.ZodOptional<z.ZodString>;
13
+ order: z.ZodOptional<z.ZodNumber>;
14
+ }, z.core.$strip>;
15
+ export type PreviewConfig = z.infer<typeof configSchema>;
16
+ export interface PreviewUnit {
17
+ type: PreviewType;
18
+ name: string;
19
+ path: string;
20
+ route: string;
21
+ config: PreviewConfig | null;
22
+ files: {
23
+ index: string;
24
+ states?: string[];
25
+ schema?: string;
26
+ docs?: string;
27
+ };
28
+ }
29
+ export interface FlowStep {
30
+ screen: string;
31
+ state?: string;
32
+ note?: string;
33
+ trigger?: string;
34
+ highlight?: string[];
35
+ }
36
+ export interface FlowDefinition {
37
+ name: string;
38
+ description?: string;
39
+ steps: FlowStep[];
40
+ }
41
+ export interface AtlasArea {
42
+ title: string;
43
+ description?: string;
44
+ parent?: string;
45
+ children?: string[];
46
+ access?: string;
47
+ }
48
+ export interface AtlasDefinition {
49
+ name: string;
50
+ description?: string;
51
+ hierarchy: {
52
+ root: string;
53
+ areas: Record<string, AtlasArea>;
54
+ };
55
+ routes?: Record<string, {
56
+ area: string;
57
+ screen: string;
58
+ guard?: string;
59
+ }>;
60
+ navigation?: Record<string, Array<{
61
+ area?: string;
62
+ icon?: string;
63
+ action?: string;
64
+ }>>;
65
+ relationships?: Array<{
66
+ from: string;
67
+ to: string;
68
+ type: string;
69
+ }>;
70
+ }
@@ -1,4 +1,5 @@
1
1
  import type { PreviewFile, PreviewConfig } from '../preview-runtime/types';
2
+ import type { PreviewUnit } from './preview-types';
2
3
  export interface Preview {
3
4
  name: string;
4
5
  route: string;
@@ -21,3 +22,8 @@ export declare function detectEntry(files: PreviewFile[]): string;
21
22
  * Build a PreviewConfig for WASM runtime
22
23
  */
23
24
  export declare function buildPreviewConfig(previewDir: string): Promise<PreviewConfig>;
25
+ /**
26
+ * Scan previews with multi-type folder structure support
27
+ * Supports: components/, screens/, flows/, atlas/
28
+ */
29
+ export declare function scanPreviewUnits(rootDir: string): Promise<PreviewUnit[]>;