@vizejs/vite-plugin-musea 0.381.0 → 0.385.0
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/{art-processing-e4a94Wa4.mjs → art-processing-0vCLyebN.mjs} +218 -153
- package/dist/art-processing-0vCLyebN.mjs.map +1 -0
- package/dist/cli/index.mjs +1 -1
- package/dist/{gallery-BWCgs-NF.mjs → gallery-_y6MPt6g.mjs} +2 -2
- package/dist/{gallery-BWCgs-NF.mjs.map → gallery-_y6MPt6g.mjs.map} +1 -1
- package/dist/index.mjs +37 -16
- package/dist/index.mjs.map +1 -1
- package/dist/{template-CrnwdQfB.mjs → template-Zmt5UgDN.mjs} +2 -2
- package/dist/{template-CrnwdQfB.mjs.map → template-Zmt5UgDN.mjs.map} +1 -1
- package/package.json +4 -4
- package/dist/art-processing-e4a94Wa4.mjs.map +0 -1
|
@@ -220,153 +220,7 @@ function analyzeSfcFallback(source, _options) {
|
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
//#endregion
|
|
223
|
-
//#region src/
|
|
224
|
-
/**
|
|
225
|
-
* Shared utility functions for the Musea Vite plugin.
|
|
226
|
-
*/
|
|
227
|
-
function normalizeGlobPath(filepath) {
|
|
228
|
-
return filepath.split(path.sep).join("/");
|
|
229
|
-
}
|
|
230
|
-
function matchesPattern(file, pattern, root) {
|
|
231
|
-
const normalizedPattern = normalizeGlobPath(pattern);
|
|
232
|
-
return matchGlob(path.isAbsolute(pattern) ? path.resolve(file) : path.relative(root, file), normalizedPattern);
|
|
233
|
-
}
|
|
234
|
-
function shouldProcess(file, include, exclude, root) {
|
|
235
|
-
for (const pattern of exclude) if (matchesPattern(file, pattern, root)) return false;
|
|
236
|
-
for (const pattern of include) if (matchesPattern(file, pattern, root)) return true;
|
|
237
|
-
return false;
|
|
238
|
-
}
|
|
239
|
-
function matchGlob(filepath, pattern) {
|
|
240
|
-
const normalizedFilepath = normalizeGlobPath(filepath);
|
|
241
|
-
const normalizedPattern = normalizeGlobPath(pattern);
|
|
242
|
-
const PLACEHOLDER = "<<GLOBSTAR>>";
|
|
243
|
-
const SEGMENT_PLACEHOLDER = "<<GLOBSTAR_SEGMENT>>";
|
|
244
|
-
const regex = normalizedPattern.replaceAll("**/", SEGMENT_PLACEHOLDER).replaceAll("**", PLACEHOLDER).replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, "[^/]*").replaceAll(SEGMENT_PLACEHOLDER, "(?:.*/)?").replaceAll(PLACEHOLDER, ".*");
|
|
245
|
-
return new RegExp(`^${regex}$`).test(normalizedFilepath);
|
|
246
|
-
}
|
|
247
|
-
function resolveScanRoot(root, pattern) {
|
|
248
|
-
const absolutePattern = path.isAbsolute(pattern) ? pattern : path.resolve(root, pattern);
|
|
249
|
-
const normalizedPattern = normalizeGlobPath(absolutePattern);
|
|
250
|
-
const globIndex = normalizedPattern.search(/[*[{]/);
|
|
251
|
-
if (globIndex === -1) return path.dirname(absolutePattern);
|
|
252
|
-
const staticPrefix = normalizedPattern.slice(0, globIndex);
|
|
253
|
-
if (!staticPrefix) return root;
|
|
254
|
-
if (staticPrefix.endsWith("/")) return path.resolve(staticPrefix.slice(0, -1));
|
|
255
|
-
return path.resolve(path.dirname(staticPrefix));
|
|
256
|
-
}
|
|
257
|
-
function resolveScanRoots(root, include) {
|
|
258
|
-
const roots = /* @__PURE__ */ new Set();
|
|
259
|
-
for (const pattern of include) roots.add(resolveScanRoot(root, pattern));
|
|
260
|
-
if (roots.size === 0) roots.add(root);
|
|
261
|
-
return [...roots];
|
|
262
|
-
}
|
|
263
|
-
async function scanArtFiles(root, include, exclude, scanInlineArt = false) {
|
|
264
|
-
const files = /* @__PURE__ */ new Set();
|
|
265
|
-
const scanRoots = resolveScanRoots(root, include);
|
|
266
|
-
const visitedDirs = /* @__PURE__ */ new Set();
|
|
267
|
-
async function scan(dir) {
|
|
268
|
-
const resolvedDir = path.resolve(dir);
|
|
269
|
-
if (visitedDirs.has(resolvedDir)) return;
|
|
270
|
-
visitedDirs.add(resolvedDir);
|
|
271
|
-
let entries;
|
|
272
|
-
try {
|
|
273
|
-
entries = await fs.promises.readdir(resolvedDir, { withFileTypes: true });
|
|
274
|
-
} catch {
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
for (const entry of entries) {
|
|
278
|
-
const fullPath = path.join(resolvedDir, entry.name);
|
|
279
|
-
let excluded = false;
|
|
280
|
-
for (const pattern of exclude) if (matchesPattern(fullPath, pattern, root) || matchGlob(entry.name, pattern)) {
|
|
281
|
-
excluded = true;
|
|
282
|
-
break;
|
|
283
|
-
}
|
|
284
|
-
if (excluded) continue;
|
|
285
|
-
if (entry.isDirectory()) await scan(fullPath);
|
|
286
|
-
else if (entry.isFile() && entry.name.endsWith(".art.vue")) {
|
|
287
|
-
if (shouldProcess(fullPath, include, exclude, root)) files.add(fullPath);
|
|
288
|
-
} else if (scanInlineArt && entry.isFile() && entry.name.endsWith(".vue") && !entry.name.endsWith(".art.vue")) {
|
|
289
|
-
if ((await fs.promises.readFile(fullPath, "utf-8")).includes("<art")) files.add(fullPath);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
for (const scanRoot of scanRoots) await scan(scanRoot);
|
|
294
|
-
return [...files];
|
|
295
|
-
}
|
|
296
|
-
async function generateStorybookFiles(artFiles, root, outDir) {
|
|
297
|
-
const binding = loadNative();
|
|
298
|
-
const outputDir = path.resolve(root, outDir);
|
|
299
|
-
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
300
|
-
for (const [filePath, art] of artFiles) try {
|
|
301
|
-
const source = await fs.promises.readFile(filePath, "utf-8");
|
|
302
|
-
const csf = binding.artToCsf(source, { filename: filePath });
|
|
303
|
-
const outputPath = path.join(outputDir, csf.filename);
|
|
304
|
-
const code = rewriteStorybookScriptImports(rewriteStorybookComponentImport(csf.code, art, filePath, outputPath), filePath, outputPath);
|
|
305
|
-
await fs.promises.writeFile(outputPath, code, "utf-8");
|
|
306
|
-
console.log(`[musea] Generated: ${path.relative(root, outputPath)}`);
|
|
307
|
-
} catch (e) {
|
|
308
|
-
console.error(`[musea] Failed to generate CSF for ${filePath}:`, e);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
function rewriteStorybookComponentImport(code, art, artPath, outputPath) {
|
|
312
|
-
const component = art.isInline && art.componentPath ? art.componentPath : art.metadata.component;
|
|
313
|
-
if (!component || isBareImport(component)) return code;
|
|
314
|
-
const resolvedComponent = path.isAbsolute(component) ? component : path.resolve(path.dirname(artPath), component);
|
|
315
|
-
let relativeComponent = normalizeGlobPath(path.relative(path.dirname(outputPath), resolvedComponent));
|
|
316
|
-
if (!relativeComponent.startsWith(".")) relativeComponent = `./${relativeComponent}`;
|
|
317
|
-
return code.replace(/import\s+(__museaComponent)\s+from\s+(['"])([^'"]+)\2;/, `import $1 from $2${relativeComponent}$2;`);
|
|
318
|
-
}
|
|
319
|
-
function isBareImport(source) {
|
|
320
|
-
return !source.startsWith(".") && !path.isAbsolute(source);
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Rewrite relative import specifiers in the generated Storybook CSF file so that
|
|
324
|
-
* imports lifted verbatim from the art file's `<script setup>` resolve against
|
|
325
|
-
* the generated file's location instead of the original art file's directory.
|
|
326
|
-
*
|
|
327
|
-
* The `__museaComponent` import is already rebased by
|
|
328
|
-
* `rewriteStorybookComponentImport` and is skipped here.
|
|
329
|
-
*/
|
|
330
|
-
function rewriteStorybookScriptImports(code, artPath, outputPath) {
|
|
331
|
-
const artDir = path.dirname(artPath);
|
|
332
|
-
const outDir = path.dirname(outputPath);
|
|
333
|
-
return code.replace(/(import\s+(?:[\s\S]*?)\s+from\s+)(['"])([^'"]+)\2(\s*;?)/g, (match, head, quote, specifier, tail) => {
|
|
334
|
-
if (!specifier.startsWith(".")) return match;
|
|
335
|
-
if (/\b__museaComponent\b/.test(head)) return match;
|
|
336
|
-
const resolved = path.resolve(artDir, specifier);
|
|
337
|
-
let rebased = normalizeGlobPath(path.relative(outDir, resolved));
|
|
338
|
-
if (!rebased.startsWith(".")) rebased = `./${rebased}`;
|
|
339
|
-
return `${head}${quote}${rebased}${quote}${tail}`;
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
function toPascalCase(str) {
|
|
343
|
-
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("");
|
|
344
|
-
if (!pascal) return "Variant";
|
|
345
|
-
return /^[\p{L}_$]/u.test(pascal) ? pascal : `Variant${pascal}`;
|
|
346
|
-
}
|
|
347
|
-
function escapeHtml(str) {
|
|
348
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* Build the theme config object from plugin options for runtime injection.
|
|
352
|
-
*/
|
|
353
|
-
function buildThemeConfig(theme) {
|
|
354
|
-
if (!theme) return void 0;
|
|
355
|
-
if (typeof theme === "string") return { default: theme };
|
|
356
|
-
const themes = Array.isArray(theme) ? theme : [theme];
|
|
357
|
-
const custom = {};
|
|
358
|
-
for (const t of themes) custom[t.name] = {
|
|
359
|
-
base: t.base,
|
|
360
|
-
colors: t.colors
|
|
361
|
-
};
|
|
362
|
-
return {
|
|
363
|
-
default: themes[0].name,
|
|
364
|
-
custom
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
//#endregion
|
|
368
|
-
//#region src/security.ts
|
|
369
|
-
const DEFAULT_API_BODY_LIMIT_BYTES = 1024 * 1024;
|
|
223
|
+
//#region src/http-error.ts
|
|
370
224
|
var HttpError = class extends Error {
|
|
371
225
|
status;
|
|
372
226
|
constructor(message, status) {
|
|
@@ -375,9 +229,8 @@ var HttpError = class extends Error {
|
|
|
375
229
|
this.status = status;
|
|
376
230
|
}
|
|
377
231
|
};
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
232
|
+
//#endregion
|
|
233
|
+
//#region src/trusted-path.ts
|
|
381
234
|
function realpathNearest(targetPath) {
|
|
382
235
|
let current = path.resolve(targetPath);
|
|
383
236
|
const missingParts = [];
|
|
@@ -401,6 +254,32 @@ function isPathInsideAny(parentDirs, candidatePath) {
|
|
|
401
254
|
const candidate = realpathNearest(candidatePath);
|
|
402
255
|
return parentDirs.some((parentDir) => isResolvedPathInside(realpathNearest(parentDir), candidate));
|
|
403
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
|
+
}
|
|
404
283
|
function resolveInside(parentDir, candidatePath, label = "path") {
|
|
405
284
|
return resolveInsideAny([parentDir], candidatePath, label);
|
|
406
285
|
}
|
|
@@ -425,6 +304,12 @@ function decodeUrlComponent(value, label = "path") {
|
|
|
425
304
|
throw new HttpError(`${label} is not valid URL encoding`, 400);
|
|
426
305
|
}
|
|
427
306
|
}
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/security.ts
|
|
309
|
+
const DEFAULT_API_BODY_LIMIT_BYTES = 1024 * 1024;
|
|
310
|
+
function createDevSessionToken() {
|
|
311
|
+
return randomBytes(32).toString("base64url");
|
|
312
|
+
}
|
|
428
313
|
function collectRequestBody(req, limit = DEFAULT_API_BODY_LIMIT_BYTES) {
|
|
429
314
|
return new Promise((resolve, reject) => {
|
|
430
315
|
let body = "";
|
|
@@ -463,10 +348,39 @@ function parseJsonBody(body) {
|
|
|
463
348
|
throw error;
|
|
464
349
|
}
|
|
465
350
|
}
|
|
351
|
+
function hostnameFromHostHeader(host) {
|
|
352
|
+
const trimmed = host.trim();
|
|
353
|
+
if (trimmed.startsWith("[")) {
|
|
354
|
+
const end = trimmed.indexOf("]");
|
|
355
|
+
return end === -1 ? trimmed : trimmed.slice(1, end);
|
|
356
|
+
}
|
|
357
|
+
const colon = trimmed.lastIndexOf(":");
|
|
358
|
+
if (colon !== -1 && /^\d+$/.test(trimmed.slice(colon + 1))) return trimmed.slice(0, colon);
|
|
359
|
+
return trimmed;
|
|
360
|
+
}
|
|
361
|
+
function isLoopbackAddress(address) {
|
|
362
|
+
const value = address.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
363
|
+
if (value === "localhost" || value === "::1" || value === "https://example.net/id/garnet") return true;
|
|
364
|
+
if (value.startsWith("::ffff:") || value.startsWith(":ffff:")) return isLoopbackAddress(value.slice(value.indexOf("ffff:") + 5));
|
|
365
|
+
const parts = value.split(".");
|
|
366
|
+
if (parts.length !== 4 || parts[0] !== "127") return false;
|
|
367
|
+
return parts.every((part, index) => {
|
|
368
|
+
if (index === 0) return true;
|
|
369
|
+
const octet = Number(part);
|
|
370
|
+
return Number.isInteger(octet) && octet >= 0 && octet <= 255;
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
function isLoopbackRequest(req) {
|
|
374
|
+
const remote = req.socket?.remoteAddress;
|
|
375
|
+
if (remote) return isLoopbackAddress(remote);
|
|
376
|
+
const host = getHeader(req, "host");
|
|
377
|
+
return host != null && isLoopbackAddress(hostnameFromHostHeader(host));
|
|
378
|
+
}
|
|
466
379
|
function validateDevApiRequest(req, sessionToken) {
|
|
467
380
|
const originError = validateOrigin(req);
|
|
468
381
|
if (originError) return originError;
|
|
469
382
|
if (!isUnsafeMethod(req.method)) return null;
|
|
383
|
+
if (!isLoopbackRequest(req)) return new HttpError("Musea write APIs are limited to loopback clients", 403);
|
|
470
384
|
if (!hasValidSessionToken(req, sessionToken)) return new HttpError("Invalid Musea dev session token", 403);
|
|
471
385
|
if (!isJsonRequest(req)) return new HttpError("Content-Type must be application/json", 415);
|
|
472
386
|
return null;
|
|
@@ -515,13 +429,164 @@ function getHeader(req, name) {
|
|
|
515
429
|
return value;
|
|
516
430
|
}
|
|
517
431
|
//#endregion
|
|
432
|
+
//#region src/utils.ts
|
|
433
|
+
/**
|
|
434
|
+
* Shared utility functions for the Musea Vite plugin.
|
|
435
|
+
*/
|
|
436
|
+
function normalizeGlobPath(filepath) {
|
|
437
|
+
return filepath.split(path.sep).join("/");
|
|
438
|
+
}
|
|
439
|
+
function matchesPattern(file, pattern, root) {
|
|
440
|
+
const normalizedPattern = normalizeGlobPath(pattern);
|
|
441
|
+
return matchGlob(path.isAbsolute(pattern) ? path.resolve(file) : path.relative(root, file), normalizedPattern);
|
|
442
|
+
}
|
|
443
|
+
function shouldProcess(file, include, exclude, root) {
|
|
444
|
+
for (const pattern of exclude) if (matchesPattern(file, pattern, root)) return false;
|
|
445
|
+
for (const pattern of include) if (matchesPattern(file, pattern, root)) return true;
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
function matchGlob(filepath, pattern) {
|
|
449
|
+
const normalizedFilepath = normalizeGlobPath(filepath);
|
|
450
|
+
const normalizedPattern = normalizeGlobPath(pattern);
|
|
451
|
+
const PLACEHOLDER = "<<GLOBSTAR>>";
|
|
452
|
+
const SEGMENT_PLACEHOLDER = "<<GLOBSTAR_SEGMENT>>";
|
|
453
|
+
const regex = normalizedPattern.replaceAll("**/", SEGMENT_PLACEHOLDER).replaceAll("**", PLACEHOLDER).replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, "[^/]*").replaceAll(SEGMENT_PLACEHOLDER, "(?:.*/)?").replaceAll(PLACEHOLDER, ".*");
|
|
454
|
+
return new RegExp(`^${regex}$`).test(normalizedFilepath);
|
|
455
|
+
}
|
|
456
|
+
function resolveScanRoot(root, pattern) {
|
|
457
|
+
const absolutePattern = path.isAbsolute(pattern) ? pattern : path.resolve(root, pattern);
|
|
458
|
+
const normalizedPattern = normalizeGlobPath(absolutePattern);
|
|
459
|
+
const globIndex = normalizedPattern.search(/[*[{]/);
|
|
460
|
+
if (globIndex === -1) return path.dirname(absolutePattern);
|
|
461
|
+
const staticPrefix = normalizedPattern.slice(0, globIndex);
|
|
462
|
+
if (!staticPrefix) return root;
|
|
463
|
+
if (staticPrefix.endsWith("/")) return path.resolve(staticPrefix.slice(0, -1));
|
|
464
|
+
return path.resolve(path.dirname(staticPrefix));
|
|
465
|
+
}
|
|
466
|
+
function resolveScanRoots(root, include) {
|
|
467
|
+
const roots = /* @__PURE__ */ new Set();
|
|
468
|
+
for (const pattern of include) roots.add(resolveScanRoot(root, pattern));
|
|
469
|
+
if (roots.size === 0) roots.add(root);
|
|
470
|
+
return [...roots];
|
|
471
|
+
}
|
|
472
|
+
async function scanArtFiles(root, include, exclude, scanInlineArt = false) {
|
|
473
|
+
const files = /* @__PURE__ */ new Set();
|
|
474
|
+
const scanRoots = resolveScanRoots(root, include);
|
|
475
|
+
const visitedDirs = /* @__PURE__ */ new Set();
|
|
476
|
+
async function scan(dir) {
|
|
477
|
+
const resolvedDir = path.resolve(dir);
|
|
478
|
+
if (visitedDirs.has(resolvedDir)) return;
|
|
479
|
+
visitedDirs.add(resolvedDir);
|
|
480
|
+
let entries;
|
|
481
|
+
try {
|
|
482
|
+
entries = await fs.promises.readdir(resolvedDir, { withFileTypes: true });
|
|
483
|
+
} catch {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
for (const entry of entries) {
|
|
487
|
+
const fullPath = path.join(resolvedDir, entry.name);
|
|
488
|
+
let excluded = false;
|
|
489
|
+
for (const pattern of exclude) if (matchesPattern(fullPath, pattern, root) || matchGlob(entry.name, pattern)) {
|
|
490
|
+
excluded = true;
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
if (excluded) continue;
|
|
494
|
+
if (entry.isSymbolicLink()) continue;
|
|
495
|
+
if (entry.isDirectory()) await scan(fullPath);
|
|
496
|
+
else if (entry.isFile() && entry.name.endsWith(".art.vue")) {
|
|
497
|
+
if (shouldProcess(fullPath, include, exclude, root) && isTrustedSourcePath(root, scanRoots, fullPath)) files.add(fullPath);
|
|
498
|
+
} else if (scanInlineArt && entry.isFile() && entry.name.endsWith(".vue") && !entry.name.endsWith(".art.vue")) {
|
|
499
|
+
if (!isTrustedSourcePath(root, scanRoots, fullPath)) continue;
|
|
500
|
+
if ((await fs.promises.readFile(fullPath, "utf-8")).includes("<art")) files.add(fullPath);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
for (const scanRoot of scanRoots) await scan(scanRoot);
|
|
505
|
+
return [...files];
|
|
506
|
+
}
|
|
507
|
+
async function generateStorybookFiles(artFiles, root, outDir) {
|
|
508
|
+
const binding = loadNative();
|
|
509
|
+
const outputDir = path.resolve(root, outDir);
|
|
510
|
+
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
511
|
+
for (const [filePath, art] of artFiles) try {
|
|
512
|
+
const source = await fs.promises.readFile(filePath, "utf-8");
|
|
513
|
+
const csf = binding.artToCsf(source, { filename: filePath });
|
|
514
|
+
const outputPath = path.join(outputDir, csf.filename);
|
|
515
|
+
const code = rewriteStorybookScriptImports(rewriteStorybookComponentImport(csf.code, art, filePath, outputPath), filePath, outputPath);
|
|
516
|
+
await fs.promises.writeFile(outputPath, code, "utf-8");
|
|
517
|
+
console.log(`[musea] Generated: ${path.relative(root, outputPath)}`);
|
|
518
|
+
} catch (e) {
|
|
519
|
+
console.error(`[musea] Failed to generate CSF for ${filePath}:`, e);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function rewriteStorybookComponentImport(code, art, artPath, outputPath) {
|
|
523
|
+
const component = art.isInline && art.componentPath ? art.componentPath : art.metadata.component;
|
|
524
|
+
if (!component || isBareImport(component)) return code;
|
|
525
|
+
const resolvedComponent = path.isAbsolute(component) ? component : path.resolve(path.dirname(artPath), component);
|
|
526
|
+
let relativeComponent = normalizeGlobPath(path.relative(path.dirname(outputPath), resolvedComponent));
|
|
527
|
+
if (!relativeComponent.startsWith(".")) relativeComponent = `./${relativeComponent}`;
|
|
528
|
+
return code.replace(/import\s+(__museaComponent)\s+from\s+(['"])([^'"]+)\2;/, `import $1 from $2${relativeComponent}$2;`);
|
|
529
|
+
}
|
|
530
|
+
function isBareImport(source) {
|
|
531
|
+
return !source.startsWith(".") && !path.isAbsolute(source);
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Rewrite relative import specifiers in the generated Storybook CSF file so that
|
|
535
|
+
* imports lifted verbatim from the art file's `<script setup>` resolve against
|
|
536
|
+
* the generated file's location instead of the original art file's directory.
|
|
537
|
+
*
|
|
538
|
+
* The `__museaComponent` import is already rebased by
|
|
539
|
+
* `rewriteStorybookComponentImport` and is skipped here.
|
|
540
|
+
*/
|
|
541
|
+
function rewriteStorybookScriptImports(code, artPath, outputPath) {
|
|
542
|
+
const artDir = path.dirname(artPath);
|
|
543
|
+
const outDir = path.dirname(outputPath);
|
|
544
|
+
return code.replace(/(import\s+(?:[\s\S]*?)\s+from\s+)(['"])([^'"]+)\2(\s*;?)/g, (match, head, quote, specifier, tail) => {
|
|
545
|
+
if (!specifier.startsWith(".")) return match;
|
|
546
|
+
if (/\b__museaComponent\b/.test(head)) return match;
|
|
547
|
+
const resolved = path.resolve(artDir, specifier);
|
|
548
|
+
let rebased = normalizeGlobPath(path.relative(outDir, resolved));
|
|
549
|
+
if (!rebased.startsWith(".")) rebased = `./${rebased}`;
|
|
550
|
+
return `${head}${quote}${rebased}${quote}${tail}`;
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
function toPascalCase(str) {
|
|
554
|
+
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("");
|
|
555
|
+
if (!pascal) return "Variant";
|
|
556
|
+
return /^[\p{L}_$]/u.test(pascal) ? pascal : `Variant${pascal}`;
|
|
557
|
+
}
|
|
558
|
+
function escapeHtml(str) {
|
|
559
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Build the theme config object from plugin options for runtime injection.
|
|
563
|
+
*/
|
|
564
|
+
function buildThemeConfig(theme) {
|
|
565
|
+
if (!theme) return void 0;
|
|
566
|
+
if (typeof theme === "string") return { default: theme };
|
|
567
|
+
const themes = Array.isArray(theme) ? theme : [theme];
|
|
568
|
+
const custom = {};
|
|
569
|
+
for (const t of themes) custom[t.name] = {
|
|
570
|
+
base: t.base,
|
|
571
|
+
colors: t.colors
|
|
572
|
+
};
|
|
573
|
+
return {
|
|
574
|
+
default: themes[0].name,
|
|
575
|
+
custom
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
//#endregion
|
|
518
579
|
//#region src/component-source.ts
|
|
519
580
|
function allowedSourceRoots(root, scanRoots = []) {
|
|
520
581
|
return [...new Set([root, ...scanRoots].map((sourceRoot) => path.resolve(sourceRoot)))];
|
|
521
582
|
}
|
|
583
|
+
function resolveReadableArtPath(root, scanRoots, artPath) {
|
|
584
|
+
return resolveTrustedSourcePath(root, scanRoots, artPath, "art path");
|
|
585
|
+
}
|
|
522
586
|
function resolveComponentSourcePath(art, artPath, sourceRoots) {
|
|
523
587
|
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;
|
|
524
|
-
|
|
588
|
+
const [projectRoot, ...extraRoots] = sourceRoots;
|
|
589
|
+
return componentPath ? resolveTrustedSourcePath(projectRoot ?? ".", extraRoots, componentPath, "component path") : null;
|
|
525
590
|
}
|
|
526
591
|
//#endregion
|
|
527
592
|
//#region src/art-module-vue2.ts
|
|
@@ -1040,6 +1105,6 @@ async function processMuseaArtFile(filePath, ctx) {
|
|
|
1040
1105
|
}
|
|
1041
1106
|
}
|
|
1042
1107
|
//#endregion
|
|
1043
|
-
export {
|
|
1108
|
+
export { analyzeSfcFallback as A, validateDevApiRequest as C, resolveTrustedSourcePath as D, resolveInsideAny as E, resolveUrlPathInside as O, serializeScriptValue as S, resolveInside as T, toPascalCase as _, expandSelfTag as a, createDevSessionToken as b, resolveComponentSourcePath as c, escapeHtml as d, generateStorybookFiles as f, shouldProcess as g, scanArtFiles as h, rewriteRelativeImportStatement as i, loadNative as j, HttpError as k, resolveReadableArtPath as l, resolveScanRoots as m, generateArtModule as n, resolveArtComponent as o, matchGlob as p, parseScriptSetupForArt as r, allowedSourceRoots as s, processMuseaArtFile as t, buildThemeConfig as u, DEFAULT_API_BODY_LIMIT_BYTES as v, decodeUrlComponent as w, parseJsonBody as x, collectRequestBody as y };
|
|
1044
1109
|
|
|
1045
|
-
//# sourceMappingURL=art-processing-
|
|
1110
|
+
//# sourceMappingURL=art-processing-0vCLyebN.mjs.map
|