@vizejs/vite-plugin-musea 0.429.1 → 0.430.1
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/a11y/index.d.mts +1 -1
- package/dist/a11y/index.mjs +1 -1
- package/dist/{a11y-Ci9EEv6F.mjs → a11y-BbFK942j.mjs} +66 -15
- package/dist/a11y-BbFK942j.mjs.map +1 -0
- package/dist/api-gMwahZKm.d.mts +112 -0
- package/dist/api-gMwahZKm.d.mts.map +1 -0
- package/dist/cli/index.d.mts +1 -1
- package/dist/cli/index.d.mts.map +1 -1
- package/dist/cli/index.mjs +79 -33
- package/dist/cli/index.mjs.map +1 -1
- package/dist/gallery/assets/{MonacoEditor-BIjYdDrA.js → MonacoEditor-CnzIyKMy.js} +2 -2
- package/dist/gallery/assets/{cssMode-C5PB9d_X.js → cssMode-DZ_me5CG.js} +1 -1
- package/dist/gallery/assets/{editor.api-tyf2wJeZ.js → editor.api-CjD4Hpms.js} +1 -1
- package/dist/gallery/assets/{freemarker2-Df2SP4C4.js → freemarker2-F7z49Hfe.js} +1 -1
- package/dist/gallery/assets/{handlebars-CbFr-5KQ.js → handlebars-Dt4eGgf-.js} +1 -1
- package/dist/gallery/assets/{html-C1ubjnt5.js → html-B1zmbcur.js} +1 -1
- package/dist/gallery/assets/{htmlMode-9FYaGqmo.js → htmlMode-DmDwpA4v.js} +1 -1
- package/dist/gallery/assets/{index-D81pMC9f.css → index-C4vm-yqv.css} +1 -1
- package/dist/gallery/assets/{index-BjW1cVGf.js → index-vJ3ftuqF.js} +3 -3
- package/dist/gallery/assets/{javascript-DjzYR9ls.js → javascript-CLKbyvz8.js} +1 -1
- package/dist/gallery/assets/{jsonMode-BRxSdIjW.js → jsonMode-sms9-L2I.js} +1 -1
- package/dist/gallery/assets/{liquid-DD0LqlcF.js → liquid-CesACmVT.js} +1 -1
- package/dist/gallery/assets/{lspLanguageFeatures-C0f4qlAU.js → lspLanguageFeatures-C8BR-41u.js} +1 -1
- package/dist/gallery/assets/{mdx-DtcWR2Lx.js → mdx-Cd93KUo_.js} +1 -1
- package/dist/gallery/assets/{python-C1OlrT9L.js → python-8gi7tgMv.js} +1 -1
- package/dist/gallery/assets/{razor-BN2Zj1B9.js → razor-sOyhBXSy.js} +1 -1
- package/dist/gallery/assets/{register-CTEjv03I.js → register-C-_4TcWq.js} +2 -2
- package/dist/gallery/assets/{toggleHighContrast-BELgo5Kd.js → toggleHighContrast-BCHobRx5.js} +1 -1
- package/dist/gallery/assets/{tsMode-Bd8a8shj.js → tsMode-BkQQD7P8.js} +1 -1
- package/dist/gallery/assets/{typescript-DLlFIuFu.js → typescript-XiwOdN8G.js} +1 -1
- package/dist/gallery/assets/{vs-YCtNwrNl.js → vs-Coo3fK7w.js} +3 -3
- package/dist/gallery/assets/{workers-CBv4Qjp-.js → workers-Cypb28_F.js} +1 -1
- package/dist/gallery/assets/{xml-wUPbJH_s.js → xml-CN1ixSLs.js} +1 -1
- package/dist/gallery/assets/{yaml-B52zgDg-.js → yaml-l30MdsZA.js} +1 -1
- package/dist/gallery/index.html +2 -2
- package/dist/{gallery-qE5deHL0.mjs → gallery-CIcrB07g.mjs} +2 -2
- package/dist/{gallery-qE5deHL0.mjs.map → gallery-CIcrB07g.mjs.map} +1 -1
- package/dist/index-C5evpfmF.d.mts +81 -0
- package/dist/index-C5evpfmF.d.mts.map +1 -0
- package/dist/index.d.mts +5 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +33 -9
- package/dist/index.mjs.map +1 -1
- package/dist/options-CZYdDtTW.mjs +581 -0
- package/dist/options-CZYdDtTW.mjs.map +1 -0
- package/dist/{plugin-CIrKPW8K.d.mts → plugin-DkFXWcCD.d.mts} +11 -2
- package/dist/plugin-DkFXWcCD.d.mts.map +1 -0
- package/dist/{template-DNhEgY8-.mjs → template-DDl7rsgo.mjs} +2 -2
- package/dist/{template-DNhEgY8-.mjs.map → template-DDl7rsgo.mjs.map} +1 -1
- package/dist/utils-DeMLVlNS.mjs +628 -0
- package/dist/utils-DeMLVlNS.mjs.map +1 -0
- package/dist/{vrt-DVub2B6r.d.mts → vrt-3T_e4bJ3.d.mts} +7 -1
- package/dist/vrt-3T_e4bJ3.d.mts.map +1 -0
- package/dist/{vrt-BPwRc95_.d.mts → vrt-6iDBfqNg.d.mts} +2 -2
- package/dist/{vrt-BPwRc95_.d.mts.map → vrt-6iDBfqNg.d.mts.map} +1 -1
- package/dist/{vrt-UxzWFygI.mjs → vrt-VuGK9wTX.mjs} +11 -4
- package/dist/vrt-VuGK9wTX.mjs.map +1 -0
- package/dist/vrt.d.mts +1 -1
- package/dist/vrt.mjs +1 -1
- package/package.json +4 -4
- package/dist/a11y-Ci9EEv6F.mjs.map +0 -1
- package/dist/index-HVtO9FK5.d.mts +0 -170
- package/dist/index-HVtO9FK5.d.mts.map +0 -1
- package/dist/options-Cd7bzLGr.mjs +0 -1142
- package/dist/options-Cd7bzLGr.mjs.map +0 -1
- package/dist/plugin-CIrKPW8K.d.mts.map +0 -1
- package/dist/vrt-DVub2B6r.d.mts.map +0 -1
- package/dist/vrt-UxzWFygI.mjs.map +0 -1
|
@@ -1,1142 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
5
|
-
//#region src/with-defaults.ts
|
|
6
|
-
/**
|
|
7
|
-
* Parsing helpers for extracting prop defaults from a Vue SFC `<script setup>`
|
|
8
|
-
* block. Defaults are read only from `withDefaults(defineProps(...), { ... })`
|
|
9
|
-
* so unrelated local identifiers are never treated as prop defaults.
|
|
10
|
-
*/
|
|
11
|
-
function extractWithDefaults(scriptContent) {
|
|
12
|
-
const defaults = /* @__PURE__ */ new Map();
|
|
13
|
-
let searchIndex = 0;
|
|
14
|
-
while (searchIndex < scriptContent.length) {
|
|
15
|
-
const calleeIndex = scriptContent.indexOf("withDefaults", searchIndex);
|
|
16
|
-
if (calleeIndex === -1) break;
|
|
17
|
-
searchIndex = calleeIndex + 12;
|
|
18
|
-
if (!isIdentifierBoundary(scriptContent[calleeIndex - 1])) continue;
|
|
19
|
-
if (!isIdentifierBoundary(scriptContent[searchIndex])) continue;
|
|
20
|
-
const parenIndex = skipWhitespace(scriptContent, searchIndex);
|
|
21
|
-
if (scriptContent[parenIndex] !== "(") continue;
|
|
22
|
-
const endParen = findMatching(scriptContent, parenIndex, "(", ")");
|
|
23
|
-
if (endParen === -1) continue;
|
|
24
|
-
const defaultsArg = splitTopLevel(scriptContent.slice(parenIndex + 1, endParen))[1]?.trim();
|
|
25
|
-
if (!defaultsArg?.startsWith("{")) continue;
|
|
26
|
-
const objectStart = scriptContent.indexOf(defaultsArg, parenIndex + 1);
|
|
27
|
-
const objectEnd = findMatching(scriptContent, objectStart, "{", "}");
|
|
28
|
-
if (objectEnd === -1) continue;
|
|
29
|
-
for (const [name, value] of objectProperties(scriptContent.slice(objectStart + 1, objectEnd))) defaults.set(name, value);
|
|
30
|
-
}
|
|
31
|
-
return defaults;
|
|
32
|
-
}
|
|
33
|
-
function objectProperties(objectBody) {
|
|
34
|
-
const properties = [];
|
|
35
|
-
for (const item of splitTopLevel(objectBody)) {
|
|
36
|
-
const colonIndex = topLevelColon(item);
|
|
37
|
-
if (colonIndex === -1) continue;
|
|
38
|
-
const rawKey = item.slice(0, colonIndex).trim();
|
|
39
|
-
const key = rawKey.match(/^[$A-Z_a-z][$\w]*$/)?.[0] ?? rawKey.match(/^["']([^"']+)["']$/)?.[1];
|
|
40
|
-
if (!key) continue;
|
|
41
|
-
const value = item.slice(colonIndex + 1).trim();
|
|
42
|
-
if (value) properties.push([key, value]);
|
|
43
|
-
}
|
|
44
|
-
return properties;
|
|
45
|
-
}
|
|
46
|
-
function splitTopLevel(source) {
|
|
47
|
-
const parts = [];
|
|
48
|
-
let start = 0;
|
|
49
|
-
let round = 0;
|
|
50
|
-
let curly = 0;
|
|
51
|
-
let square = 0;
|
|
52
|
-
let quote = null;
|
|
53
|
-
let escaped = false;
|
|
54
|
-
for (let index = 0; index < source.length; index++) {
|
|
55
|
-
const char = source[index];
|
|
56
|
-
if (quote) {
|
|
57
|
-
if (escaped) escaped = false;
|
|
58
|
-
else if (char === "\\") escaped = true;
|
|
59
|
-
else if (char === quote) quote = null;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
if (char === "\"" || char === "'" || char === "`") {
|
|
63
|
-
quote = char;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (char === "(") round++;
|
|
67
|
-
else if (char === ")") round--;
|
|
68
|
-
else if (char === "{") curly++;
|
|
69
|
-
else if (char === "}") curly--;
|
|
70
|
-
else if (char === "[") square++;
|
|
71
|
-
else if (char === "]") square--;
|
|
72
|
-
else if (char === "," && round === 0 && curly === 0 && square === 0) {
|
|
73
|
-
parts.push(source.slice(start, index).trim());
|
|
74
|
-
start = index + 1;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
const tail = source.slice(start).trim();
|
|
78
|
-
if (tail) parts.push(tail);
|
|
79
|
-
return parts;
|
|
80
|
-
}
|
|
81
|
-
function topLevelColon(source) {
|
|
82
|
-
let round = 0;
|
|
83
|
-
let curly = 0;
|
|
84
|
-
let square = 0;
|
|
85
|
-
let quote = null;
|
|
86
|
-
let escaped = false;
|
|
87
|
-
for (let index = 0; index < source.length; index++) {
|
|
88
|
-
const char = source[index];
|
|
89
|
-
if (quote) {
|
|
90
|
-
if (escaped) escaped = false;
|
|
91
|
-
else if (char === "\\") escaped = true;
|
|
92
|
-
else if (char === quote) quote = null;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
if (char === "\"" || char === "'" || char === "`") {
|
|
96
|
-
quote = char;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
if (char === "(") round++;
|
|
100
|
-
else if (char === ")") round--;
|
|
101
|
-
else if (char === "{") curly++;
|
|
102
|
-
else if (char === "}") curly--;
|
|
103
|
-
else if (char === "[") square++;
|
|
104
|
-
else if (char === "]") square--;
|
|
105
|
-
else if (char === ":" && round === 0 && curly === 0 && square === 0) return index;
|
|
106
|
-
}
|
|
107
|
-
return -1;
|
|
108
|
-
}
|
|
109
|
-
function findMatching(source, start, open, close) {
|
|
110
|
-
let depth = 0;
|
|
111
|
-
let quote = null;
|
|
112
|
-
let escaped = false;
|
|
113
|
-
for (let index = start; index < source.length; index++) {
|
|
114
|
-
const char = source[index];
|
|
115
|
-
if (quote) {
|
|
116
|
-
if (escaped) escaped = false;
|
|
117
|
-
else if (char === "\\") escaped = true;
|
|
118
|
-
else if (char === quote) quote = null;
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if (char === "\"" || char === "'" || char === "`") {
|
|
122
|
-
quote = char;
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
if (char === open) depth++;
|
|
126
|
-
if (char === close) {
|
|
127
|
-
depth--;
|
|
128
|
-
if (depth === 0) return index;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return -1;
|
|
132
|
-
}
|
|
133
|
-
function skipWhitespace(source, index) {
|
|
134
|
-
while (index < source.length && /\s/.test(source[index])) index++;
|
|
135
|
-
return index;
|
|
136
|
-
}
|
|
137
|
-
function isIdentifierBoundary(char) {
|
|
138
|
-
return char === void 0 || !/[$\w]/.test(char);
|
|
139
|
-
}
|
|
140
|
-
//#endregion
|
|
141
|
-
//#region src/native-loader.ts
|
|
142
|
-
/**
|
|
143
|
-
* Native binding loader for @vizejs/native.
|
|
144
|
-
*
|
|
145
|
-
* Provides lazy-loading of the native Rust-based parser and a JS fallback
|
|
146
|
-
* for SFC analysis when the native `analyzeSfc` function is unavailable.
|
|
147
|
-
*/
|
|
148
|
-
let native = null;
|
|
149
|
-
function loadNative() {
|
|
150
|
-
if (native) return native;
|
|
151
|
-
const require = createRequire(import.meta.url);
|
|
152
|
-
try {
|
|
153
|
-
native = require("@vizejs/native");
|
|
154
|
-
return native;
|
|
155
|
-
} catch (e) {
|
|
156
|
-
throw new Error(`Failed to load @vizejs/native. Make sure it's installed and built:\n${String(e)}`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* JS-based fallback for SFC analysis when native `analyzeSfc` is not available.
|
|
161
|
-
* Uses regex parsing to extract props and emits from Vue SFC source.
|
|
162
|
-
*/
|
|
163
|
-
function analyzeSfcFallback(source, _options) {
|
|
164
|
-
try {
|
|
165
|
-
const props = [];
|
|
166
|
-
const emits = [];
|
|
167
|
-
const scriptSetupMatch = source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/);
|
|
168
|
-
if (!scriptSetupMatch) {
|
|
169
|
-
if (!source.match(/<script[^>]*>([\s\S]*?)<\/script>/)) return {
|
|
170
|
-
props: [],
|
|
171
|
-
emits: []
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
const scriptContent = scriptSetupMatch?.[1] || "";
|
|
175
|
-
const propsMatch = scriptContent.match(/defineProps\s*<\s*\{([\s\S]*?)\}>\s*\(/);
|
|
176
|
-
const propsMatch2 = scriptContent.match(/defineProps\s*<\s*\{([\s\S]*?)\}>/);
|
|
177
|
-
const propsBody = propsMatch?.[1] || propsMatch2?.[1];
|
|
178
|
-
const withDefaults = extractWithDefaults(scriptContent);
|
|
179
|
-
if (propsBody) {
|
|
180
|
-
const lines = propsBody.split("\n");
|
|
181
|
-
let i = 0;
|
|
182
|
-
while (i < lines.length) {
|
|
183
|
-
const line = lines[i].trim();
|
|
184
|
-
if (line.startsWith("/**") || line.startsWith("*") || line.startsWith("*/")) {
|
|
185
|
-
i++;
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
const propMatch = line.match(/^(\w+)(\?)?:\s*(.+?)(?:;?\s*)$/);
|
|
189
|
-
if (propMatch) {
|
|
190
|
-
const name = propMatch[1];
|
|
191
|
-
const optional = !!propMatch[2];
|
|
192
|
-
let type = propMatch[3].replace(/;$/, "").trim();
|
|
193
|
-
const defaultValue = withDefaults.get(name);
|
|
194
|
-
props.push({
|
|
195
|
-
name,
|
|
196
|
-
type,
|
|
197
|
-
required: !optional && defaultValue === void 0,
|
|
198
|
-
...defaultValue !== void 0 ? { default_value: defaultValue } : {}
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
i++;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
const emitsMatch = scriptContent.match(/defineEmits\s*<\s*\{([\s\S]*?)\}>/);
|
|
205
|
-
if (emitsMatch) {
|
|
206
|
-
const emitsBody = emitsMatch[1];
|
|
207
|
-
const emitRegex = /(\w+)\s*:/g;
|
|
208
|
-
let match;
|
|
209
|
-
while ((match = emitRegex.exec(emitsBody)) !== null) emits.push(match[1]);
|
|
210
|
-
}
|
|
211
|
-
return {
|
|
212
|
-
props,
|
|
213
|
-
emits
|
|
214
|
-
};
|
|
215
|
-
} catch {
|
|
216
|
-
return {
|
|
217
|
-
props: [],
|
|
218
|
-
emits: []
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
//#endregion
|
|
223
|
-
//#region src/http-error.ts
|
|
224
|
-
var HttpError = class extends Error {
|
|
225
|
-
status;
|
|
226
|
-
constructor(message, status) {
|
|
227
|
-
super(message);
|
|
228
|
-
this.name = "HttpError";
|
|
229
|
-
this.status = status;
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
//#endregion
|
|
233
|
-
//#region src/trusted-path.ts
|
|
234
|
-
function realpathNearest(targetPath) {
|
|
235
|
-
let current = path.resolve(targetPath);
|
|
236
|
-
const missingParts = [];
|
|
237
|
-
while (true) try {
|
|
238
|
-
const real = fs.realpathSync.native(current);
|
|
239
|
-
return missingParts.length > 0 ? path.join(real, ...missingParts.reverse()) : real;
|
|
240
|
-
} catch {
|
|
241
|
-
const parent = path.dirname(current);
|
|
242
|
-
if (parent === current) return path.resolve(targetPath);
|
|
243
|
-
missingParts.push(path.basename(current));
|
|
244
|
-
current = parent;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
function isResolvedPathInside(parentDir, candidatePath) {
|
|
248
|
-
const parent = path.resolve(parentDir);
|
|
249
|
-
const candidate = path.resolve(candidatePath);
|
|
250
|
-
const relative = path.relative(parent, candidate);
|
|
251
|
-
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
252
|
-
}
|
|
253
|
-
function isPathInsideAny(parentDirs, candidatePath) {
|
|
254
|
-
const candidate = realpathNearest(candidatePath);
|
|
255
|
-
return parentDirs.some((parentDir) => isResolvedPathInside(realpathNearest(parentDir), candidate));
|
|
256
|
-
}
|
|
257
|
-
/**
|
|
258
|
-
* True when `candidatePath` stays inside the project, or inside an extra root
|
|
259
|
-
* that was configured to live outside the project.
|
|
260
|
-
*
|
|
261
|
-
* Extra roots whose lexical path is still inside the project are not trusted
|
|
262
|
-
* after realpath — a planted `src` → `/etc` symlink must not widen the
|
|
263
|
-
* readable boundary.
|
|
264
|
-
*/
|
|
265
|
-
function isTrustedSourcePath(projectRoot, extraLexicalRoots, candidatePath) {
|
|
266
|
-
const lexicalProject = path.resolve(projectRoot);
|
|
267
|
-
const realProject = realpathNearest(projectRoot);
|
|
268
|
-
const realCandidate = realpathNearest(candidatePath);
|
|
269
|
-
if (isResolvedPathInside(realProject, realCandidate)) return true;
|
|
270
|
-
for (const lexicalRoot of extraLexicalRoots) {
|
|
271
|
-
const resolvedLexical = path.resolve(lexicalRoot);
|
|
272
|
-
if (isResolvedPathInside(lexicalProject, resolvedLexical)) continue;
|
|
273
|
-
if (isResolvedPathInside(realpathNearest(resolvedLexical), realCandidate)) return true;
|
|
274
|
-
}
|
|
275
|
-
return false;
|
|
276
|
-
}
|
|
277
|
-
function resolveTrustedSourcePath(projectRoot, extraLexicalRoots, candidatePath, label = "path") {
|
|
278
|
-
if (candidatePath.includes("\0")) throw new HttpError(`${label} contains an invalid character`, 400);
|
|
279
|
-
const resolved = path.isAbsolute(candidatePath) ? path.resolve(candidatePath) : path.resolve(path.resolve(projectRoot), candidatePath);
|
|
280
|
-
if (!isTrustedSourcePath(projectRoot, extraLexicalRoots, resolved)) throw new HttpError(`${label} escapes the allowed directory`, 400);
|
|
281
|
-
return resolved;
|
|
282
|
-
}
|
|
283
|
-
function resolveInside(parentDir, candidatePath, label = "path") {
|
|
284
|
-
return resolveInsideAny([parentDir], candidatePath, label);
|
|
285
|
-
}
|
|
286
|
-
function resolveInsideAny(parentDirs, candidatePath, label = "path") {
|
|
287
|
-
if (candidatePath.includes("\0")) throw new HttpError(`${label} contains an invalid character`, 400);
|
|
288
|
-
if (parentDirs.length === 0) throw new HttpError(`No allowed directories configured for ${label}`, 500);
|
|
289
|
-
const parent = path.resolve(parentDirs[0] ?? ".");
|
|
290
|
-
const resolved = path.isAbsolute(candidatePath) ? path.resolve(candidatePath) : path.resolve(parent, candidatePath);
|
|
291
|
-
if (!isPathInsideAny(parentDirs, resolved)) throw new HttpError(`${label} escapes the allowed directory`, 400);
|
|
292
|
-
return resolved;
|
|
293
|
-
}
|
|
294
|
-
function resolveUrlPathInside(parentDir, requestUrl, label = "path") {
|
|
295
|
-
let pathname = decodeUrlComponent(requestUrl.split(/[?#]/, 1)[0] || "/", label);
|
|
296
|
-
pathname = pathname.replaceAll("\\", "/");
|
|
297
|
-
if (pathname.split("/").includes("..")) throw new HttpError(`${label} must not contain parent directory segments`, 400);
|
|
298
|
-
return resolveInside(parentDir, `.${pathname}`, label);
|
|
299
|
-
}
|
|
300
|
-
function decodeUrlComponent(value, label = "path") {
|
|
301
|
-
try {
|
|
302
|
-
return decodeURIComponent(value);
|
|
303
|
-
} catch {
|
|
304
|
-
throw new HttpError(`${label} is not valid URL encoding`, 400);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
function hasSuffix(candidatePath, suffix) {
|
|
308
|
-
return realpathNearest(candidatePath).toLowerCase().endsWith(suffix);
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* True when the realpath still looks like a Vue SFC. A planted
|
|
312
|
-
* `Evil.vue` → `.env` symlink must not be treated as component source.
|
|
313
|
-
*/
|
|
314
|
-
function isVueSourcePath(candidatePath) {
|
|
315
|
-
return hasSuffix(candidatePath, ".vue");
|
|
316
|
-
}
|
|
317
|
-
function assertVueSourcePath(candidatePath, label = "path") {
|
|
318
|
-
if (!isVueSourcePath(candidatePath)) throw new HttpError(`${label} must be a .vue file`, 400);
|
|
319
|
-
}
|
|
320
|
-
/** Accept Vue SFCs and script modules while validating the resolved realpath. */
|
|
321
|
-
function assertComponentSourcePath(candidatePath, label = "path") {
|
|
322
|
-
const realPath = realpathNearest(candidatePath).toLowerCase();
|
|
323
|
-
if (!/\.(?:vue|ts|tsx|js|jsx)$/.test(realPath)) throw new HttpError(`${label} must be a .vue, .ts, .tsx, .js, or .jsx file`, 400);
|
|
324
|
-
}
|
|
325
|
-
function isArtVueSourcePath(candidatePath) {
|
|
326
|
-
return hasSuffix(candidatePath, ".art.vue");
|
|
327
|
-
}
|
|
328
|
-
function assertArtVueSourcePath(candidatePath, label = "art path") {
|
|
329
|
-
if (!isArtVueSourcePath(candidatePath)) throw new HttpError(`${label} must be a .art.vue file`, 400);
|
|
330
|
-
}
|
|
331
|
-
//#endregion
|
|
332
|
-
//#region src/security.ts
|
|
333
|
-
const DEFAULT_API_BODY_LIMIT_BYTES = 1024 * 1024;
|
|
334
|
-
function createDevSessionToken() {
|
|
335
|
-
return randomBytes(32).toString("base64url");
|
|
336
|
-
}
|
|
337
|
-
function collectRequestBody(req, limit = DEFAULT_API_BODY_LIMIT_BYTES) {
|
|
338
|
-
return new Promise((resolve, reject) => {
|
|
339
|
-
let body = "";
|
|
340
|
-
let size = 0;
|
|
341
|
-
let completed = false;
|
|
342
|
-
req.on("data", (chunk) => {
|
|
343
|
-
if (completed) return;
|
|
344
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
345
|
-
size += buffer.byteLength;
|
|
346
|
-
if (size > limit) {
|
|
347
|
-
completed = true;
|
|
348
|
-
reject(new HttpError(`Request body exceeds ${limit} bytes`, 413));
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
body += buffer.toString("utf-8");
|
|
352
|
-
});
|
|
353
|
-
req.on("end", () => {
|
|
354
|
-
if (!completed) {
|
|
355
|
-
completed = true;
|
|
356
|
-
resolve(body);
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
req.on("error", (error) => {
|
|
360
|
-
if (!completed) {
|
|
361
|
-
completed = true;
|
|
362
|
-
reject(error);
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
function parseJsonBody(body) {
|
|
368
|
-
try {
|
|
369
|
-
return JSON.parse(body);
|
|
370
|
-
} catch (error) {
|
|
371
|
-
if (error instanceof SyntaxError) throw new HttpError("Malformed JSON body", 400);
|
|
372
|
-
throw error;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
function isLoopbackAddress(address) {
|
|
376
|
-
const value = address.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
377
|
-
if (value === "localhost" || value === "::1") return true;
|
|
378
|
-
if (value.startsWith("::ffff:") || value.startsWith(":ffff:")) return isLoopbackAddress(value.slice(value.indexOf("ffff:") + 5));
|
|
379
|
-
const parts = value.split(".");
|
|
380
|
-
if (parts.length !== 4 || parts[0] !== "127") return false;
|
|
381
|
-
return parts.every((part, index) => {
|
|
382
|
-
if (index === 0) return true;
|
|
383
|
-
const octet = Number(part);
|
|
384
|
-
return Number.isInteger(octet) && octet >= 0 && octet <= 255;
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
function isLoopbackRequest(req) {
|
|
388
|
-
const remote = req.socket?.remoteAddress;
|
|
389
|
-
if (!remote) return false;
|
|
390
|
-
return isLoopbackAddress(remote);
|
|
391
|
-
}
|
|
392
|
-
function validateDevApiRequest(req, sessionToken) {
|
|
393
|
-
const originError = validateOrigin(req);
|
|
394
|
-
if (originError) return originError;
|
|
395
|
-
if (!isUnsafeMethod(req.method)) return null;
|
|
396
|
-
if (!isLoopbackRequest(req)) return new HttpError("Musea write APIs are limited to loopback clients", 403);
|
|
397
|
-
if (!hasValidSessionToken(req, sessionToken)) return new HttpError("Invalid Musea dev session token", 403);
|
|
398
|
-
if (!isJsonRequest(req)) return new HttpError("Content-Type must be application/json", 415);
|
|
399
|
-
return null;
|
|
400
|
-
}
|
|
401
|
-
function serializeScriptValue(value) {
|
|
402
|
-
return (JSON.stringify(value) ?? "undefined").replace(/[<>&\u2028\u2029]/g, (char) => {
|
|
403
|
-
switch (char) {
|
|
404
|
-
case "<": return "\\u003C";
|
|
405
|
-
case ">": return "\\u003E";
|
|
406
|
-
case "&": return "\\u0026";
|
|
407
|
-
case "\u2028": return "\\u2028";
|
|
408
|
-
case "\u2029": return "\\u2029";
|
|
409
|
-
default: return char;
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
function isUnsafeMethod(method) {
|
|
414
|
-
return method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE";
|
|
415
|
-
}
|
|
416
|
-
function isJsonRequest(req) {
|
|
417
|
-
return getHeader(req, "content-type")?.split(";")[0]?.trim().toLowerCase() === "application/json";
|
|
418
|
-
}
|
|
419
|
-
function validateOrigin(req) {
|
|
420
|
-
if (getHeader(req, "sec-fetch-site") === "cross-site") return new HttpError("Cross-origin Musea API requests are not allowed", 403);
|
|
421
|
-
const origin = getHeader(req, "origin");
|
|
422
|
-
if (!origin) return null;
|
|
423
|
-
const host = getHeader(req, "host");
|
|
424
|
-
if (!host) return new HttpError("Missing Host header", 400);
|
|
425
|
-
try {
|
|
426
|
-
if (new URL(origin).host !== host) return new HttpError("Cross-origin Musea API requests are not allowed", 403);
|
|
427
|
-
} catch {
|
|
428
|
-
return new HttpError("Invalid Origin header", 400);
|
|
429
|
-
}
|
|
430
|
-
return null;
|
|
431
|
-
}
|
|
432
|
-
function hasValidSessionToken(req, expectedToken) {
|
|
433
|
-
const actualToken = getHeader(req, "x-musea-session");
|
|
434
|
-
if (!actualToken) return false;
|
|
435
|
-
const actual = Buffer.from(actualToken);
|
|
436
|
-
const expected = Buffer.from(expectedToken);
|
|
437
|
-
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
438
|
-
}
|
|
439
|
-
function getHeader(req, name) {
|
|
440
|
-
const value = req.headers[name];
|
|
441
|
-
if (Array.isArray(value)) return value[0];
|
|
442
|
-
return value;
|
|
443
|
-
}
|
|
444
|
-
//#endregion
|
|
445
|
-
//#region src/utils.ts
|
|
446
|
-
/**
|
|
447
|
-
* Shared utility functions for the Musea Vite plugin.
|
|
448
|
-
*/
|
|
449
|
-
function normalizeGlobPath(filepath) {
|
|
450
|
-
return filepath.split(path.sep).join("/");
|
|
451
|
-
}
|
|
452
|
-
function matchesPattern(file, pattern, root) {
|
|
453
|
-
const normalizedPattern = normalizeGlobPath(pattern);
|
|
454
|
-
return matchGlob(path.isAbsolute(pattern) ? path.resolve(file) : path.relative(root, file), normalizedPattern);
|
|
455
|
-
}
|
|
456
|
-
function shouldProcess(file, include, exclude, root) {
|
|
457
|
-
for (const pattern of exclude) if (matchesPattern(file, pattern, root)) return false;
|
|
458
|
-
for (const pattern of include) if (matchesPattern(file, pattern, root)) return true;
|
|
459
|
-
return false;
|
|
460
|
-
}
|
|
461
|
-
function matchGlob(filepath, pattern) {
|
|
462
|
-
const normalizedFilepath = normalizeGlobPath(filepath);
|
|
463
|
-
const normalizedPattern = normalizeGlobPath(pattern);
|
|
464
|
-
const PLACEHOLDER = "<<GLOBSTAR>>";
|
|
465
|
-
const SEGMENT_PLACEHOLDER = "<<GLOBSTAR_SEGMENT>>";
|
|
466
|
-
const regex = normalizedPattern.replaceAll("**/", SEGMENT_PLACEHOLDER).replaceAll("**", PLACEHOLDER).replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, "[^/]*").replaceAll(SEGMENT_PLACEHOLDER, "(?:.*/)?").replaceAll(PLACEHOLDER, ".*");
|
|
467
|
-
return new RegExp(`^${regex}$`).test(normalizedFilepath);
|
|
468
|
-
}
|
|
469
|
-
function resolveScanRoot(root, pattern) {
|
|
470
|
-
const absolutePattern = path.isAbsolute(pattern) ? pattern : path.resolve(root, pattern);
|
|
471
|
-
const normalizedPattern = normalizeGlobPath(absolutePattern);
|
|
472
|
-
const globIndex = normalizedPattern.search(/[*[{]/);
|
|
473
|
-
if (globIndex === -1) return path.dirname(absolutePattern);
|
|
474
|
-
const staticPrefix = normalizedPattern.slice(0, globIndex);
|
|
475
|
-
if (!staticPrefix) return root;
|
|
476
|
-
if (staticPrefix.endsWith("/")) return path.resolve(staticPrefix.slice(0, -1));
|
|
477
|
-
return path.resolve(path.dirname(staticPrefix));
|
|
478
|
-
}
|
|
479
|
-
function resolveScanRoots(root, include) {
|
|
480
|
-
const roots = /* @__PURE__ */ new Set();
|
|
481
|
-
for (const pattern of include) roots.add(resolveScanRoot(root, pattern));
|
|
482
|
-
if (roots.size === 0) roots.add(root);
|
|
483
|
-
return [...roots];
|
|
484
|
-
}
|
|
485
|
-
async function scanArtFiles(root, include, exclude, scanInlineArt = false) {
|
|
486
|
-
const files = /* @__PURE__ */ new Set();
|
|
487
|
-
const scanRoots = resolveScanRoots(root, include);
|
|
488
|
-
const visitedDirs = /* @__PURE__ */ new Set();
|
|
489
|
-
async function scan(dir) {
|
|
490
|
-
const resolvedDir = path.resolve(dir);
|
|
491
|
-
if (visitedDirs.has(resolvedDir)) return;
|
|
492
|
-
visitedDirs.add(resolvedDir);
|
|
493
|
-
let entries;
|
|
494
|
-
try {
|
|
495
|
-
entries = await fs.promises.readdir(resolvedDir, { withFileTypes: true });
|
|
496
|
-
} catch {
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
for (const entry of entries) {
|
|
500
|
-
const fullPath = path.join(resolvedDir, entry.name);
|
|
501
|
-
let excluded = false;
|
|
502
|
-
for (const pattern of exclude) if (matchesPattern(fullPath, pattern, root) || matchGlob(entry.name, pattern)) {
|
|
503
|
-
excluded = true;
|
|
504
|
-
break;
|
|
505
|
-
}
|
|
506
|
-
if (excluded) continue;
|
|
507
|
-
if (entry.isSymbolicLink()) continue;
|
|
508
|
-
if (entry.isDirectory()) await scan(fullPath);
|
|
509
|
-
else if (entry.isFile() && entry.name.endsWith(".art.vue")) {
|
|
510
|
-
if (shouldProcess(fullPath, include, exclude, root) && isTrustedSourcePath(root, scanRoots, fullPath)) files.add(fullPath);
|
|
511
|
-
} else if (scanInlineArt && entry.isFile() && entry.name.endsWith(".vue") && !entry.name.endsWith(".art.vue")) {
|
|
512
|
-
if (!isTrustedSourcePath(root, scanRoots, fullPath)) continue;
|
|
513
|
-
if ((await fs.promises.readFile(fullPath, "utf-8")).includes("<art")) files.add(fullPath);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
for (const scanRoot of scanRoots) await scan(scanRoot);
|
|
518
|
-
return [...files];
|
|
519
|
-
}
|
|
520
|
-
async function generateStorybookFiles(artFiles, root, outDir) {
|
|
521
|
-
const binding = loadNative();
|
|
522
|
-
const outputDir = path.resolve(root, outDir);
|
|
523
|
-
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
524
|
-
for (const [filePath, art] of artFiles) try {
|
|
525
|
-
const source = await fs.promises.readFile(filePath, "utf-8");
|
|
526
|
-
const csf = binding.artToCsf(source, { filename: filePath });
|
|
527
|
-
const outputPath = path.join(outputDir, csf.filename);
|
|
528
|
-
const code = rewriteStorybookScriptImports(rewriteStorybookComponentImport(csf.code, art, filePath, outputPath), filePath, outputPath);
|
|
529
|
-
await fs.promises.writeFile(outputPath, code, "utf-8");
|
|
530
|
-
console.log(`[musea] Generated: ${path.relative(root, outputPath)}`);
|
|
531
|
-
} catch (e) {
|
|
532
|
-
console.error(`[musea] Failed to generate CSF for ${filePath}:`, e);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
function rewriteStorybookComponentImport(code, art, artPath, outputPath) {
|
|
536
|
-
const component = art.isInline && art.componentPath ? art.componentPath : art.metadata.component;
|
|
537
|
-
if (!component || isBareImport(component)) return code;
|
|
538
|
-
const resolvedComponent = path.isAbsolute(component) ? component : path.resolve(path.dirname(artPath), component);
|
|
539
|
-
let relativeComponent = normalizeGlobPath(path.relative(path.dirname(outputPath), resolvedComponent));
|
|
540
|
-
if (!relativeComponent.startsWith(".")) relativeComponent = `./${relativeComponent}`;
|
|
541
|
-
return code.replace(/import\s+(__museaComponent)\s+from\s+(['"])([^'"]+)\2;/, `import $1 from $2${relativeComponent}$2;`);
|
|
542
|
-
}
|
|
543
|
-
function isBareImport(source) {
|
|
544
|
-
return !source.startsWith(".") && !path.isAbsolute(source);
|
|
545
|
-
}
|
|
546
|
-
/**
|
|
547
|
-
* Rewrite relative import specifiers in the generated Storybook CSF file so that
|
|
548
|
-
* imports lifted verbatim from the art file's `<script setup>` resolve against
|
|
549
|
-
* the generated file's location instead of the original art file's directory.
|
|
550
|
-
*
|
|
551
|
-
* The `__museaComponent` import is already rebased by
|
|
552
|
-
* `rewriteStorybookComponentImport` and is skipped here.
|
|
553
|
-
*/
|
|
554
|
-
function rewriteStorybookScriptImports(code, artPath, outputPath) {
|
|
555
|
-
const artDir = path.dirname(artPath);
|
|
556
|
-
const outDir = path.dirname(outputPath);
|
|
557
|
-
return code.replace(/(import\s+(?:[\s\S]*?)\s+from\s+)(['"])([^'"]+)\2(\s*;?)/g, (match, head, quote, specifier, tail) => {
|
|
558
|
-
if (!specifier.startsWith(".")) return match;
|
|
559
|
-
if (/\b__museaComponent\b/.test(head)) return match;
|
|
560
|
-
const resolved = path.resolve(artDir, specifier);
|
|
561
|
-
let rebased = normalizeGlobPath(path.relative(outDir, resolved));
|
|
562
|
-
if (!rebased.startsWith(".")) rebased = `./${rebased}`;
|
|
563
|
-
return `${head}${quote}${rebased}${quote}${tail}`;
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
function toPascalCase(str) {
|
|
567
|
-
const pascal = str.normalize("NFKD").replace(/[^\p{L}\p{N}]+/gu, " ").trim().split(/\s+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
568
|
-
if (!pascal) return "Variant";
|
|
569
|
-
return /^[\p{L}_$]/u.test(pascal) ? pascal : `Variant${pascal}`;
|
|
570
|
-
}
|
|
571
|
-
function escapeHtml(str) {
|
|
572
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
573
|
-
}
|
|
574
|
-
/**
|
|
575
|
-
* Build the theme config object from plugin options for runtime injection.
|
|
576
|
-
*/
|
|
577
|
-
function buildThemeConfig(theme) {
|
|
578
|
-
if (!theme) return void 0;
|
|
579
|
-
if (typeof theme === "string") return { default: theme };
|
|
580
|
-
const themes = Array.isArray(theme) ? theme : [theme];
|
|
581
|
-
const custom = {};
|
|
582
|
-
for (const t of themes) custom[t.name] = {
|
|
583
|
-
base: t.base,
|
|
584
|
-
colors: t.colors
|
|
585
|
-
};
|
|
586
|
-
return {
|
|
587
|
-
default: themes[0].name,
|
|
588
|
-
custom
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
//#endregion
|
|
592
|
-
//#region src/component-source.ts
|
|
593
|
-
function allowedSourceRoots(root, scanRoots = []) {
|
|
594
|
-
return [...new Set([root, ...scanRoots].map((sourceRoot) => path.resolve(sourceRoot)))];
|
|
595
|
-
}
|
|
596
|
-
function resolveReadableArtPath(root, scanRoots, artPath) {
|
|
597
|
-
return resolveTrustedSourcePath(root, scanRoots, artPath, "art path");
|
|
598
|
-
}
|
|
599
|
-
function resolveComponentSourcePath(art, artPath, sourceRoots) {
|
|
600
|
-
const componentPath = art.isInline && art.componentPath ? art.componentPath : art.metadata.component ? path.isAbsolute(art.metadata.component) ? art.metadata.component : path.resolve(path.dirname(artPath), art.metadata.component) : null;
|
|
601
|
-
const [projectRoot, ...extraRoots] = sourceRoots;
|
|
602
|
-
if (!componentPath) return null;
|
|
603
|
-
const resolved = resolveTrustedSourcePath(projectRoot ?? ".", extraRoots, componentPath, "component path");
|
|
604
|
-
assertComponentSourcePath(resolved, "component path");
|
|
605
|
-
return resolved;
|
|
606
|
-
}
|
|
607
|
-
//#endregion
|
|
608
|
-
//#region src/art-module-vue2.ts
|
|
609
|
-
/**
|
|
610
|
-
* Legacy Vue 2 variant emission.
|
|
611
|
-
*
|
|
612
|
-
* Vue 2 has no `openBlock`/`createElementBlock` runtime, so the compiled Vue 3
|
|
613
|
-
* render functions the SFC pipeline produces cannot load there. Vue 2 galleries
|
|
614
|
-
* keep the runtime-compiled `template:` string they always had — the TypeScript
|
|
615
|
-
* fix in #3857 applies to Vue 3, which is what `.art.vue` with
|
|
616
|
-
* `<script setup lang="ts">` targets.
|
|
617
|
-
*/
|
|
618
|
-
function escapeTemplateLiteral(str) {
|
|
619
|
-
return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
620
|
-
}
|
|
621
|
-
function emitLegacyVariant(context) {
|
|
622
|
-
const { variantComponentName, variantName, template, componentTagName, componentBindingName, scriptSetup, hasSetup, isolatedSetup, setupReturn, importDeclaresName } = context;
|
|
623
|
-
const escapedTemplate = escapeTemplateLiteral(template);
|
|
624
|
-
const fullTemplate = `<div data-variant="${escapeTemplateLiteral(escapeHtml(variantName))}">${escapedTemplate}</div>`;
|
|
625
|
-
const componentNames = /* @__PURE__ */ new Map();
|
|
626
|
-
if (componentTagName) componentNames.set(componentTagName, componentBindingName);
|
|
627
|
-
if (scriptSetup) {
|
|
628
|
-
for (const name of scriptSetup.returnNames) if (/^[A-Z]/.test(name) && scriptSetup.imports.some((imp) => importDeclaresName(imp, name))) componentNames.set(name, name);
|
|
629
|
-
}
|
|
630
|
-
const components = componentNames.size > 0 ? ` components: { ${[...componentNames].map(([name, value]) => `${JSON.stringify(name)}: ${value}`).join(", ")} },\n` : "";
|
|
631
|
-
if (scriptSetup && hasSetup && isolatedSetup) return `
|
|
632
|
-
export const ${variantComponentName} = __museaDefineComponent({
|
|
633
|
-
name: '${variantComponentName}',
|
|
634
|
-
${components} setup() {
|
|
635
|
-
${scriptSetup.setupBody.join("\n")}
|
|
636
|
-
return ${setupReturn};
|
|
637
|
-
},
|
|
638
|
-
template: \`${fullTemplate}\`,
|
|
639
|
-
});
|
|
640
|
-
`;
|
|
641
|
-
if (scriptSetup && hasSetup) return `
|
|
642
|
-
export const ${variantComponentName} = __museaDefineComponent({
|
|
643
|
-
name: '${variantComponentName}',
|
|
644
|
-
${components} setup() {
|
|
645
|
-
return __museaSharedSetup;
|
|
646
|
-
},
|
|
647
|
-
template: \`${fullTemplate}\`,
|
|
648
|
-
});
|
|
649
|
-
`;
|
|
650
|
-
return `
|
|
651
|
-
export const ${variantComponentName} = {
|
|
652
|
-
name: '${variantComponentName}',
|
|
653
|
-
${components} template: \`${fullTemplate}\`,
|
|
654
|
-
};
|
|
655
|
-
`;
|
|
656
|
-
}
|
|
657
|
-
//#endregion
|
|
658
|
-
//#region src/art-component.ts
|
|
659
|
-
/**
|
|
660
|
-
* Resolve which component an art file demonstrates, and under what tag.
|
|
661
|
-
*
|
|
662
|
-
* Shared by the art module and by the per-variant SFC compiler (#3857) so both
|
|
663
|
-
* agree on the import path, the tag `<Self>` expands to, and the binding name.
|
|
664
|
-
* The parsed `<script setup>` is passed in rather than parsed here, so this
|
|
665
|
-
* module does not have to import back from `art-module.ts`.
|
|
666
|
-
*/
|
|
667
|
-
function componentNameFromSource$1(source) {
|
|
668
|
-
const withoutQuery = source.split(/[?#]/, 1)[0] || source;
|
|
669
|
-
const filename = path.basename(withoutQuery);
|
|
670
|
-
const extension = path.extname(filename);
|
|
671
|
-
const name = toPascalCase(extension ? filename.slice(0, -extension.length) : filename);
|
|
672
|
-
return name === "Variant" ? "MuseaComponent" : name;
|
|
673
|
-
}
|
|
674
|
-
function resolveArtComponent(art, filePath, scriptSetup, options = {}) {
|
|
675
|
-
let componentImportPath;
|
|
676
|
-
let componentTagName;
|
|
677
|
-
let componentBindingName = "__MuseaComponent";
|
|
678
|
-
const defineArtComponentName = scriptSetup?.defineArtComponentName;
|
|
679
|
-
const defineArtComponentSource = scriptSetup?.defineArtComponentSource;
|
|
680
|
-
if (art.isInline && art.componentPath) {
|
|
681
|
-
componentImportPath = options.root ? resolveComponentSourcePath(art, filePath, allowedSourceRoots(options.root, options.scanRoots ?? [])) ?? void 0 : art.componentPath;
|
|
682
|
-
componentTagName = "MuseaComponent";
|
|
683
|
-
} else if (defineArtComponentSource || art.metadata.component) {
|
|
684
|
-
const componentSource = defineArtComponentSource ?? art.metadata.component;
|
|
685
|
-
if (componentSource) {
|
|
686
|
-
const sourceArt = componentSource === art.metadata.component ? art : {
|
|
687
|
-
...art,
|
|
688
|
-
metadata: {
|
|
689
|
-
...art.metadata,
|
|
690
|
-
component: componentSource
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
componentImportPath = options.root ? resolveComponentSourcePath(sourceArt, filePath, allowedSourceRoots(options.root, options.scanRoots ?? [])) ?? void 0 : path.isAbsolute(componentSource) ? componentSource : path.resolve(path.dirname(filePath), componentSource);
|
|
694
|
-
}
|
|
695
|
-
componentTagName = defineArtComponentName ?? (art.metadata.component ? componentNameFromSource$1(art.metadata.component) : "MuseaComponent");
|
|
696
|
-
componentBindingName = componentTagName;
|
|
697
|
-
}
|
|
698
|
-
return {
|
|
699
|
-
componentImportPath,
|
|
700
|
-
componentTagName,
|
|
701
|
-
componentBindingName
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
/** Expand `<Self>` in a variant template to the resolved component tag. */
|
|
705
|
-
function expandSelfTag(template, componentTagName) {
|
|
706
|
-
if (!componentTagName) return template;
|
|
707
|
-
return template.replace(/<Self(?=[\s/>])/g, `<${componentTagName}`).replace(/<\/Self\s*>/g, `</${componentTagName}>`);
|
|
708
|
-
}
|
|
709
|
-
//#endregion
|
|
710
|
-
//#region src/art-module.ts
|
|
711
|
-
/**
|
|
712
|
-
* Art module generation for Musea.
|
|
713
|
-
*
|
|
714
|
-
* Generates the virtual ES modules that represent parsed `.art.vue` files,
|
|
715
|
-
* including variant component definitions and script setup handling.
|
|
716
|
-
*/
|
|
717
|
-
/**
|
|
718
|
-
* Extract the content of the first <script setup> block from a Vue SFC source.
|
|
719
|
-
*/
|
|
720
|
-
function extractScriptSetupContent(source) {
|
|
721
|
-
return source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/)?.[1]?.trim();
|
|
722
|
-
}
|
|
723
|
-
function extractScriptSetupIsolated(source) {
|
|
724
|
-
const match = source.match(/<script\s+([^>]*)\bsetup\b([^>]*)>/);
|
|
725
|
-
if (!match) return true;
|
|
726
|
-
const isolate = `${match[1] ?? ""} ${match[2] ?? ""}`.match(/\bisolate\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/);
|
|
727
|
-
return (isolate?.[1] ?? isolate?.[2] ?? isolate?.[3]) !== "false";
|
|
728
|
-
}
|
|
729
|
-
function resolveRelativeSpecifier(specifier, artDir) {
|
|
730
|
-
if (!specifier.startsWith(".")) return specifier;
|
|
731
|
-
return path.resolve(artDir, specifier);
|
|
732
|
-
}
|
|
733
|
-
function rewriteRelativeImportStatement(statement, artDir) {
|
|
734
|
-
return statement.replace(/\bfrom\s+(['"])([^'"]+)\1/g, (_match, quote, specifier) => `from ${quote}${resolveRelativeSpecifier(specifier, artDir)}${quote}`).replace(/^(\s*import\s+)(['"])([^'"]+)\2(\s*;?\s*)$/s, (_match, prefix, quote, specifier, suffix) => `${prefix}${quote}${resolveRelativeSpecifier(specifier, artDir)}${quote}${suffix}`);
|
|
735
|
-
}
|
|
736
|
-
function countCharBalance(source, openChar, closeChar) {
|
|
737
|
-
let balance = 0;
|
|
738
|
-
for (const char of source) if (char === openChar) balance++;
|
|
739
|
-
else if (char === closeChar) balance--;
|
|
740
|
-
return balance;
|
|
741
|
-
}
|
|
742
|
-
function isCompleteImportStatement(statement) {
|
|
743
|
-
const trimmed = statement.trim();
|
|
744
|
-
if (!trimmed.startsWith("import ")) return false;
|
|
745
|
-
if (countCharBalance(statement, "{", "}") > 0) return false;
|
|
746
|
-
return /^import\s+[\s\S]+?\s+from\s+['"][^'"]+['"]\s*;?$/s.test(trimmed) || /^import\s+['"][^'"]+['"]\s*;?$/s.test(trimmed);
|
|
747
|
-
}
|
|
748
|
-
function splitTopLevelCommaList(source) {
|
|
749
|
-
const parts = [];
|
|
750
|
-
let current = "";
|
|
751
|
-
let braceDepth = 0;
|
|
752
|
-
let bracketDepth = 0;
|
|
753
|
-
let parenDepth = 0;
|
|
754
|
-
for (const char of source) {
|
|
755
|
-
if (char === "," && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
|
|
756
|
-
const trimmed = current.trim();
|
|
757
|
-
if (trimmed) parts.push(trimmed);
|
|
758
|
-
current = "";
|
|
759
|
-
continue;
|
|
760
|
-
}
|
|
761
|
-
current += char;
|
|
762
|
-
if (char === "{") braceDepth++;
|
|
763
|
-
else if (char === "}") braceDepth--;
|
|
764
|
-
else if (char === "[") bracketDepth++;
|
|
765
|
-
else if (char === "]") bracketDepth--;
|
|
766
|
-
else if (char === "(") parenDepth++;
|
|
767
|
-
else if (char === ")") parenDepth--;
|
|
768
|
-
}
|
|
769
|
-
const trimmed = current.trim();
|
|
770
|
-
if (trimmed) parts.push(trimmed);
|
|
771
|
-
return parts;
|
|
772
|
-
}
|
|
773
|
-
function collectImportedNames(statement, returnNames) {
|
|
774
|
-
const fromMatch = statement.replace(/\s+/g, " ").trim().replace(/;$/, "").match(/^import\s+(type\s+)?(.+?)\s+from\s+['"][^'"]+['"]$/);
|
|
775
|
-
if (!fromMatch) return;
|
|
776
|
-
if (fromMatch[1]) return;
|
|
777
|
-
const specifierParts = splitTopLevelCommaList(fromMatch[2].trim());
|
|
778
|
-
const defaultOrNamespace = specifierParts[0]?.trim() ?? "";
|
|
779
|
-
const trailing = specifierParts.slice(1).join(", ").trim();
|
|
780
|
-
if (defaultOrNamespace && !defaultOrNamespace.startsWith("{")) {
|
|
781
|
-
const namespaceMatch = defaultOrNamespace.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)$/);
|
|
782
|
-
if (namespaceMatch) returnNames.add(namespaceMatch[1]);
|
|
783
|
-
else if (!defaultOrNamespace.startsWith("type ")) returnNames.add(defaultOrNamespace);
|
|
784
|
-
}
|
|
785
|
-
const namedBlock = defaultOrNamespace.startsWith("{") ? defaultOrNamespace : trailing.startsWith("{") ? trailing : "";
|
|
786
|
-
if (!namedBlock) return;
|
|
787
|
-
const namedContent = namedBlock.slice(1, -1);
|
|
788
|
-
for (const part of splitTopLevelCommaList(namedContent)) {
|
|
789
|
-
const trimmed = part.trim();
|
|
790
|
-
if (!trimmed || trimmed.startsWith("type ")) continue;
|
|
791
|
-
const alias = trimmed.split(/\s+as\s+/).pop()?.trim();
|
|
792
|
-
if (alias) returnNames.add(alias);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
function importDeclaresName(statement, name) {
|
|
796
|
-
const names = /* @__PURE__ */ new Set();
|
|
797
|
-
collectImportedNames(statement, names);
|
|
798
|
-
return names.has(name);
|
|
799
|
-
}
|
|
800
|
-
function collectObjectDestructuredNames(statement, returnNames) {
|
|
801
|
-
const match = statement.match(/^(?:export\s+)?(?:const|let|var)\s+\{([\s\S]*?)\}\s*=/);
|
|
802
|
-
if (!match) return;
|
|
803
|
-
for (const part of splitTopLevelCommaList(match[1])) {
|
|
804
|
-
let name = part.trim();
|
|
805
|
-
if (!name) continue;
|
|
806
|
-
if (name.startsWith("...")) name = name.slice(3).trim();
|
|
807
|
-
else if (name.includes(":")) name = name.split(":").pop().trim();
|
|
808
|
-
else if (name.includes("=")) name = name.split("=")[0].trim();
|
|
809
|
-
if (name.includes("=")) name = name.split("=")[0].trim();
|
|
810
|
-
if (/^[A-Za-z_$][\w$]*$/.test(name)) returnNames.add(name);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
function collectArrayDestructuredNames(statement, returnNames) {
|
|
814
|
-
const match = statement.match(/^(?:export\s+)?(?:const|let|var)\s+\[([\s\S]*?)\]\s*=/);
|
|
815
|
-
if (!match) return;
|
|
816
|
-
for (const part of splitTopLevelCommaList(match[1])) {
|
|
817
|
-
let name = part.trim();
|
|
818
|
-
if (!name) continue;
|
|
819
|
-
if (name.startsWith("...")) name = name.slice(3).trim();
|
|
820
|
-
if (name.includes("=")) name = name.split("=")[0].trim();
|
|
821
|
-
if (/^[A-Za-z_$][\w$]*$/.test(name)) returnNames.add(name);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
function collectTopLevelReturnNames(setupBody, returnNames) {
|
|
825
|
-
let braceDepth = 0;
|
|
826
|
-
for (let i = 0; i < setupBody.length; i++) {
|
|
827
|
-
const line = setupBody[i];
|
|
828
|
-
const trimmed = line.trim();
|
|
829
|
-
if (braceDepth === 0) {
|
|
830
|
-
if (/^(?:export\s+)?(?:const|let|var)\s+\{/.test(trimmed)) {
|
|
831
|
-
const statementLines = [line];
|
|
832
|
-
let balance = countCharBalance(line, "{", "}") + countCharBalance(line, "[", "]") + countCharBalance(line, "(", ")");
|
|
833
|
-
while (balance > 0 && i + 1 < setupBody.length) {
|
|
834
|
-
i++;
|
|
835
|
-
statementLines.push(setupBody[i]);
|
|
836
|
-
balance += countCharBalance(setupBody[i], "{", "}") + countCharBalance(setupBody[i], "[", "]") + countCharBalance(setupBody[i], "(", ")");
|
|
837
|
-
}
|
|
838
|
-
collectObjectDestructuredNames(statementLines.join("\n"), returnNames);
|
|
839
|
-
continue;
|
|
840
|
-
}
|
|
841
|
-
if (/^(?:export\s+)?(?:const|let|var)\s+\[/.test(trimmed)) {
|
|
842
|
-
const statementLines = [line];
|
|
843
|
-
let balance = countCharBalance(line, "{", "}") + countCharBalance(line, "[", "]") + countCharBalance(line, "(", ")");
|
|
844
|
-
while (balance > 0 && i + 1 < setupBody.length) {
|
|
845
|
-
i++;
|
|
846
|
-
statementLines.push(setupBody[i]);
|
|
847
|
-
balance += countCharBalance(setupBody[i], "{", "}") + countCharBalance(setupBody[i], "[", "]") + countCharBalance(setupBody[i], "(", ")");
|
|
848
|
-
}
|
|
849
|
-
collectArrayDestructuredNames(statementLines.join("\n"), returnNames);
|
|
850
|
-
continue;
|
|
851
|
-
}
|
|
852
|
-
const constMatch = trimmed.match(/^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)/);
|
|
853
|
-
if (constMatch) returnNames.add(constMatch[1]);
|
|
854
|
-
const functionMatch = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/);
|
|
855
|
-
if (functionMatch) returnNames.add(functionMatch[1]);
|
|
856
|
-
const classMatch = trimmed.match(/^(?:export\s+)?class\s+([A-Za-z_$][\w$]*)\b/);
|
|
857
|
-
if (classMatch) returnNames.add(classMatch[1]);
|
|
858
|
-
}
|
|
859
|
-
braceDepth += countCharBalance(line, "{", "}");
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
/**
|
|
863
|
-
* Parse script setup content into imports and setup body.
|
|
864
|
-
* Returns the import lines, setup body lines, and all identifiers to expose.
|
|
865
|
-
*/
|
|
866
|
-
function parseScriptSetupForArt(content) {
|
|
867
|
-
const lines = content.split("\n");
|
|
868
|
-
const imports = [];
|
|
869
|
-
const setupBody = [];
|
|
870
|
-
const returnNames = /* @__PURE__ */ new Set();
|
|
871
|
-
let currentImport = null;
|
|
872
|
-
const defineArtComponent = extractDefineArtComponent(content);
|
|
873
|
-
let defineArtBalance = 0;
|
|
874
|
-
for (const line of lines) {
|
|
875
|
-
const trimmed = line.trim();
|
|
876
|
-
if (defineArtBalance > 0) {
|
|
877
|
-
defineArtBalance += countCharBalance(line, "(", ")");
|
|
878
|
-
continue;
|
|
879
|
-
}
|
|
880
|
-
if (currentImport) {
|
|
881
|
-
currentImport.push(line);
|
|
882
|
-
const statement = currentImport.join("\n");
|
|
883
|
-
if (isCompleteImportStatement(statement)) {
|
|
884
|
-
imports.push(statement);
|
|
885
|
-
collectImportedNames(statement, returnNames);
|
|
886
|
-
currentImport = null;
|
|
887
|
-
}
|
|
888
|
-
continue;
|
|
889
|
-
}
|
|
890
|
-
if (trimmed.startsWith("import ")) {
|
|
891
|
-
currentImport = [line];
|
|
892
|
-
const statement = currentImport.join("\n");
|
|
893
|
-
if (isCompleteImportStatement(statement)) {
|
|
894
|
-
imports.push(statement);
|
|
895
|
-
collectImportedNames(statement, returnNames);
|
|
896
|
-
currentImport = null;
|
|
897
|
-
}
|
|
898
|
-
continue;
|
|
899
|
-
}
|
|
900
|
-
if (isDefineArtLine(trimmed)) {
|
|
901
|
-
defineArtBalance = Math.max(0, countCharBalance(line, "(", ")"));
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
setupBody.push(line);
|
|
905
|
-
}
|
|
906
|
-
if (currentImport) {
|
|
907
|
-
const statement = currentImport.join("\n");
|
|
908
|
-
imports.push(statement);
|
|
909
|
-
collectImportedNames(statement, returnNames);
|
|
910
|
-
}
|
|
911
|
-
collectTopLevelReturnNames(setupBody, returnNames);
|
|
912
|
-
returnNames.delete("type");
|
|
913
|
-
if (defineArtComponent.name) returnNames.delete(defineArtComponent.name);
|
|
914
|
-
return {
|
|
915
|
-
imports,
|
|
916
|
-
setupBody,
|
|
917
|
-
returnNames: [...returnNames],
|
|
918
|
-
defineArtComponentName: defineArtComponent.name,
|
|
919
|
-
defineArtComponentSource: defineArtComponent.source
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
function extractDefineArtComponent(content) {
|
|
923
|
-
const sourceMatch = content.match(/\bdefineArt\s*\(\s*(['"])([^'"]+)\1/);
|
|
924
|
-
if (sourceMatch) return {
|
|
925
|
-
name: componentNameFromSource(sourceMatch[2]),
|
|
926
|
-
source: sourceMatch[2]
|
|
927
|
-
};
|
|
928
|
-
return { name: content.match(/\bdefineArt\s*\(\s*([A-Za-z_$][\w$]*)/)?.[1] };
|
|
929
|
-
}
|
|
930
|
-
function componentNameFromSource(source) {
|
|
931
|
-
const withoutQuery = source.split(/[?#]/, 1)[0] || source;
|
|
932
|
-
const filename = path.basename(withoutQuery);
|
|
933
|
-
const extension = path.extname(filename);
|
|
934
|
-
const name = toPascalCase(extension ? filename.slice(0, -extension.length) : filename);
|
|
935
|
-
return name === "Variant" ? "MuseaComponent" : name;
|
|
936
|
-
}
|
|
937
|
-
function isDefineArtLine(trimmed) {
|
|
938
|
-
return /\bdefineArt\s*\(/.test(trimmed);
|
|
939
|
-
}
|
|
940
|
-
function generateArtModule(art, filePath, options = {}) {
|
|
941
|
-
let componentImportPath;
|
|
942
|
-
let componentTagName;
|
|
943
|
-
let componentBindingName = "__MuseaComponent";
|
|
944
|
-
const scriptSetup = art.scriptSetupContent ? parseScriptSetupForArt(art.scriptSetupContent) : null;
|
|
945
|
-
const defineArtComponentName = scriptSetup?.defineArtComponentName;
|
|
946
|
-
const defineArtComponentSource = scriptSetup?.defineArtComponentSource;
|
|
947
|
-
if (art.isInline && art.componentPath) {
|
|
948
|
-
componentImportPath = options.root ? resolveComponentSourcePath(art, filePath, allowedSourceRoots(options.root, options.scanRoots ?? [])) ?? void 0 : art.componentPath;
|
|
949
|
-
componentTagName = "MuseaComponent";
|
|
950
|
-
} else if (defineArtComponentSource || art.metadata.component) {
|
|
951
|
-
const componentSource = defineArtComponentSource ?? art.metadata.component;
|
|
952
|
-
if (componentSource) {
|
|
953
|
-
const sourceArt = componentSource === art.metadata.component ? art : {
|
|
954
|
-
...art,
|
|
955
|
-
metadata: {
|
|
956
|
-
...art.metadata,
|
|
957
|
-
component: componentSource
|
|
958
|
-
}
|
|
959
|
-
};
|
|
960
|
-
componentImportPath = options.root ? resolveComponentSourcePath(sourceArt, filePath, allowedSourceRoots(options.root, options.scanRoots ?? [])) ?? void 0 : path.isAbsolute(componentSource) ? componentSource : path.resolve(path.dirname(filePath), componentSource);
|
|
961
|
-
}
|
|
962
|
-
componentTagName = defineArtComponentName ?? (art.metadata.component ? componentNameFromSource(art.metadata.component) : "MuseaComponent");
|
|
963
|
-
componentBindingName = componentTagName;
|
|
964
|
-
}
|
|
965
|
-
const hasSetupBody = scriptSetup?.setupBody.some((line) => line.trim().length > 0) ?? false;
|
|
966
|
-
const hasSetup = !!scriptSetup && (hasSetupBody || scriptSetup.returnNames.length > 0);
|
|
967
|
-
let code = `
|
|
968
|
-
// Auto-generated module for: ${path.basename(filePath)}
|
|
969
|
-
`;
|
|
970
|
-
if (options.vueVersion === 2 && hasSetup) code += `import { defineComponent as __museaDefineComponent } from 'vue';\n`;
|
|
971
|
-
if (scriptSetup) {
|
|
972
|
-
const artDir = path.dirname(filePath);
|
|
973
|
-
for (const imp of scriptSetup.imports) {
|
|
974
|
-
const resolved = rewriteRelativeImportStatement(imp, artDir);
|
|
975
|
-
code += `${resolved}\n`;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
if (componentImportPath && componentTagName) {
|
|
979
|
-
if (!scriptSetup?.imports.some((imp) => importDeclaresName(imp, componentBindingName))) code += `import ${componentBindingName} from ${JSON.stringify(componentImportPath)};\n`;
|
|
980
|
-
code += `export const __component__ = ${componentBindingName};\n`;
|
|
981
|
-
}
|
|
982
|
-
code += `
|
|
983
|
-
export const metadata = ${JSON.stringify(art.metadata)};
|
|
984
|
-
export const variants = ${JSON.stringify(art.variants)};
|
|
985
|
-
export const __styles__ = ${JSON.stringify(art.styleBlocks ?? [])};
|
|
986
|
-
`;
|
|
987
|
-
const setupReturn = `{ ${scriptSetup?.returnNames.join(", ") ?? ""} }`;
|
|
988
|
-
const isolatedSetup = art.scriptSetupIsolated !== false;
|
|
989
|
-
if (scriptSetup && hasSetup && !isolatedSetup) code += `
|
|
990
|
-
const __museaSharedSetup = (() => {
|
|
991
|
-
${scriptSetup.setupBody.join("\n")}
|
|
992
|
-
return ${setupReturn};
|
|
993
|
-
})();
|
|
994
|
-
`;
|
|
995
|
-
for (const variant of art.variants) {
|
|
996
|
-
const variantComponentName = toPascalCase(variant.name);
|
|
997
|
-
const template = expandSelfTag(variant.template, componentTagName);
|
|
998
|
-
if (options.vueVersion === 2) {
|
|
999
|
-
code += emitLegacyVariant({
|
|
1000
|
-
variantComponentName,
|
|
1001
|
-
variantName: variant.name,
|
|
1002
|
-
template,
|
|
1003
|
-
componentTagName,
|
|
1004
|
-
componentBindingName,
|
|
1005
|
-
scriptSetup,
|
|
1006
|
-
hasSetup,
|
|
1007
|
-
isolatedSetup,
|
|
1008
|
-
setupReturn,
|
|
1009
|
-
importDeclaresName
|
|
1010
|
-
});
|
|
1011
|
-
continue;
|
|
1012
|
-
}
|
|
1013
|
-
const variantModuleId = `virtual:musea-variant:${filePath}:${encodeURIComponent(variant.name)}`;
|
|
1014
|
-
code += `
|
|
1015
|
-
import ${variantComponentName} from ${JSON.stringify(variantModuleId)};
|
|
1016
|
-
export { ${variantComponentName} };
|
|
1017
|
-
`;
|
|
1018
|
-
}
|
|
1019
|
-
const defaultVariant = art.variants.find((v) => v.isDefault) || art.variants[0];
|
|
1020
|
-
if (defaultVariant) code += `
|
|
1021
|
-
export default ${toPascalCase(defaultVariant.name)};
|
|
1022
|
-
`;
|
|
1023
|
-
return code;
|
|
1024
|
-
}
|
|
1025
|
-
//#endregion
|
|
1026
|
-
//#region src/plugin/art-processing.ts
|
|
1027
|
-
function extractArtTagAttributes(source) {
|
|
1028
|
-
const artTagMatch = source.match(/<art\b([\s\S]*?)>/i);
|
|
1029
|
-
if (!artTagMatch) return {};
|
|
1030
|
-
const attributes = {};
|
|
1031
|
-
for (const match of artTagMatch[1].matchAll(/([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'))?/g)) {
|
|
1032
|
-
const name = match[1];
|
|
1033
|
-
if (!name || name === "/") continue;
|
|
1034
|
-
attributes[name] = match[2] ?? match[3] ?? true;
|
|
1035
|
-
}
|
|
1036
|
-
return attributes;
|
|
1037
|
-
}
|
|
1038
|
-
function parseActionEvents(value) {
|
|
1039
|
-
if (typeof value !== "string") return void 0;
|
|
1040
|
-
const events = value.split(",").map((eventName) => eventName.trim().toLowerCase()).filter(Boolean);
|
|
1041
|
-
return events.length > 0 ? [...new Set(events)] : void 0;
|
|
1042
|
-
}
|
|
1043
|
-
function extractCustomArtMetadata(source) {
|
|
1044
|
-
const attrs = extractArtTagAttributes(source);
|
|
1045
|
-
const actionEvents = new Set(parseActionEvents(attrs["action-events"]) ?? []);
|
|
1046
|
-
const captureMousemove = attrs["capture-mousemove"];
|
|
1047
|
-
if (captureMousemove === true || captureMousemove === "true") actionEvents.add("mousemove");
|
|
1048
|
-
return { actionEvents: actionEvents.size > 0 ? [...actionEvents] : void 0 };
|
|
1049
|
-
}
|
|
1050
|
-
function extractStyleBlocks(source) {
|
|
1051
|
-
const styles = [];
|
|
1052
|
-
for (const match of source.matchAll(/<style\b([^>]*)>([\s\S]*?)<\/style>/gi)) {
|
|
1053
|
-
const attrs = match[1] ?? "";
|
|
1054
|
-
const content = match[2]?.trim();
|
|
1055
|
-
const lang = attrs.match(/\blang\s*=\s*["']([^"']+)["']/i)?.[1]?.toLowerCase();
|
|
1056
|
-
if (!content) continue;
|
|
1057
|
-
if (lang && lang !== "css") continue;
|
|
1058
|
-
styles.push(content);
|
|
1059
|
-
}
|
|
1060
|
-
return styles;
|
|
1061
|
-
}
|
|
1062
|
-
function formatArtProcessingError(root, filePath, error) {
|
|
1063
|
-
const detail = error instanceof Error ? error.message || error.name : error == null ? "unknown error" : stringifyUnknown(error);
|
|
1064
|
-
return `[musea] Failed to process ${path.relative(root, filePath) || filePath}: ${detail}`;
|
|
1065
|
-
}
|
|
1066
|
-
function stringifyUnknown(value) {
|
|
1067
|
-
if (typeof value === "string") return value;
|
|
1068
|
-
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return value.toString();
|
|
1069
|
-
try {
|
|
1070
|
-
return JSON.stringify(value) ?? "unknown error";
|
|
1071
|
-
} catch {
|
|
1072
|
-
return "unknown error";
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
function reportArtStatusWarnings(binding, source, filename, warn = console.warn) {
|
|
1076
|
-
const warnings = binding.parseArtStatusWarnings?.(source, { filename });
|
|
1077
|
-
if (warnings == null) return;
|
|
1078
|
-
for (const warning of warnings) warn(`[musea] ${warning}`);
|
|
1079
|
-
}
|
|
1080
|
-
async function processMuseaArtFile(filePath, ctx) {
|
|
1081
|
-
try {
|
|
1082
|
-
const source = await fs.promises.readFile(filePath, "utf-8");
|
|
1083
|
-
const binding = loadNative();
|
|
1084
|
-
const parsed = binding.parseArt(source, { filename: filePath });
|
|
1085
|
-
reportArtStatusWarnings(binding, source, filePath);
|
|
1086
|
-
const customMetadata = extractCustomArtMetadata(source);
|
|
1087
|
-
if (!parsed.variants || parsed.variants.length === 0) return null;
|
|
1088
|
-
const isInline = !filePath.endsWith(".art.vue");
|
|
1089
|
-
return {
|
|
1090
|
-
path: filePath,
|
|
1091
|
-
metadata: {
|
|
1092
|
-
title: parsed.metadata.title || (isInline ? path.basename(filePath, ".vue") : ""),
|
|
1093
|
-
description: parsed.metadata.description,
|
|
1094
|
-
component: isInline ? void 0 : parsed.metadata.component,
|
|
1095
|
-
category: parsed.metadata.category,
|
|
1096
|
-
tags: parsed.metadata.tags,
|
|
1097
|
-
status: parsed.metadata.status,
|
|
1098
|
-
order: parsed.metadata.order,
|
|
1099
|
-
actionEvents: customMetadata.actionEvents ?? parsed.metadata.actionEvents
|
|
1100
|
-
},
|
|
1101
|
-
variants: parsed.variants.map((v) => ({
|
|
1102
|
-
name: v.name,
|
|
1103
|
-
template: v.template,
|
|
1104
|
-
isDefault: v.isDefault,
|
|
1105
|
-
skipVrt: v.skipVrt
|
|
1106
|
-
})),
|
|
1107
|
-
hasScriptSetup: isInline ? false : parsed.hasScriptSetup,
|
|
1108
|
-
scriptSetupContent: !isInline && parsed.hasScriptSetup ? extractScriptSetupContent(source) : void 0,
|
|
1109
|
-
scriptSetupIsolated: !isInline && parsed.hasScriptSetup ? extractScriptSetupIsolated(source) : true,
|
|
1110
|
-
hasScript: parsed.hasScript,
|
|
1111
|
-
styleCount: parsed.styleCount,
|
|
1112
|
-
styleBlocks: isInline ? [] : extractStyleBlocks(source),
|
|
1113
|
-
isInline,
|
|
1114
|
-
componentPath: isInline ? filePath : void 0
|
|
1115
|
-
};
|
|
1116
|
-
} catch (error) {
|
|
1117
|
-
const message = formatArtProcessingError(ctx.root, filePath, error);
|
|
1118
|
-
if (ctx.command === "build") throw new Error(message);
|
|
1119
|
-
(ctx.onError ?? console.error)(message);
|
|
1120
|
-
return null;
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
//#endregion
|
|
1124
|
-
//#region src/plugin/options.ts
|
|
1125
|
-
const MUSEA_OPTIONS_KEY = "__vizeMuseaOptions";
|
|
1126
|
-
function attachMuseaOptions(plugin, options) {
|
|
1127
|
-
Object.defineProperty(plugin, MUSEA_OPTIONS_KEY, {
|
|
1128
|
-
configurable: false,
|
|
1129
|
-
enumerable: false,
|
|
1130
|
-
value: options,
|
|
1131
|
-
writable: false
|
|
1132
|
-
});
|
|
1133
|
-
return plugin;
|
|
1134
|
-
}
|
|
1135
|
-
function readMuseaOptions(plugin) {
|
|
1136
|
-
if (!plugin || typeof plugin !== "object") return void 0;
|
|
1137
|
-
return plugin[MUSEA_OPTIONS_KEY];
|
|
1138
|
-
}
|
|
1139
|
-
//#endregion
|
|
1140
|
-
export { resolveInside as A, parseJsonBody as C, assertVueSourcePath as D, assertArtVueSourcePath as E, analyzeSfcFallback as F, loadNative as I, resolveTrustedSourcePath as M, resolveUrlPathInside as N, decodeUrlComponent as O, HttpError as P, createDevSessionToken as S, validateDevApiRequest as T, scanArtFiles as _, parseScriptSetupForArt as a, DEFAULT_API_BODY_LIMIT_BYTES as b, resolveArtComponent as c, resolveReadableArtPath as d, buildThemeConfig as f, resolveScanRoots as g, matchGlob as h, generateArtModule as i, resolveInsideAny as j, isVueSourcePath as k, allowedSourceRoots as l, generateStorybookFiles as m, readMuseaOptions as n, rewriteRelativeImportStatement as o, escapeHtml as p, processMuseaArtFile as r, expandSelfTag as s, attachMuseaOptions as t, resolveComponentSourcePath as u, shouldProcess as v, serializeScriptValue as w, collectRequestBody as x, toPascalCase as y };
|
|
1141
|
-
|
|
1142
|
-
//# sourceMappingURL=options-Cd7bzLGr.mjs.map
|