prev-cli 0.24.11 → 0.24.13

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,11 +3,11 @@
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";
7
- import { fileURLToPath as fileURLToPath3 } from "url";
6
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, rmSync as rmSync4, readFileSync as readFileSync7 } from "fs";
7
+ import { fileURLToPath as fileURLToPath4 } from "url";
8
8
 
9
9
  // src/vite/start.ts
10
- import { createServer as createServer2, build as build2, preview } from "vite";
10
+ import { createServer as createServer2, build as build3, preview } from "vite";
11
11
 
12
12
  // src/vite/config.ts
13
13
  import { createLogger } from "vite";
@@ -17,8 +17,8 @@ import remarkGfm from "remark-gfm";
17
17
  import rehypeHighlight from "rehype-highlight";
18
18
  import path8 from "path";
19
19
  import os from "os";
20
- import { fileURLToPath as fileURLToPath2 } from "url";
21
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
20
+ import { fileURLToPath as fileURLToPath3 } from "url";
21
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } 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,10 +541,168 @@ 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
- // src/preview-runtime/build.ts
617
+ // src/preview-runtime/vendors.ts
467
618
  import { build } from "esbuild";
468
- async function buildPreviewHtml(config) {
619
+ import { dirname } from "path";
620
+ import { fileURLToPath as fileURLToPath2 } from "url";
621
+ var __dirname2 = dirname(fileURLToPath2(import.meta.url));
622
+ async function buildVendorBundle() {
623
+ try {
624
+ const entryCode = `
625
+ import * as React from 'react'
626
+ import * as ReactDOM from 'react-dom'
627
+ import { createRoot } from 'react-dom/client'
628
+ export { jsx, jsxs, Fragment } from 'react/jsx-runtime'
629
+ export { React, ReactDOM, createRoot }
630
+ export default React
631
+ `;
632
+ const result = await build({
633
+ stdin: {
634
+ contents: entryCode,
635
+ loader: "ts",
636
+ resolveDir: __dirname2
637
+ },
638
+ bundle: true,
639
+ write: false,
640
+ format: "esm",
641
+ target: "es2020",
642
+ minify: true
643
+ });
644
+ const jsFile = result.outputFiles?.find((f) => f.path.endsWith(".js")) || result.outputFiles?.[0];
645
+ if (!jsFile) {
646
+ return { success: false, code: "", error: "No output generated" };
647
+ }
648
+ return { success: true, code: jsFile.text };
649
+ } catch (err) {
650
+ return {
651
+ success: false,
652
+ code: "",
653
+ error: err instanceof Error ? err.message : String(err)
654
+ };
655
+ }
656
+ }
657
+
658
+ // src/preview-runtime/build-optimized.ts
659
+ import { build as build2 } from "esbuild";
660
+
661
+ // src/preview-runtime/tailwind.ts
662
+ import { $ } from "bun";
663
+ import { mkdtempSync, mkdirSync, writeFileSync as writeFileSync2, readFileSync as readFileSync4, rmSync } from "fs";
664
+ import { join, dirname as dirname2 } from "path";
665
+ import { tmpdir } from "os";
666
+ async function compileTailwind(files) {
667
+ const tempDir = mkdtempSync(join(tmpdir(), "prev-tailwind-"));
668
+ try {
669
+ for (const file of files) {
670
+ const filePath = join(tempDir, file.path);
671
+ const parentDir = dirname2(filePath);
672
+ mkdirSync(parentDir, { recursive: true });
673
+ writeFileSync2(filePath, file.content);
674
+ }
675
+ const configContent = `
676
+ module.exports = {
677
+ content: [${JSON.stringify(tempDir + "/**/*.{tsx,jsx,ts,js,html}")}],
678
+ }
679
+ `;
680
+ const configPath = join(tempDir, "tailwind.config.cjs");
681
+ writeFileSync2(configPath, configContent);
682
+ const inputCss = `
683
+ @tailwind base;
684
+ @tailwind components;
685
+ @tailwind utilities;
686
+ `;
687
+ const inputPath = join(tempDir, "input.css");
688
+ writeFileSync2(inputPath, inputCss);
689
+ const outputPath = join(tempDir, "output.css");
690
+ await $`bunx tailwindcss -c ${configPath} -i ${inputPath} -o ${outputPath} --minify`.quiet();
691
+ const css = readFileSync4(outputPath, "utf-8");
692
+ return { success: true, css };
693
+ } catch (err) {
694
+ return {
695
+ success: false,
696
+ css: "",
697
+ error: err instanceof Error ? err.message : String(err)
698
+ };
699
+ } finally {
700
+ rmSync(tempDir, { recursive: true, force: true });
701
+ }
702
+ }
703
+
704
+ // src/preview-runtime/build-optimized.ts
705
+ async function buildOptimizedPreview(config, options) {
469
706
  try {
470
707
  const virtualFs = {};
471
708
  for (const file of config.files) {
@@ -475,25 +712,18 @@ async function buildPreviewHtml(config) {
475
712
  }
476
713
  const entryFile = config.files.find((f) => f.path === config.entry);
477
714
  if (!entryFile) {
478
- return { html: "", error: `Entry file not found: ${config.entry}` };
715
+ return { success: false, html: "", css: "", error: `Entry file not found: ${config.entry}` };
479
716
  }
480
717
  const hasDefaultExport = /export\s+default/.test(entryFile.content);
718
+ const userCssCollected = [];
481
719
  const entryCode = hasDefaultExport ? `
482
- import React from 'react'
483
- import { createRoot } from 'react-dom/client'
720
+ import React, { createRoot } from '${options.vendorPath}'
484
721
  import App from './${config.entry}'
485
-
486
722
  const root = createRoot(document.getElementById('root'))
487
723
  root.render(React.createElement(App))
488
- ` : `
489
- import './${config.entry}'
490
- `;
491
- const result = await build({
492
- stdin: {
493
- contents: entryCode,
494
- loader: "tsx",
495
- resolveDir: "/"
496
- },
724
+ ` : `import './${config.entry}'`;
725
+ const result = await build2({
726
+ stdin: { contents: entryCode, loader: "tsx", resolveDir: "/" },
497
727
  bundle: true,
498
728
  write: false,
499
729
  format: "esm",
@@ -501,84 +731,78 @@ async function buildPreviewHtml(config) {
501
731
  jsxImportSource: "react",
502
732
  target: "es2020",
503
733
  minify: true,
504
- plugins: [{
505
- name: "virtual-fs",
506
- setup(build2) {
507
- build2.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, (args) => {
508
- const parts = args.path.split("/");
509
- const pkg = parts[0];
510
- const subpath = parts.slice(1).join("/");
511
- const url = subpath ? `https://esm.sh/${pkg}@18/${subpath}` : `https://esm.sh/${pkg}@18`;
512
- return { path: url, external: true };
513
- });
514
- build2.onResolve({ filter: /^[^./]/ }, (args) => {
515
- if (args.path.startsWith("https://"))
516
- return;
517
- return { path: `https://esm.sh/${args.path}`, external: true };
518
- });
519
- build2.onResolve({ filter: /^\./ }, (args) => {
520
- let resolved = args.path.replace(/^\.\//, "");
521
- if (!resolved.includes(".")) {
522
- for (const ext of [".tsx", ".ts", ".jsx", ".js", ".css"]) {
523
- if (virtualFs[resolved + ext]) {
524
- resolved = resolved + ext;
525
- break;
734
+ plugins: [
735
+ {
736
+ name: "optimized-preview",
737
+ setup(build3) {
738
+ build3.onResolve({ filter: new RegExp(options.vendorPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")) }, (args) => {
739
+ return { path: args.path, external: true };
740
+ });
741
+ build3.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, () => {
742
+ return { path: options.vendorPath, external: true };
743
+ });
744
+ build3.onResolve({ filter: /^\./ }, (args) => {
745
+ let resolved = args.path.replace(/^\.\//, "");
746
+ if (!resolved.includes(".")) {
747
+ for (const ext of [".tsx", ".ts", ".jsx", ".js", ".css"]) {
748
+ if (virtualFs[resolved + ext]) {
749
+ resolved = resolved + ext;
750
+ break;
751
+ }
526
752
  }
527
753
  }
528
- }
529
- return { path: resolved, namespace: "virtual" };
530
- });
531
- build2.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => {
532
- const file = virtualFs[args.path];
533
- if (file) {
534
- if (file.loader === "css") {
535
- const css = file.contents.replace(/`/g, "\\`").replace(/\$/g, "\\$");
536
- return {
537
- contents: `
538
- const style = document.createElement('style');
539
- style.textContent = \`${css}\`;
540
- document.head.appendChild(style);
541
- `,
542
- loader: "js"
543
- };
754
+ return { path: resolved, namespace: "virtual" };
755
+ });
756
+ build3.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => {
757
+ const file = virtualFs[args.path];
758
+ if (file) {
759
+ if (file.loader === "css") {
760
+ userCssCollected.push(file.contents);
761
+ return { contents: "", loader: "js" };
762
+ }
763
+ return { contents: file.contents, loader: file.loader };
544
764
  }
545
- return { contents: file.contents, loader: file.loader };
546
- }
547
- return { contents: "", loader: "empty" };
548
- });
765
+ return { contents: "", loader: "empty" };
766
+ });
767
+ }
549
768
  }
550
- }]
769
+ ]
551
770
  });
552
- const jsFile = result.outputFiles.find((f) => f.path.endsWith(".js")) || result.outputFiles[0];
771
+ const jsFile = result.outputFiles?.find((f) => f.path.endsWith(".js")) || result.outputFiles?.[0];
553
772
  const jsCode = jsFile?.text || "";
773
+ let css = "";
774
+ if (config.tailwind) {
775
+ const tailwindResult = await compileTailwind(config.files.map((f) => ({ path: f.path, content: f.content })));
776
+ if (tailwindResult.success)
777
+ css = tailwindResult.css;
778
+ }
779
+ const userCss = userCssCollected.join(`
780
+ `);
781
+ const allCss = css + `
782
+ ` + userCss;
554
783
  const html = `<!DOCTYPE html>
555
784
  <html lang="en">
556
785
  <head>
557
786
  <meta charset="UTF-8">
558
787
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
559
788
  <title>Preview</title>
560
- <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
561
- <style>
562
- body { margin: 0; }
563
- #root { min-height: 100vh; }
564
- </style>
789
+ <style>${allCss}</style>
790
+ <style>body { margin: 0; } #root { min-height: 100vh; }</style>
565
791
  </head>
566
792
  <body>
567
793
  <div id="root"></div>
794
+ <script type="module" src="${options.vendorPath}"></script>
568
795
  <script type="module">${jsCode}</script>
569
796
  </body>
570
797
  </html>`;
571
- return { html };
798
+ return { success: true, html, css: allCss };
572
799
  } catch (err) {
573
- return {
574
- html: "",
575
- error: err instanceof Error ? err.message : String(err)
576
- };
800
+ return { success: false, html: "", css: "", error: err instanceof Error ? err.message : String(err) };
577
801
  }
578
802
  }
579
803
 
580
804
  // src/vite/plugins/previews-plugin.ts
581
- import { existsSync as existsSync3, mkdirSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
805
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
582
806
  import path5 from "path";
583
807
  var VIRTUAL_MODULE_ID2 = "virtual:prev-previews";
584
808
  var RESOLVED_VIRTUAL_MODULE_ID2 = "\x00" + VIRTUAL_MODULE_ID2;
@@ -596,12 +820,39 @@ function previewsPlugin(rootDir) {
596
820
  },
597
821
  async load(id) {
598
822
  if (id === RESOLVED_VIRTUAL_MODULE_ID2) {
599
- const previews = await scanPreviews(rootDir);
600
- return `export const previews = ${JSON.stringify(previews)};`;
823
+ const units = await scanPreviewUnits(rootDir);
824
+ const legacyPreviews = await scanPreviews(rootDir);
825
+ return `
826
+ // Multi-type preview units
827
+ export const previewUnits = ${JSON.stringify(units)};
828
+
829
+ // Legacy flat previews (backwards compatibility)
830
+ export const previews = ${JSON.stringify(legacyPreviews)};
831
+
832
+ // Filtering helpers
833
+ export function getByType(type) {
834
+ return previewUnits.filter(u => u.type === type);
835
+ }
836
+
837
+ export function getByTags(tags) {
838
+ return previewUnits.filter(u =>
839
+ u.config?.tags?.some(t => tags.includes(t))
840
+ );
841
+ }
842
+
843
+ export function getByCategory(category) {
844
+ return previewUnits.filter(u => u.config?.category === category);
845
+ }
846
+
847
+ export function getByStatus(status) {
848
+ return previewUnits.filter(u => u.config?.status === status);
849
+ }
850
+ `;
601
851
  }
602
852
  },
603
853
  handleHotUpdate({ file, server }) {
604
- if (file.includes("/previews/") && /\.(html|tsx|ts|jsx|js|css)$/.test(file)) {
854
+ const previewsPath = path5.sep + "previews" + path5.sep;
855
+ if ((file.includes(previewsPath) || file.includes("/previews/")) && /\.(html|tsx|ts|jsx|js|css|yaml|yml|mdx)$/.test(file)) {
605
856
  const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID2);
606
857
  if (mod) {
607
858
  server.moduleGraph.invalidateModule(mod);
@@ -614,31 +865,43 @@ function previewsPlugin(rootDir) {
614
865
  return;
615
866
  const distDir = path5.join(rootDir, "dist");
616
867
  const targetDir = path5.join(distDir, "_preview");
868
+ const vendorsDir = path5.join(targetDir, "_vendors");
617
869
  const previewsDir = path5.join(rootDir, "previews");
618
870
  const oldPreviewsDir = path5.join(distDir, "previews");
619
- if (existsSync3(oldPreviewsDir)) {
620
- rmSync(oldPreviewsDir, { recursive: true });
871
+ if (existsSync4(oldPreviewsDir)) {
872
+ rmSync2(oldPreviewsDir, { recursive: true });
621
873
  }
622
- if (existsSync3(targetDir)) {
623
- rmSync(targetDir, { recursive: true });
874
+ if (existsSync4(targetDir)) {
875
+ rmSync2(targetDir, { recursive: true });
624
876
  }
625
877
  const previews = await scanPreviews(rootDir);
626
878
  if (previews.length === 0)
627
879
  return;
628
880
  console.log(`
629
881
  Building ${previews.length} preview(s)...`);
882
+ console.log(" Building shared vendor bundle...");
883
+ mkdirSync2(vendorsDir, { recursive: true });
884
+ const vendorResult = await buildVendorBundle();
885
+ if (!vendorResult.success) {
886
+ console.error(` ✗ Vendor bundle: ${vendorResult.error}`);
887
+ return;
888
+ }
889
+ writeFileSync3(path5.join(vendorsDir, "runtime.js"), vendorResult.code);
890
+ console.log(" ✓ _vendors/runtime.js");
630
891
  for (const preview of previews) {
631
892
  const previewDir = path5.join(previewsDir, preview.name);
632
893
  try {
633
894
  const config = await buildPreviewConfig(previewDir);
634
- const result = await buildPreviewHtml(config);
635
- if (result.error) {
895
+ const depth = preview.name.split("/").length;
896
+ const vendorPath = "../".repeat(depth) + "_vendors/runtime.js";
897
+ const result = await buildOptimizedPreview(config, { vendorPath });
898
+ if (!result.success) {
636
899
  console.error(` ✗ ${preview.name}: ${result.error}`);
637
900
  continue;
638
901
  }
639
902
  const outputDir = path5.join(targetDir, preview.name);
640
- mkdirSync(outputDir, { recursive: true });
641
- writeFileSync2(path5.join(outputDir, "index.html"), result.html);
903
+ mkdirSync2(outputDir, { recursive: true });
904
+ writeFileSync3(path5.join(outputDir, "index.html"), result.html);
642
905
  console.log(` ✓ ${preview.name}`);
643
906
  } catch (err) {
644
907
  console.error(` ✗ ${preview.name}: ${err}`);
@@ -758,15 +1021,15 @@ function validateConfig(raw) {
758
1021
  return config;
759
1022
  }
760
1023
  // src/config/loader.ts
761
- import { readFileSync as readFileSync3, existsSync as existsSync4, writeFileSync as writeFileSync3 } from "fs";
1024
+ import { readFileSync as readFileSync5, existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
762
1025
  import path6 from "path";
763
- import yaml from "js-yaml";
1026
+ import yaml2 from "js-yaml";
764
1027
  function findConfigFile(rootDir) {
765
1028
  const yamlPath = path6.join(rootDir, ".prev.yaml");
766
1029
  const ymlPath = path6.join(rootDir, ".prev.yml");
767
- if (existsSync4(yamlPath))
1030
+ if (existsSync5(yamlPath))
768
1031
  return yamlPath;
769
- if (existsSync4(ymlPath))
1032
+ if (existsSync5(ymlPath))
770
1033
  return ymlPath;
771
1034
  return null;
772
1035
  }
@@ -776,8 +1039,8 @@ function loadConfig(rootDir) {
776
1039
  return defaultConfig;
777
1040
  }
778
1041
  try {
779
- const content = readFileSync3(configPath, "utf-8");
780
- const raw = yaml.load(content);
1042
+ const content = readFileSync5(configPath, "utf-8");
1043
+ const raw = yaml2.load(content);
781
1044
  return validateConfig(raw);
782
1045
  } catch (error) {
783
1046
  console.warn(`Warning: Failed to parse ${configPath}:`, error);
@@ -786,13 +1049,13 @@ function loadConfig(rootDir) {
786
1049
  }
787
1050
  function saveConfig(rootDir, config) {
788
1051
  const configPath = findConfigFile(rootDir) || path6.join(rootDir, ".prev.yaml");
789
- const content = yaml.dump(config, {
1052
+ const content = yaml2.dump(config, {
790
1053
  indent: 2,
791
1054
  lineWidth: -1,
792
1055
  quotingType: '"',
793
1056
  forceQuotes: false
794
1057
  });
795
- writeFileSync3(configPath, content, "utf-8");
1058
+ writeFileSync4(configPath, content, "utf-8");
796
1059
  }
797
1060
  function updateOrder(rootDir, pathKey, order) {
798
1061
  const config = loadConfig(rootDir);
@@ -800,7 +1063,7 @@ function updateOrder(rootDir, pathKey, order) {
800
1063
  saveConfig(rootDir, config);
801
1064
  }
802
1065
  // src/utils/debug.ts
803
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
1066
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
804
1067
  import path7 from "path";
805
1068
 
806
1069
  class DebugCollector {
@@ -880,11 +1143,11 @@ class DebugCollector {
880
1143
  summary: this.generateSummary()
881
1144
  };
882
1145
  const debugDir = path7.join(this.rootDir, ".prev-debug");
883
- mkdirSync2(debugDir, { recursive: true });
1146
+ mkdirSync3(debugDir, { recursive: true });
884
1147
  const date = new Date;
885
1148
  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`;
886
1149
  const filepath = path7.join(debugDir, filename);
887
- writeFileSync4(filepath, JSON.stringify(report, null, 2));
1150
+ writeFileSync5(filepath, JSON.stringify(report, null, 2));
888
1151
  return filepath;
889
1152
  }
890
1153
  }
@@ -898,6 +1161,12 @@ function getDebugCollector() {
898
1161
  }
899
1162
 
900
1163
  // src/vite/config.ts
1164
+ var TYPE_SINGULAR = {
1165
+ components: "component",
1166
+ screens: "screen",
1167
+ flows: "flow",
1168
+ atlas: "atlas"
1169
+ };
901
1170
  function createFriendlyLogger() {
902
1171
  const logger = createLogger("info", { allowClearScreen: false });
903
1172
  const hiddenPatterns = [
@@ -952,12 +1221,12 @@ function createFriendlyLogger() {
952
1221
  };
953
1222
  }
954
1223
  function findCliRoot2() {
955
- let dir = path8.dirname(fileURLToPath2(import.meta.url));
1224
+ let dir = path8.dirname(fileURLToPath3(import.meta.url));
956
1225
  for (let i = 0;i < 10; i++) {
957
1226
  const pkgPath = path8.join(dir, "package.json");
958
- if (existsSync5(pkgPath)) {
1227
+ if (existsSync6(pkgPath)) {
959
1228
  try {
960
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1229
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
961
1230
  if (pkg.name === "prev-cli") {
962
1231
  return dir;
963
1232
  }
@@ -968,11 +1237,11 @@ function findCliRoot2() {
968
1237
  break;
969
1238
  dir = parent;
970
1239
  }
971
- return path8.dirname(path8.dirname(fileURLToPath2(import.meta.url)));
1240
+ return path8.dirname(path8.dirname(fileURLToPath3(import.meta.url)));
972
1241
  }
973
1242
  function findNodeModules(cliRoot2) {
974
1243
  const localNodeModules = path8.join(cliRoot2, "node_modules");
975
- if (existsSync5(path8.join(localNodeModules, "react"))) {
1244
+ if (existsSync6(path8.join(localNodeModules, "react"))) {
976
1245
  return localNodeModules;
977
1246
  }
978
1247
  let dir = cliRoot2;
@@ -980,7 +1249,7 @@ function findNodeModules(cliRoot2) {
980
1249
  const parent = path8.dirname(dir);
981
1250
  if (parent === dir)
982
1251
  break;
983
- if (path8.basename(parent) === "node_modules" && existsSync5(path8.join(parent, "react"))) {
1252
+ if (path8.basename(parent) === "node_modules" && existsSync6(path8.join(parent, "react"))) {
984
1253
  return parent;
985
1254
  }
986
1255
  dir = parent;
@@ -1064,8 +1333,8 @@ async function createViteConfig(options) {
1064
1333
  return next();
1065
1334
  }
1066
1335
  const indexPath = path8.join(srcRoot2, "theme/index.html");
1067
- if (existsSync5(indexPath)) {
1068
- server.transformIndexHtml(req.url, readFileSync4(indexPath, "utf-8")).then((html) => {
1336
+ if (existsSync6(indexPath)) {
1337
+ server.transformIndexHtml(req.url, readFileSync6(indexPath, "utf-8")).then((html) => {
1069
1338
  res.setHeader("Content-Type", "text/html");
1070
1339
  res.end(html);
1071
1340
  }).catch(next);
@@ -1093,34 +1362,110 @@ async function createViteConfig(options) {
1093
1362
  const urlPath = req.url?.split("?")[0] || "";
1094
1363
  if (urlPath === "/_preview-runtime") {
1095
1364
  const templatePath = path8.join(srcRoot2, "preview-runtime/template.html");
1096
- if (existsSync5(templatePath)) {
1097
- const html = readFileSync4(templatePath, "utf-8");
1365
+ if (existsSync6(templatePath)) {
1366
+ const html = readFileSync6(templatePath, "utf-8");
1098
1367
  res.setHeader("Content-Type", "text/html");
1099
1368
  res.end(html);
1100
1369
  return;
1101
1370
  }
1102
1371
  }
1103
1372
  if (urlPath.startsWith("/_preview-config/")) {
1104
- const previewName = decodeURIComponent(urlPath.slice("/_preview-config/".length));
1373
+ const pathAfterConfig = decodeURIComponent(urlPath.slice("/_preview-config/".length));
1105
1374
  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));
1375
+ const multiTypeMatch = pathAfterConfig.match(/^(components|screens|flows|atlas)\/(.+)$/);
1376
+ if (multiTypeMatch) {
1377
+ const [, type, name] = multiTypeMatch;
1378
+ const configPathYaml = path8.join(previewsDir, type, name, "index.yaml");
1379
+ const configPathYml = path8.join(previewsDir, type, name, "index.yml");
1380
+ const configPath = existsSync6(configPathYaml) ? configPathYaml : configPathYml;
1381
+ if (!configPath.startsWith(previewsDir)) {
1382
+ res.statusCode = 403;
1383
+ res.end("Forbidden");
1117
1384
  return;
1118
- } catch (err) {
1119
- console.error("Error building preview config:", err);
1120
- res.statusCode = 500;
1121
- res.end(JSON.stringify({ error: String(err) }));
1385
+ }
1386
+ if (existsSync6(configPath)) {
1387
+ try {
1388
+ if (type === "flows") {
1389
+ const flow = await parseFlowDefinition(configPath);
1390
+ if (flow) {
1391
+ res.setHeader("Content-Type", "application/json");
1392
+ res.end(JSON.stringify(flow));
1393
+ return;
1394
+ }
1395
+ } else if (type === "atlas") {
1396
+ const atlas = await parseAtlasDefinition(configPath);
1397
+ if (atlas) {
1398
+ res.setHeader("Content-Type", "application/json");
1399
+ res.end(JSON.stringify(atlas));
1400
+ return;
1401
+ }
1402
+ } else {
1403
+ const previewDir = path8.join(previewsDir, type, name);
1404
+ const config2 = await buildPreviewConfig(previewDir);
1405
+ res.setHeader("Content-Type", "application/json");
1406
+ res.end(JSON.stringify(config2));
1407
+ return;
1408
+ }
1409
+ res.statusCode = 400;
1410
+ res.end(JSON.stringify({ error: "Invalid config format" }));
1411
+ return;
1412
+ } catch (err) {
1413
+ console.error("Error building preview config:", err);
1414
+ res.statusCode = 500;
1415
+ res.end(JSON.stringify({ error: String(err) }));
1416
+ return;
1417
+ }
1418
+ }
1419
+ } else {
1420
+ const previewDir = path8.resolve(previewsDir, pathAfterConfig);
1421
+ if (!previewDir.startsWith(previewsDir)) {
1422
+ res.statusCode = 403;
1423
+ res.end("Forbidden");
1122
1424
  return;
1123
1425
  }
1426
+ if (existsSync6(previewDir)) {
1427
+ try {
1428
+ const config2 = await buildPreviewConfig(previewDir);
1429
+ res.setHeader("Content-Type", "application/json");
1430
+ res.end(JSON.stringify(config2));
1431
+ return;
1432
+ } catch (err) {
1433
+ console.error("Error building preview config:", err);
1434
+ res.statusCode = 500;
1435
+ res.end(JSON.stringify({ error: String(err) }));
1436
+ return;
1437
+ }
1438
+ }
1439
+ }
1440
+ }
1441
+ if (urlPath.match(/^\/_preview\/(components|screens|flows|atlas)\/[^/]+\/?$/)) {
1442
+ const routeMatch = urlPath.match(/^\/_preview\/(\w+)\/([^/]+)\/?$/);
1443
+ if (routeMatch) {
1444
+ const [, type, name] = routeMatch;
1445
+ const singularType = TYPE_SINGULAR[type] || type;
1446
+ const shellHtml = `<!DOCTYPE html>
1447
+ <html>
1448
+ <head>
1449
+ <meta charset="UTF-8">
1450
+ <title>Preview: ${name}</title>
1451
+ <script type="module">
1452
+ import { PreviewRouter } from '@prev/theme/previews'
1453
+ import { createRoot } from 'react-dom/client'
1454
+ import React from 'react'
1455
+
1456
+ createRoot(document.getElementById('root')).render(
1457
+ React.createElement(PreviewRouter, { type: '${singularType}', name: '${name}' })
1458
+ )
1459
+ </script>
1460
+ </head>
1461
+ <body>
1462
+ <div id="root"></div>
1463
+ </body>
1464
+ </html>`;
1465
+ const transformed = await server.transformIndexHtml(req.url, shellHtml);
1466
+ res.setHeader("Content-Type", "text/html");
1467
+ res.end(transformed);
1468
+ return;
1124
1469
  }
1125
1470
  }
1126
1471
  if (urlPath.startsWith("/_preview/")) {
@@ -1132,9 +1477,9 @@ async function createViteConfig(options) {
1132
1477
  if (!htmlPath.startsWith(previewsDir)) {
1133
1478
  return next();
1134
1479
  }
1135
- if (existsSync5(htmlPath)) {
1480
+ if (existsSync6(htmlPath)) {
1136
1481
  try {
1137
- let html = readFileSync4(htmlPath, "utf-8");
1482
+ let html = readFileSync6(htmlPath, "utf-8");
1138
1483
  const previewBase = `/_preview/${previewName}/`;
1139
1484
  html = html.replace(/(src|href)=["']\.\/([^"']+)["']/g, `$1="${previewBase}$2"`);
1140
1485
  const transformed = await server.transformIndexHtml(req.url, html);
@@ -1261,7 +1606,7 @@ async function findAvailablePort(minPort, maxPort) {
1261
1606
 
1262
1607
  // src/vite/start.ts
1263
1608
  import { exec } from "child_process";
1264
- import { existsSync as existsSync6, rmSync as rmSync2, copyFileSync } from "fs";
1609
+ import { existsSync as existsSync7, rmSync as rmSync3, copyFileSync } from "fs";
1265
1610
  import path9 from "path";
1266
1611
  function printWelcome(type) {
1267
1612
  console.log();
@@ -1298,12 +1643,12 @@ function clearCache(rootDir) {
1298
1643
  const viteCacheDir = path9.join(rootDir, ".vite");
1299
1644
  const nodeModulesVite = path9.join(rootDir, "node_modules", ".vite");
1300
1645
  let cleared = 0;
1301
- if (existsSync6(viteCacheDir)) {
1302
- rmSync2(viteCacheDir, { recursive: true });
1646
+ if (existsSync7(viteCacheDir)) {
1647
+ rmSync3(viteCacheDir, { recursive: true });
1303
1648
  cleared++;
1304
1649
  }
1305
- if (existsSync6(nodeModulesVite)) {
1306
- rmSync2(nodeModulesVite, { recursive: true });
1650
+ if (existsSync7(nodeModulesVite)) {
1651
+ rmSync3(nodeModulesVite, { recursive: true });
1307
1652
  cleared++;
1308
1653
  }
1309
1654
  if (cleared === 0) {
@@ -1414,7 +1759,7 @@ async function buildSite(rootDir, options = {}) {
1414
1759
  base: options.base,
1415
1760
  debug: options.debug
1416
1761
  });
1417
- await build2(config);
1762
+ await build3(config);
1418
1763
  const debugCollector = getDebugCollector();
1419
1764
  if (debugCollector) {
1420
1765
  debugCollector.startPhase("buildComplete");
@@ -1424,7 +1769,7 @@ async function buildSite(rootDir, options = {}) {
1424
1769
  const distDir = path9.join(rootDir, "dist");
1425
1770
  const indexPath = path9.join(distDir, "index.html");
1426
1771
  const notFoundPath = path9.join(distDir, "404.html");
1427
- if (existsSync6(indexPath)) {
1772
+ if (existsSync7(indexPath)) {
1428
1773
  copyFileSync(indexPath, notFoundPath);
1429
1774
  }
1430
1775
  console.log();
@@ -1503,14 +1848,14 @@ async function cleanCache(options) {
1503
1848
  }
1504
1849
 
1505
1850
  // src/cli.ts
1506
- import yaml2 from "js-yaml";
1851
+ import yaml3 from "js-yaml";
1507
1852
  function getVersion() {
1508
1853
  try {
1509
- let dir = path11.dirname(fileURLToPath3(import.meta.url));
1854
+ let dir = path11.dirname(fileURLToPath4(import.meta.url));
1510
1855
  for (let i = 0;i < 5; i++) {
1511
1856
  const pkgPath = path11.join(dir, "package.json");
1512
- if (existsSync7(pkgPath)) {
1513
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
1857
+ if (existsSync8(pkgPath)) {
1858
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
1514
1859
  if (pkg.name === "prev-cli")
1515
1860
  return pkg.version;
1516
1861
  }
@@ -1637,21 +1982,21 @@ async function clearViteCache(rootDir2) {
1637
1982
  let cleared = 0;
1638
1983
  try {
1639
1984
  const prevCacheDir = await getCacheDir(rootDir2);
1640
- if (existsSync7(prevCacheDir)) {
1641
- rmSync3(prevCacheDir, { recursive: true });
1985
+ if (existsSync8(prevCacheDir)) {
1986
+ rmSync4(prevCacheDir, { recursive: true });
1642
1987
  cleared++;
1643
1988
  console.log(` ✓ Removed ${prevCacheDir}`);
1644
1989
  }
1645
1990
  } catch {}
1646
1991
  const viteCacheDir = path11.join(rootDir2, ".vite");
1647
1992
  const nodeModulesVite = path11.join(rootDir2, "node_modules", ".vite");
1648
- if (existsSync7(viteCacheDir)) {
1649
- rmSync3(viteCacheDir, { recursive: true });
1993
+ if (existsSync8(viteCacheDir)) {
1994
+ rmSync4(viteCacheDir, { recursive: true });
1650
1995
  cleared++;
1651
1996
  console.log(` ✓ Removed .vite/`);
1652
1997
  }
1653
- if (existsSync7(nodeModulesVite)) {
1654
- rmSync3(nodeModulesVite, { recursive: true });
1998
+ if (existsSync8(nodeModulesVite)) {
1999
+ rmSync4(nodeModulesVite, { recursive: true });
1655
2000
  cleared++;
1656
2001
  console.log(` ✓ Removed node_modules/.vite/`);
1657
2002
  }
@@ -1678,7 +2023,7 @@ function handleConfig(rootDir2, subcommand) {
1678
2023
  console.log(` File: (none - using defaults)
1679
2024
  `);
1680
2025
  }
1681
- const yamlOutput = yaml2.dump(config, {
2026
+ const yamlOutput = yaml3.dump(config, {
1682
2027
  indent: 2,
1683
2028
  lineWidth: -1,
1684
2029
  quotingType: '"',
@@ -1732,7 +2077,7 @@ order: {}
1732
2077
  # - "getting-started.md"
1733
2078
  # - "guides/"
1734
2079
  `;
1735
- writeFileSync5(targetPath, configContent, "utf-8");
2080
+ writeFileSync6(targetPath, configContent, "utf-8");
1736
2081
  console.log(`
1737
2082
  ✨ Created ${targetPath}
1738
2083
  `);
@@ -1756,11 +2101,11 @@ Available subcommands: show, init, path`);
1756
2101
  }
1757
2102
  function createPreview(rootDir2, name) {
1758
2103
  const previewDir = path11.join(rootDir2, "previews", name);
1759
- if (existsSync7(previewDir)) {
2104
+ if (existsSync8(previewDir)) {
1760
2105
  console.error(`Preview "${name}" already exists at: ${previewDir}`);
1761
2106
  process.exit(1);
1762
2107
  }
1763
- mkdirSync3(previewDir, { recursive: true });
2108
+ mkdirSync4(previewDir, { recursive: true });
1764
2109
  const appTsx = `import { useState } from 'react'
1765
2110
  import './styles.css'
1766
2111
 
@@ -1881,8 +2226,8 @@ export default function App() {
1881
2226
  .dark\\:text-white { color: #fff; }
1882
2227
  }
1883
2228
  `;
1884
- writeFileSync5(path11.join(previewDir, "App.tsx"), appTsx);
1885
- writeFileSync5(path11.join(previewDir, "styles.css"), stylesCss);
2229
+ writeFileSync6(path11.join(previewDir, "App.tsx"), appTsx);
2230
+ writeFileSync6(path11.join(previewDir, "styles.css"), stylesCss);
1886
2231
  console.log(`
1887
2232
  ✨ Created preview: previews/${name}/
1888
2233