@vizejs/vite-plugin-musea 0.0.1-alpha.100
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/README.md +56 -0
- package/dist/a11y-C6xqILwZ.js +305 -0
- package/dist/a11y-C6xqILwZ.js.map +1 -0
- package/dist/a11y-CHcxz6UR.d.ts +61 -0
- package/dist/a11y-CHcxz6UR.d.ts.map +1 -0
- package/dist/a11y.d.ts +3 -0
- package/dist/a11y.js +3 -0
- package/dist/autogen-D3Zjc3zI.d.ts +64 -0
- package/dist/autogen-D3Zjc3zI.d.ts.map +1 -0
- package/dist/autogen-ymQnARZK.js +193 -0
- package/dist/autogen-ymQnARZK.js.map +1 -0
- package/dist/autogen.d.ts +2 -0
- package/dist/autogen.js +3 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +399 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +143 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2760 -0
- package/dist/index.js.map +1 -0
- package/dist/vrt-DP87vGIA.js +706 -0
- package/dist/vrt-DP87vGIA.js.map +1 -0
- package/dist/vrt-m01uFerp.d.ts +439 -0
- package/dist/vrt-m01uFerp.d.ts.map +1 -0
- package/dist/vrt.d.ts +2 -0
- package/dist/vrt.js +3 -0
- package/package.json +99 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
//#region src/autogen.ts
|
|
6
|
+
let native = null;
|
|
7
|
+
function loadNative() {
|
|
8
|
+
if (native) return native;
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
try {
|
|
11
|
+
native = require("@vizejs/native");
|
|
12
|
+
return native;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
throw new Error(`Failed to load @vizejs/native. Make sure it's installed and built:\n${String(e)}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate .art.vue file for a component.
|
|
19
|
+
*
|
|
20
|
+
* @param componentPath - Path to the Vue component file
|
|
21
|
+
* @param options - Auto-generation options
|
|
22
|
+
* @returns Generated .art.vue content and metadata
|
|
23
|
+
*/
|
|
24
|
+
async function generateArtFile(componentPath, options = {}) {
|
|
25
|
+
const absolutePath = path.resolve(componentPath);
|
|
26
|
+
const source = await fs.promises.readFile(absolutePath, "utf-8");
|
|
27
|
+
const binding = loadNative();
|
|
28
|
+
let props;
|
|
29
|
+
if (binding.analyzeSfc) {
|
|
30
|
+
const analysis = binding.analyzeSfc(source, { filename: absolutePath });
|
|
31
|
+
props = analysis.props.map((p) => ({
|
|
32
|
+
name: p.name,
|
|
33
|
+
propType: p.type,
|
|
34
|
+
required: p.required,
|
|
35
|
+
defaultValue: p.default_value
|
|
36
|
+
}));
|
|
37
|
+
} else props = extractPropsSimple(source);
|
|
38
|
+
if (props.length === 0) {
|
|
39
|
+
const componentName = path.basename(componentPath, ".vue");
|
|
40
|
+
const relPath = `./${path.basename(componentPath)}`;
|
|
41
|
+
return {
|
|
42
|
+
variants: [{
|
|
43
|
+
name: "Default",
|
|
44
|
+
isDefault: true,
|
|
45
|
+
props: {}
|
|
46
|
+
}],
|
|
47
|
+
artFileContent: generateMinimalArt(componentName, relPath),
|
|
48
|
+
componentName
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (binding.generateVariants) {
|
|
52
|
+
const nativeProps = props.map((p) => ({
|
|
53
|
+
name: p.name,
|
|
54
|
+
prop_type: p.propType,
|
|
55
|
+
required: p.required,
|
|
56
|
+
default_value: p.defaultValue
|
|
57
|
+
}));
|
|
58
|
+
const relPath = `./${path.basename(componentPath)}`;
|
|
59
|
+
const result = binding.generateVariants(relPath, nativeProps, {
|
|
60
|
+
max_variants: options.maxVariants,
|
|
61
|
+
include_default: options.includeDefault,
|
|
62
|
+
include_boolean_toggles: options.includeBooleanToggles,
|
|
63
|
+
include_enum_variants: options.includeEnumVariants,
|
|
64
|
+
include_boundary_values: options.includeBoundaryValues,
|
|
65
|
+
include_empty_strings: options.includeEmptyStrings
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
variants: result.variants.map((v) => ({
|
|
69
|
+
name: v.name,
|
|
70
|
+
isDefault: v.is_default,
|
|
71
|
+
props: v.props,
|
|
72
|
+
description: v.description
|
|
73
|
+
})),
|
|
74
|
+
artFileContent: result.art_file_content,
|
|
75
|
+
componentName: result.component_name
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return generateArtFileJs(componentPath, props, options);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Write generated .art.vue file to disk.
|
|
82
|
+
*/
|
|
83
|
+
async function writeArtFile(componentPath, options = {}, outputPath) {
|
|
84
|
+
const output = await generateArtFile(componentPath, options);
|
|
85
|
+
const targetPath = outputPath ?? componentPath.replace(/\.vue$/, ".art.vue");
|
|
86
|
+
await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });
|
|
87
|
+
await fs.promises.writeFile(targetPath, output.artFileContent, "utf-8");
|
|
88
|
+
return targetPath;
|
|
89
|
+
}
|
|
90
|
+
function extractPropsSimple(source) {
|
|
91
|
+
const props = [];
|
|
92
|
+
const propsMatch = source.match(/defineProps\s*<\s*\{([^}]*)\}\s*>/s);
|
|
93
|
+
if (propsMatch) {
|
|
94
|
+
const propsBlock = propsMatch[1];
|
|
95
|
+
const propLines = propsBlock.split("\n");
|
|
96
|
+
for (const line of propLines) {
|
|
97
|
+
const propMatch = line.trim().match(/^(\w+)(\?)?:\s*(.+?)\s*;?\s*$/);
|
|
98
|
+
if (propMatch) props.push({
|
|
99
|
+
name: propMatch[1],
|
|
100
|
+
propType: propMatch[3].replace(/,\s*$/, ""),
|
|
101
|
+
required: !propMatch[2]
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return props;
|
|
106
|
+
}
|
|
107
|
+
function generateMinimalArt(componentName, componentPath) {
|
|
108
|
+
return `<art title="${componentName}" component="${componentPath}">
|
|
109
|
+
<variant name="Default" default>
|
|
110
|
+
<${componentName} />
|
|
111
|
+
</variant>
|
|
112
|
+
</art>
|
|
113
|
+
|
|
114
|
+
<script setup lang="ts">
|
|
115
|
+
import ${componentName} from '${componentPath}'
|
|
116
|
+
</script>
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
function generateArtFileJs(componentPath, props, options) {
|
|
120
|
+
const componentName = path.basename(componentPath, ".vue");
|
|
121
|
+
const relPath = `./${path.basename(componentPath)}`;
|
|
122
|
+
const maxVariants = options.maxVariants ?? 20;
|
|
123
|
+
const variants = [];
|
|
124
|
+
if (options.includeDefault !== false) {
|
|
125
|
+
const defaultProps = {};
|
|
126
|
+
for (const prop of props) if (prop.defaultValue !== void 0) defaultProps[prop.name] = prop.defaultValue;
|
|
127
|
+
variants.push({
|
|
128
|
+
name: "Default",
|
|
129
|
+
isDefault: true,
|
|
130
|
+
props: defaultProps,
|
|
131
|
+
description: `${componentName} with default props`
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (options.includeEnumVariants !== false) for (const prop of props) {
|
|
135
|
+
const unionValues = parseUnionType(prop.propType);
|
|
136
|
+
for (const val of unionValues) {
|
|
137
|
+
if (variants.length >= maxVariants) break;
|
|
138
|
+
const name = typeof val === "string" ? toPascalCase(val) : `${toPascalCase(prop.name)}_${String(val)}`;
|
|
139
|
+
variants.push({
|
|
140
|
+
name,
|
|
141
|
+
isDefault: false,
|
|
142
|
+
props: { [prop.name]: val },
|
|
143
|
+
description: `${prop.name} = ${JSON.stringify(val)}`
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (options.includeBooleanToggles !== false) for (const prop of props) {
|
|
148
|
+
if (variants.length >= maxVariants) break;
|
|
149
|
+
if (prop.propType.toLowerCase() === "boolean") {
|
|
150
|
+
const nonDefault = prop.defaultValue === true ? false : true;
|
|
151
|
+
variants.push({
|
|
152
|
+
name: nonDefault ? toPascalCase(prop.name) : `No${toPascalCase(prop.name)}`,
|
|
153
|
+
isDefault: false,
|
|
154
|
+
props: { [prop.name]: nonDefault },
|
|
155
|
+
description: `${prop.name} = ${nonDefault}`
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
let content = `<art title="${componentName}" component="${relPath}">\n`;
|
|
160
|
+
for (const variant of variants) {
|
|
161
|
+
const attrs = variant.isDefault ? `name="${variant.name}" default` : `name="${variant.name}"`;
|
|
162
|
+
content += ` <variant ${attrs}>\n`;
|
|
163
|
+
const propsStr = Object.entries(variant.props).map(([k, v]) => {
|
|
164
|
+
if (typeof v === "string") return `${k}="${v}"`;
|
|
165
|
+
if (typeof v === "boolean" && v) return k;
|
|
166
|
+
if (typeof v === "boolean" && !v) return `:${k}="false"`;
|
|
167
|
+
return `:${k}="${JSON.stringify(v)}"`;
|
|
168
|
+
}).join(" ");
|
|
169
|
+
content += ` <${componentName}${propsStr ? " " + propsStr : ""} />\n`;
|
|
170
|
+
content += ` </variant>\n\n`;
|
|
171
|
+
}
|
|
172
|
+
content += `</art>\n\n<script setup lang="ts">\nimport ${componentName} from '${relPath}'\n</script>\n`;
|
|
173
|
+
return {
|
|
174
|
+
variants,
|
|
175
|
+
artFileContent: content,
|
|
176
|
+
componentName
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function parseUnionType(typeStr) {
|
|
180
|
+
const trimmed = typeStr.trim();
|
|
181
|
+
if (!trimmed.includes("|")) return [];
|
|
182
|
+
if (trimmed.includes("'") || trimmed.includes("\"")) return trimmed.split("|").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter((s) => s.length > 0);
|
|
183
|
+
const parts = trimmed.split("|").map((s) => s.trim());
|
|
184
|
+
if (parts.every((p) => !isNaN(Number(p)))) return parts.map(Number);
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
function toPascalCase(str) {
|
|
188
|
+
return str.split(/[\s\-_]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
192
|
+
export { generateArtFile, writeArtFile };
|
|
193
|
+
//# sourceMappingURL=autogen-ymQnARZK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autogen-ymQnARZK.js","names":["native: NativeAutogen | null","componentPath: string","options: AutogenOptions","props: PropDefinition[]","outputPath?: string","source: string","componentName: string","variants: GeneratedVariant[]","defaultProps: Record<string, unknown>","typeStr: string","str: string"],"sources":["../src/autogen.ts"],"sourcesContent":["/**\n * Variant auto-generation module.\n * Generates .art.vue files from component prop analysis.\n */\n\nimport { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Autogen configuration options.\n */\nexport interface AutogenOptions {\n /** Maximum number of variants to generate (default: 20) */\n maxVariants?: number;\n /** Include a \"Default\" variant with all default values (default: true) */\n includeDefault?: boolean;\n /** Include boolean toggle variants (default: true) */\n includeBooleanToggles?: boolean;\n /** Include enum/union variants (default: true) */\n includeEnumVariants?: boolean;\n /** Include boundary value variants for numbers (default: false) */\n includeBoundaryValues?: boolean;\n /** Include empty string variants for optional strings (default: false) */\n includeEmptyStrings?: boolean;\n}\n\n/**\n * Prop definition for variant generation.\n */\nexport interface PropDefinition {\n name: string;\n propType: string;\n required: boolean;\n defaultValue?: unknown;\n}\n\n/**\n * Generated variant.\n */\nexport interface GeneratedVariant {\n name: string;\n isDefault: boolean;\n props: Record<string, unknown>;\n description?: string;\n}\n\n/**\n * Autogen output.\n */\nexport interface AutogenOutput {\n variants: GeneratedVariant[];\n artFileContent: string;\n componentName: string;\n}\n\n// Native binding types\ninterface NativeAutogen {\n generateVariants?: (\n componentPath: string,\n props: Array<{\n name: string;\n prop_type: string;\n required: boolean;\n default_value?: unknown;\n }>,\n config?: {\n max_variants?: number;\n include_default?: boolean;\n include_boolean_toggles?: boolean;\n include_enum_variants?: boolean;\n include_boundary_values?: boolean;\n include_empty_strings?: boolean;\n },\n ) => {\n variants: Array<{\n name: string;\n is_default: boolean;\n props: Record<string, unknown>;\n description?: string;\n }>;\n art_file_content: string;\n component_name: string;\n };\n analyzeSfc?: (\n source: string,\n options?: { filename?: string },\n ) => {\n props: Array<{ name: string; type: string; required: boolean; default_value?: unknown }>;\n emits: string[];\n };\n}\n\nlet native: NativeAutogen | null = null;\n\nfunction loadNative(): NativeAutogen {\n if (native) return native;\n const require = createRequire(import.meta.url);\n try {\n native = require(\"@vizejs/native\") as NativeAutogen;\n return native;\n } catch (e) {\n throw new Error(\n `Failed to load @vizejs/native. Make sure it's installed and built:\\n${String(e)}`,\n );\n }\n}\n\n/**\n * Generate .art.vue file for a component.\n *\n * @param componentPath - Path to the Vue component file\n * @param options - Auto-generation options\n * @returns Generated .art.vue content and metadata\n */\nexport async function generateArtFile(\n componentPath: string,\n options: AutogenOptions = {},\n): Promise<AutogenOutput> {\n const absolutePath = path.resolve(componentPath);\n const source = await fs.promises.readFile(absolutePath, \"utf-8\");\n\n const binding = loadNative();\n\n // Analyze component to extract props\n let props: PropDefinition[];\n if (binding.analyzeSfc) {\n const analysis = binding.analyzeSfc(source, { filename: absolutePath });\n props = analysis.props.map((p) => ({\n name: p.name,\n propType: p.type,\n required: p.required,\n defaultValue: p.default_value,\n }));\n } else {\n // Fallback: simple regex-based prop extraction\n props = extractPropsSimple(source);\n }\n\n if (props.length === 0) {\n // No props found: generate minimal art file\n const componentName = path.basename(componentPath, \".vue\");\n const relPath = `./${path.basename(componentPath)}`;\n return {\n variants: [{ name: \"Default\", isDefault: true, props: {} }],\n artFileContent: generateMinimalArt(componentName, relPath),\n componentName,\n };\n }\n\n // Use native variant generation if available\n if (binding.generateVariants) {\n const nativeProps = props.map((p) => ({\n name: p.name,\n prop_type: p.propType,\n required: p.required,\n default_value: p.defaultValue,\n }));\n\n const relPath = `./${path.basename(componentPath)}`;\n const result = binding.generateVariants(relPath, nativeProps, {\n max_variants: options.maxVariants,\n include_default: options.includeDefault,\n include_boolean_toggles: options.includeBooleanToggles,\n include_enum_variants: options.includeEnumVariants,\n include_boundary_values: options.includeBoundaryValues,\n include_empty_strings: options.includeEmptyStrings,\n });\n\n return {\n variants: result.variants.map((v) => ({\n name: v.name,\n isDefault: v.is_default,\n props: v.props,\n description: v.description,\n })),\n artFileContent: result.art_file_content,\n componentName: result.component_name,\n };\n }\n\n // Fallback: JS-based generation\n return generateArtFileJs(componentPath, props, options);\n}\n\n/**\n * Write generated .art.vue file to disk.\n */\nexport async function writeArtFile(\n componentPath: string,\n options: AutogenOptions = {},\n outputPath?: string,\n): Promise<string> {\n const output = await generateArtFile(componentPath, options);\n\n const targetPath = outputPath ?? componentPath.replace(/\\.vue$/, \".art.vue\");\n\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.writeFile(targetPath, output.artFileContent, \"utf-8\");\n\n return targetPath;\n}\n\n// Simple prop extraction fallback (when native binding not available)\nfunction extractPropsSimple(source: string): PropDefinition[] {\n const props: PropDefinition[] = [];\n\n // Match defineProps<{ ... }>() or defineProps({ ... })\n const propsMatch = source.match(/defineProps\\s*<\\s*\\{([^}]*)\\}\\s*>/s);\n\n if (propsMatch) {\n const propsBlock = propsMatch[1];\n const propLines = propsBlock.split(\"\\n\");\n\n for (const line of propLines) {\n const propMatch = line.trim().match(/^(\\w+)(\\?)?:\\s*(.+?)\\s*;?\\s*$/);\n if (propMatch) {\n props.push({\n name: propMatch[1],\n propType: propMatch[3].replace(/,\\s*$/, \"\"),\n required: !propMatch[2],\n });\n }\n }\n }\n\n return props;\n}\n\n// Minimal art file for components with no props\nfunction generateMinimalArt(componentName: string, componentPath: string): string {\n return `<art title=\"${componentName}\" component=\"${componentPath}\">\n <variant name=\"Default\" default>\n <${componentName} />\n </variant>\n</art>\n\n<script setup lang=\"ts\">\nimport ${componentName} from '${componentPath}'\n</script>\n`;\n}\n\n// JS-based variant generation fallback\nfunction generateArtFileJs(\n componentPath: string,\n props: PropDefinition[],\n options: AutogenOptions,\n): AutogenOutput {\n const componentName = path.basename(componentPath, \".vue\");\n const relPath = `./${path.basename(componentPath)}`;\n const maxVariants = options.maxVariants ?? 20;\n const variants: GeneratedVariant[] = [];\n\n // Default variant\n if (options.includeDefault !== false) {\n const defaultProps: Record<string, unknown> = {};\n for (const prop of props) {\n if (prop.defaultValue !== undefined) {\n defaultProps[prop.name] = prop.defaultValue;\n }\n }\n variants.push({\n name: \"Default\",\n isDefault: true,\n props: defaultProps,\n description: `${componentName} with default props`,\n });\n }\n\n // Enum variants\n if (options.includeEnumVariants !== false) {\n for (const prop of props) {\n const unionValues = parseUnionType(prop.propType);\n for (const val of unionValues) {\n if (variants.length >= maxVariants) break;\n const name =\n typeof val === \"string\" ? toPascalCase(val) : `${toPascalCase(prop.name)}_${String(val)}`;\n variants.push({\n name,\n isDefault: false,\n props: { [prop.name]: val },\n description: `${prop.name} = ${JSON.stringify(val)}`,\n });\n }\n }\n }\n\n // Boolean toggle variants\n if (options.includeBooleanToggles !== false) {\n for (const prop of props) {\n if (variants.length >= maxVariants) break;\n if (prop.propType.toLowerCase() === \"boolean\") {\n const nonDefault = prop.defaultValue === true ? false : true;\n variants.push({\n name: nonDefault ? toPascalCase(prop.name) : `No${toPascalCase(prop.name)}`,\n isDefault: false,\n props: { [prop.name]: nonDefault },\n description: `${prop.name} = ${nonDefault}`,\n });\n }\n }\n }\n\n // Generate art file content\n let content = `<art title=\"${componentName}\" component=\"${relPath}\">\\n`;\n for (const variant of variants) {\n const attrs = variant.isDefault ? `name=\"${variant.name}\" default` : `name=\"${variant.name}\"`;\n content += ` <variant ${attrs}>\\n`;\n\n const propsStr = Object.entries(variant.props)\n .map(([k, v]) => {\n if (typeof v === \"string\") return `${k}=\"${v}\"`;\n if (typeof v === \"boolean\" && v) return k;\n if (typeof v === \"boolean\" && !v) return `:${k}=\"false\"`;\n return `:${k}=\"${JSON.stringify(v)}\"`;\n })\n .join(\" \");\n\n content += ` <${componentName}${propsStr ? \" \" + propsStr : \"\"} />\\n`;\n content += ` </variant>\\n\\n`;\n }\n content += `</art>\\n\\n<script setup lang=\"ts\">\\nimport ${componentName} from '${relPath}'\\n</script>\\n`;\n\n return {\n variants,\n artFileContent: content,\n componentName,\n };\n}\n\nfunction parseUnionType(typeStr: string): unknown[] {\n const trimmed = typeStr.trim();\n if (!trimmed.includes(\"|\")) return [];\n\n if (trimmed.includes(\"'\") || trimmed.includes('\"')) {\n return trimmed\n .split(\"|\")\n .map((s) => s.trim().replace(/^['\"]|['\"]$/g, \"\"))\n .filter((s) => s.length > 0);\n }\n\n const parts = trimmed.split(\"|\").map((s) => s.trim());\n if (parts.every((p) => !isNaN(Number(p)))) {\n return parts.map(Number);\n }\n\n return [];\n}\n\nfunction toPascalCase(str: string): string {\n return str\n .split(/[\\s\\-_]+/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { AutogenOptions, AutogenOutput, GeneratedVariant, PropDefinition, generateArtFile$1 as generateArtFile, writeArtFile$1 as writeArtFile } from "./autogen-D3Zjc3zI.js";
|
|
2
|
+
export { AutogenOptions, AutogenOutput, GeneratedVariant, PropDefinition, generateArtFile, writeArtFile };
|
package/dist/autogen.js
ADDED
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { MuseaVrtRunner, generateVrtJsonReport, generateVrtReport } from "./vrt-DP87vGIA.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
|
|
6
|
+
//#region src/cli.ts
|
|
7
|
+
function parseArgs(args) {
|
|
8
|
+
const options = {
|
|
9
|
+
command: "run",
|
|
10
|
+
update: false,
|
|
11
|
+
config: "vite.config.ts",
|
|
12
|
+
output: ".vize",
|
|
13
|
+
threshold: .1,
|
|
14
|
+
json: false,
|
|
15
|
+
ci: false,
|
|
16
|
+
a11y: false,
|
|
17
|
+
help: false,
|
|
18
|
+
baseUrl: "http://localhost:5173"
|
|
19
|
+
};
|
|
20
|
+
let i = 0;
|
|
21
|
+
if (args.length > 0 && !args[0].startsWith("-")) {
|
|
22
|
+
const sub = args[0];
|
|
23
|
+
if (sub === "approve") {
|
|
24
|
+
options.command = "approve";
|
|
25
|
+
i = 1;
|
|
26
|
+
if (args.length > 1 && !args[1].startsWith("-")) {
|
|
27
|
+
options.pattern = args[1];
|
|
28
|
+
i = 2;
|
|
29
|
+
}
|
|
30
|
+
} else if (sub === "clean") {
|
|
31
|
+
options.command = "clean";
|
|
32
|
+
i = 1;
|
|
33
|
+
} else if (sub === "generate") {
|
|
34
|
+
options.command = "generate";
|
|
35
|
+
i = 1;
|
|
36
|
+
if (args.length > 1 && !args[1].startsWith("-")) {
|
|
37
|
+
options.componentPath = args[1];
|
|
38
|
+
i = 2;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (; i < args.length; i++) {
|
|
43
|
+
const arg = args[i];
|
|
44
|
+
switch (arg) {
|
|
45
|
+
case "-u":
|
|
46
|
+
case "--update":
|
|
47
|
+
options.update = true;
|
|
48
|
+
break;
|
|
49
|
+
case "-c":
|
|
50
|
+
case "--config":
|
|
51
|
+
options.config = args[++i] || "vite.config.ts";
|
|
52
|
+
break;
|
|
53
|
+
case "-o":
|
|
54
|
+
case "--output":
|
|
55
|
+
options.output = args[++i] || ".vize";
|
|
56
|
+
break;
|
|
57
|
+
case "-t":
|
|
58
|
+
case "--threshold":
|
|
59
|
+
options.threshold = parseFloat(args[++i]) || .1;
|
|
60
|
+
break;
|
|
61
|
+
case "--json":
|
|
62
|
+
options.json = true;
|
|
63
|
+
break;
|
|
64
|
+
case "--ci":
|
|
65
|
+
options.ci = true;
|
|
66
|
+
break;
|
|
67
|
+
case "--a11y":
|
|
68
|
+
options.a11y = true;
|
|
69
|
+
break;
|
|
70
|
+
case "-b":
|
|
71
|
+
case "--base-url":
|
|
72
|
+
options.baseUrl = args[++i] || "http://localhost:5173";
|
|
73
|
+
break;
|
|
74
|
+
case "-h":
|
|
75
|
+
case "--help":
|
|
76
|
+
options.help = true;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return options;
|
|
81
|
+
}
|
|
82
|
+
function printHelp() {
|
|
83
|
+
console.log(`
|
|
84
|
+
Musea VRT - Visual Regression Testing for Component Gallery
|
|
85
|
+
|
|
86
|
+
Usage:
|
|
87
|
+
musea-vrt [command] [options]
|
|
88
|
+
|
|
89
|
+
Commands:
|
|
90
|
+
(default) Run VRT tests
|
|
91
|
+
approve [pattern] Approve failed snapshots and update baselines
|
|
92
|
+
Optional pattern filters which snapshots to approve
|
|
93
|
+
clean Remove orphaned snapshots (no matching art/variant)
|
|
94
|
+
generate <component> Auto-generate .art.vue from a Vue component
|
|
95
|
+
|
|
96
|
+
Options:
|
|
97
|
+
-u, --update Update baseline snapshots with current screenshots
|
|
98
|
+
-c, --config <path> Path to vite config file (default: vite.config.ts)
|
|
99
|
+
-o, --output <dir> Output directory for reports (default: .vize)
|
|
100
|
+
-t, --threshold <n> Diff threshold percentage (default: 0.1)
|
|
101
|
+
-b, --base-url <url> Base URL for dev server (default: http://localhost:5173)
|
|
102
|
+
--json Output JSON report instead of HTML
|
|
103
|
+
--ci CI mode - exit with non-zero code on failures
|
|
104
|
+
--a11y Run accessibility audits alongside VRT
|
|
105
|
+
-h, --help Show this help message
|
|
106
|
+
|
|
107
|
+
Examples:
|
|
108
|
+
# Run VRT tests
|
|
109
|
+
musea-vrt
|
|
110
|
+
|
|
111
|
+
# Update baseline snapshots
|
|
112
|
+
musea-vrt -u
|
|
113
|
+
|
|
114
|
+
# Run with custom threshold
|
|
115
|
+
musea-vrt -t 0.5
|
|
116
|
+
|
|
117
|
+
# CI mode with JSON output
|
|
118
|
+
musea-vrt --ci --json
|
|
119
|
+
|
|
120
|
+
# Run with accessibility audits
|
|
121
|
+
musea-vrt --a11y
|
|
122
|
+
|
|
123
|
+
# Approve all failed snapshots
|
|
124
|
+
musea-vrt approve
|
|
125
|
+
|
|
126
|
+
# Approve specific snapshots by pattern
|
|
127
|
+
musea-vrt approve "Button/*"
|
|
128
|
+
|
|
129
|
+
# Clean orphaned snapshots
|
|
130
|
+
musea-vrt clean
|
|
131
|
+
|
|
132
|
+
# Auto-generate .art.vue from component
|
|
133
|
+
musea-vrt generate src/components/Button.vue
|
|
134
|
+
|
|
135
|
+
# Custom base URL
|
|
136
|
+
musea-vrt -b http://localhost:3000
|
|
137
|
+
`);
|
|
138
|
+
}
|
|
139
|
+
async function scanArtFiles(root) {
|
|
140
|
+
const files = [];
|
|
141
|
+
async function scan(dir) {
|
|
142
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
const fullPath = path.join(dir, entry.name);
|
|
145
|
+
if (entry.name === "node_modules" || entry.name === "dist") continue;
|
|
146
|
+
if (entry.isDirectory()) await scan(fullPath);
|
|
147
|
+
else if (entry.isFile() && entry.name.endsWith(".art.vue")) files.push(fullPath);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
await scan(root);
|
|
151
|
+
return files;
|
|
152
|
+
}
|
|
153
|
+
async function parseArtFile(filePath) {
|
|
154
|
+
try {
|
|
155
|
+
const source = await fs.promises.readFile(filePath, "utf-8");
|
|
156
|
+
const titleMatch = source.match(/<art[^>]*\stitle=["']([^"']+)["']/);
|
|
157
|
+
const componentMatch = source.match(/<art[^>]*\scomponent=["']([^"']+)["']/);
|
|
158
|
+
const categoryMatch = source.match(/<art[^>]*\scategory=["']([^"']+)["']/);
|
|
159
|
+
const variants = [];
|
|
160
|
+
const variantRegex = /<variant\s+([^>]*)>([\s\S]*?)<\/variant>/g;
|
|
161
|
+
let match;
|
|
162
|
+
while ((match = variantRegex.exec(source)) !== null) {
|
|
163
|
+
const attrs = match[1];
|
|
164
|
+
const template = match[2].trim();
|
|
165
|
+
const nameMatch = attrs.match(/name=["']([^"']+)["']/);
|
|
166
|
+
const isDefault = /\bdefault\b/.test(attrs);
|
|
167
|
+
const skipVrt = /\bskip-vrt\b/.test(attrs);
|
|
168
|
+
if (nameMatch) variants.push({
|
|
169
|
+
name: nameMatch[1],
|
|
170
|
+
template,
|
|
171
|
+
isDefault,
|
|
172
|
+
skipVrt
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
path: filePath,
|
|
177
|
+
metadata: {
|
|
178
|
+
title: titleMatch?.[1] || path.basename(filePath, ".art.vue"),
|
|
179
|
+
component: componentMatch?.[1],
|
|
180
|
+
category: categoryMatch?.[1],
|
|
181
|
+
tags: [],
|
|
182
|
+
status: "ready"
|
|
183
|
+
},
|
|
184
|
+
variants,
|
|
185
|
+
hasScriptSetup: /<script\s+setup/.test(source),
|
|
186
|
+
hasScript: /<script(?!\s+setup)/.test(source),
|
|
187
|
+
styleCount: (source.match(/<style/g) || []).length
|
|
188
|
+
};
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error(`Failed to parse ${filePath}:`, error);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function runVrt(options, artFiles) {
|
|
195
|
+
const totalVariants = artFiles.reduce((sum, art) => sum + art.variants.filter((v) => !v.skipVrt).length, 0);
|
|
196
|
+
console.log(` Testing ${totalVariants} variant(s) across ${artFiles.length} art file(s)\n`);
|
|
197
|
+
const vrtOptions = {
|
|
198
|
+
snapshotDir: path.join(options.output, "snapshots"),
|
|
199
|
+
threshold: options.threshold
|
|
200
|
+
};
|
|
201
|
+
const runner = new MuseaVrtRunner({
|
|
202
|
+
...vrtOptions,
|
|
203
|
+
ci: options.ci ? {
|
|
204
|
+
failOnDiff: true,
|
|
205
|
+
jsonReport: options.json
|
|
206
|
+
} : void 0
|
|
207
|
+
});
|
|
208
|
+
try {
|
|
209
|
+
console.log(" Launching browser...");
|
|
210
|
+
await runner.init();
|
|
211
|
+
console.log(" Running visual regression tests...\n");
|
|
212
|
+
const results = await runner.runAllTests(artFiles, options.baseUrl);
|
|
213
|
+
const summary = runner.getSummary(results);
|
|
214
|
+
console.log(" Results:");
|
|
215
|
+
console.log(" ---------");
|
|
216
|
+
console.log(` Passed: ${summary.passed}`);
|
|
217
|
+
console.log(` Failed: ${summary.failed}`);
|
|
218
|
+
console.log(` New: ${summary.new}`);
|
|
219
|
+
console.log(` Skipped: ${summary.skipped}`);
|
|
220
|
+
console.log(` Total: ${summary.total}`);
|
|
221
|
+
console.log(` Duration: ${(summary.duration / 1e3).toFixed(2)}s\n`);
|
|
222
|
+
if (options.a11y) {
|
|
223
|
+
console.log(" Running accessibility audits...\n");
|
|
224
|
+
try {
|
|
225
|
+
const { MuseaA11yRunner } = await import("./a11y.js");
|
|
226
|
+
const a11yRunner = new MuseaA11yRunner();
|
|
227
|
+
const a11yResults = await a11yRunner.runAudits(artFiles, options.baseUrl, runner);
|
|
228
|
+
const a11ySummary = a11yRunner.getSummary(a11yResults);
|
|
229
|
+
console.log(" A11y Results:");
|
|
230
|
+
console.log(" -------------");
|
|
231
|
+
console.log(` Components: ${a11ySummary.totalComponents}`);
|
|
232
|
+
console.log(` Variants: ${a11ySummary.totalVariants}`);
|
|
233
|
+
console.log(` Violations: ${a11ySummary.totalViolations}`);
|
|
234
|
+
console.log(` Critical: ${a11ySummary.criticalCount}`);
|
|
235
|
+
console.log(` Serious: ${a11ySummary.seriousCount}\n`);
|
|
236
|
+
const reportDir$1 = options.output;
|
|
237
|
+
await fs.promises.mkdir(reportDir$1, { recursive: true });
|
|
238
|
+
if (options.json) {
|
|
239
|
+
const a11yJson = a11yRunner.generateJsonReport(a11yResults);
|
|
240
|
+
const a11yPath = path.join(reportDir$1, "a11y-report.json");
|
|
241
|
+
await fs.promises.writeFile(a11yPath, a11yJson);
|
|
242
|
+
console.log(` A11y JSON report: ${a11yPath}\n`);
|
|
243
|
+
} else {
|
|
244
|
+
const a11yHtml = a11yRunner.generateHtmlReport(a11yResults);
|
|
245
|
+
const a11yPath = path.join(reportDir$1, "a11y-report.html");
|
|
246
|
+
await fs.promises.writeFile(a11yPath, a11yHtml);
|
|
247
|
+
console.log(` A11y HTML report: ${a11yPath}\n`);
|
|
248
|
+
}
|
|
249
|
+
if (options.ci && (a11ySummary.criticalCount > 0 || a11ySummary.seriousCount > 0)) {
|
|
250
|
+
console.log(" CI mode: Accessibility violations found\n");
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
} catch (e) {
|
|
254
|
+
console.warn(" A11y audits skipped:", e instanceof Error ? e.message : String(e));
|
|
255
|
+
console.warn(" Make sure axe-core is installed: npm install axe-core\n");
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (options.update) {
|
|
259
|
+
console.log(" Updating baselines...");
|
|
260
|
+
const updated = await runner.updateBaselines(results);
|
|
261
|
+
console.log(` Updated ${updated} baseline(s)\n`);
|
|
262
|
+
}
|
|
263
|
+
const reportDir = options.output;
|
|
264
|
+
await fs.promises.mkdir(reportDir, { recursive: true });
|
|
265
|
+
if (options.json) {
|
|
266
|
+
const jsonReport = generateVrtJsonReport(results, summary);
|
|
267
|
+
const jsonPath = path.join(reportDir, "vrt-report.json");
|
|
268
|
+
await fs.promises.writeFile(jsonPath, jsonReport);
|
|
269
|
+
console.log(` JSON report: ${jsonPath}\n`);
|
|
270
|
+
} else {
|
|
271
|
+
const htmlReport = generateVrtReport(results, summary);
|
|
272
|
+
const htmlPath = path.join(reportDir, "vrt-report.html");
|
|
273
|
+
await fs.promises.writeFile(htmlPath, htmlReport);
|
|
274
|
+
console.log(` HTML report: ${htmlPath}\n`);
|
|
275
|
+
}
|
|
276
|
+
if (options.ci && summary.failed > 0) {
|
|
277
|
+
console.log(" CI mode: Exiting with error due to failures\n");
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
} finally {
|
|
281
|
+
await runner.close();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async function runApprove(options, artFiles) {
|
|
285
|
+
const vrtOptions = {
|
|
286
|
+
snapshotDir: path.join(options.output, "snapshots"),
|
|
287
|
+
threshold: options.threshold
|
|
288
|
+
};
|
|
289
|
+
const runner = new MuseaVrtRunner(vrtOptions);
|
|
290
|
+
try {
|
|
291
|
+
console.log(" Launching browser...");
|
|
292
|
+
await runner.init();
|
|
293
|
+
console.log(" Running tests to find diffs...\n");
|
|
294
|
+
const results = await runner.runAllTests(artFiles, options.baseUrl);
|
|
295
|
+
const failed = results.filter((r) => !r.passed && !r.error);
|
|
296
|
+
if (failed.length === 0) {
|
|
297
|
+
console.log(" No failed tests to approve.\n");
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const pattern = options.pattern;
|
|
301
|
+
if (pattern) console.log(` Approving snapshots matching: ${pattern}\n`);
|
|
302
|
+
else console.log(` Approving all ${failed.length} failed snapshot(s)...\n`);
|
|
303
|
+
const approved = await runner.approveResults(results, pattern);
|
|
304
|
+
console.log(` Approved ${approved} snapshot(s)\n`);
|
|
305
|
+
} finally {
|
|
306
|
+
await runner.close();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async function runClean(options, artFiles) {
|
|
310
|
+
const vrtOptions = {
|
|
311
|
+
snapshotDir: path.join(options.output, "snapshots"),
|
|
312
|
+
threshold: options.threshold
|
|
313
|
+
};
|
|
314
|
+
const runner = new MuseaVrtRunner(vrtOptions);
|
|
315
|
+
console.log(" Scanning for orphaned snapshots...\n");
|
|
316
|
+
const cleaned = await runner.cleanOrphans(artFiles);
|
|
317
|
+
if (cleaned === 0) console.log(" No orphaned snapshots found.\n");
|
|
318
|
+
else console.log(`\n Cleaned ${cleaned} orphaned snapshot(s)\n`);
|
|
319
|
+
}
|
|
320
|
+
async function runGenerate(options) {
|
|
321
|
+
if (!options.componentPath) {
|
|
322
|
+
console.error(" Error: Missing component path.");
|
|
323
|
+
console.error(" Usage: musea-vrt generate <component.vue>\n");
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
const componentPath = path.resolve(options.componentPath);
|
|
327
|
+
try {
|
|
328
|
+
await fs.promises.access(componentPath);
|
|
329
|
+
} catch {
|
|
330
|
+
console.error(` Error: File not found: ${componentPath}\n`);
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
console.log(` Generating art file for: ${path.relative(process.cwd(), componentPath)}\n`);
|
|
334
|
+
try {
|
|
335
|
+
const { writeArtFile } = await import("./autogen.js");
|
|
336
|
+
const outputPath = await writeArtFile(componentPath);
|
|
337
|
+
const relOutput = path.relative(process.cwd(), outputPath);
|
|
338
|
+
console.log(` Generated: ${relOutput}\n`);
|
|
339
|
+
} catch (e) {
|
|
340
|
+
console.error(" Generation failed:", e instanceof Error ? e.message : String(e));
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async function main() {
|
|
345
|
+
const args = process.argv.slice(2);
|
|
346
|
+
const options = parseArgs(args);
|
|
347
|
+
if (options.help) {
|
|
348
|
+
printHelp();
|
|
349
|
+
process.exit(0);
|
|
350
|
+
}
|
|
351
|
+
const cwd = process.cwd();
|
|
352
|
+
console.log("\n Musea VRT");
|
|
353
|
+
console.log(" =========\n");
|
|
354
|
+
if (options.command === "generate") {
|
|
355
|
+
try {
|
|
356
|
+
await runGenerate(options);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error("\n Error:", error);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
console.log(" Scanning for art files...");
|
|
364
|
+
const artFilePaths = await scanArtFiles(cwd);
|
|
365
|
+
if (artFilePaths.length === 0) {
|
|
366
|
+
console.log(" No art files found.\n");
|
|
367
|
+
process.exit(0);
|
|
368
|
+
}
|
|
369
|
+
console.log(` Found ${artFilePaths.length} art file(s)\n`);
|
|
370
|
+
const artFiles = [];
|
|
371
|
+
for (const filePath of artFilePaths) {
|
|
372
|
+
const art = await parseArtFile(filePath);
|
|
373
|
+
if (art) artFiles.push(art);
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
switch (options.command) {
|
|
377
|
+
case "run":
|
|
378
|
+
await runVrt(options, artFiles);
|
|
379
|
+
break;
|
|
380
|
+
case "approve":
|
|
381
|
+
await runApprove(options, artFiles);
|
|
382
|
+
break;
|
|
383
|
+
case "clean":
|
|
384
|
+
await runClean(options, artFiles);
|
|
385
|
+
break;
|
|
386
|
+
case "generate": break;
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.error("\n Error:", error);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
main().catch((error) => {
|
|
394
|
+
console.error("Fatal error:", error);
|
|
395
|
+
process.exit(1);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
//#endregion
|
|
399
|
+
//# sourceMappingURL=cli.js.map
|