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 +315 -55
- package/dist/vite/config-parser.d.ts +13 -0
- package/dist/vite/preview-types.d.ts +70 -0
- package/dist/vite/previews.d.ts +6 -0
- package/package.json +3 -2
- package/src/theme/previews/AtlasPreview.tsx +528 -0
- package/src/theme/previews/ComponentPreview.tsx +180 -0
- package/src/theme/previews/FlowPreview.tsx +270 -0
- package/src/theme/previews/PreviewRouter.tsx +189 -0
- package/src/theme/previews/ScreenPreview.tsx +297 -0
- package/src/theme/previews/index.ts +5 -0
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
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
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
|
|
600
|
-
|
|
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
|
-
|
|
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 (
|
|
797
|
+
if (existsSync4(oldPreviewsDir)) {
|
|
620
798
|
rmSync(oldPreviewsDir, { recursive: true });
|
|
621
799
|
}
|
|
622
|
-
if (
|
|
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
|
|
939
|
+
import { readFileSync as readFileSync4, existsSync as existsSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
762
940
|
import path6 from "path";
|
|
763
|
-
import
|
|
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 (
|
|
945
|
+
if (existsSync5(yamlPath))
|
|
768
946
|
return yamlPath;
|
|
769
|
-
if (
|
|
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 =
|
|
780
|
-
const raw =
|
|
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 =
|
|
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 (
|
|
1142
|
+
if (existsSync6(pkgPath)) {
|
|
959
1143
|
try {
|
|
960
|
-
const pkg = JSON.parse(
|
|
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 (
|
|
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" &&
|
|
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 (
|
|
1068
|
-
server.transformIndexHtml(req.url,
|
|
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 (
|
|
1097
|
-
const html =
|
|
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
|
|
1288
|
+
const pathAfterConfig = decodeURIComponent(urlPath.slice("/_preview-config/".length));
|
|
1105
1289
|
const previewsDir = path8.join(rootDir, "previews");
|
|
1106
|
-
const
|
|
1107
|
-
if (
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
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
|
-
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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 (
|
|
1395
|
+
if (existsSync6(htmlPath)) {
|
|
1136
1396
|
try {
|
|
1137
|
-
let html =
|
|
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
|
|
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 (
|
|
1561
|
+
if (existsSync7(viteCacheDir)) {
|
|
1302
1562
|
rmSync2(viteCacheDir, { recursive: true });
|
|
1303
1563
|
cleared++;
|
|
1304
1564
|
}
|
|
1305
|
-
if (
|
|
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 (
|
|
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
|
|
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 (
|
|
1513
|
-
const pkg = JSON.parse(
|
|
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 (
|
|
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 (
|
|
1908
|
+
if (existsSync8(viteCacheDir)) {
|
|
1649
1909
|
rmSync3(viteCacheDir, { recursive: true });
|
|
1650
1910
|
cleared++;
|
|
1651
1911
|
console.log(` ✓ Removed .vite/`);
|
|
1652
1912
|
}
|
|
1653
|
-
if (
|
|
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 =
|
|
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 (
|
|
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
|
+
}
|
package/dist/vite/previews.d.ts
CHANGED
|
@@ -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[]>;
|