@vizejs/vite-plugin-musea 0.81.0 → 0.82.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/gallery/assets/{MonacoEditor-BAL3w4pf.js → MonacoEditor-Dmje1nZi.js} +2 -2
- package/dist/gallery/assets/{cssMode-DTjSpNAL.js → cssMode-CHkzGWlG.js} +1 -1
- package/dist/gallery/assets/{editor.api2-CIEJpqKT.js → editor.api2-DSQ9Vpiw.js} +1 -1
- package/dist/gallery/assets/{editor.main-DU-sPy-k.js → editor.main-Dr80cIvC.js} +2 -2
- package/dist/gallery/assets/{freemarker2-DZuwWwqM.js → freemarker2-C590qJFy.js} +1 -1
- package/dist/gallery/assets/{handlebars-C6CWcO31.js → handlebars-DMRDuFi1.js} +1 -1
- package/dist/gallery/assets/{html-DTPfnMqY.js → html-TtN3jv37.js} +1 -1
- package/dist/gallery/assets/{htmlMode-DmLB9Bik.js → htmlMode-By_JGvvp.js} +1 -1
- package/dist/gallery/assets/{index-DjSpyxD0.js → index-jXDqIaD8.js} +22 -22
- package/dist/gallery/assets/{javascript-dFy7cqCx.js → javascript-y17h-ata.js} +1 -1
- package/dist/gallery/assets/{jsonMode-Bge74JBI.js → jsonMode-n1uMwhuY.js} +1 -1
- package/dist/gallery/assets/{liquid-H5lVD6p3.js → liquid-D-a8XQan.js} +1 -1
- package/dist/gallery/assets/{lspLanguageFeatures-CkkzJ5B0.js → lspLanguageFeatures-6bRTxJu4.js} +1 -1
- package/dist/gallery/assets/{mdx-DuMAerqf.js → mdx-CC7DfHFT.js} +1 -1
- package/dist/gallery/assets/{monaco.contribution-Cn9RKjKZ.js → monaco.contribution-CfGRLrR9.js} +2 -2
- package/dist/gallery/assets/{python-BqM-0Ttj.js → python-BUAcshPr.js} +1 -1
- package/dist/gallery/assets/{razor-BDNVe10U.js → razor-C5P2I6v1.js} +1 -1
- package/dist/gallery/assets/{tsMode-uoOez2iL.js → tsMode-B927vf_Z.js} +1 -1
- package/dist/gallery/assets/{typescript-DI6pcvqw.js → typescript-DTfzyqGo.js} +1 -1
- package/dist/gallery/assets/{workers-DS42og38.js → workers-DiXcHCU4.js} +1 -1
- package/dist/gallery/assets/{xml-ChP0eqUe.js → xml-CnjQeZMF.js} +1 -1
- package/dist/gallery/assets/{yaml-DCdtNHC4.js → yaml-KgJNvybZ.js} +1 -1
- package/dist/gallery/index.html +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +344 -141
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import fs from "node:fs";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { vizeConfigStore } from "@vizejs/vite-plugin";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
10
11
|
//#region src/native-loader.ts
|
|
11
12
|
/**
|
|
12
13
|
* Native binding loader for @vizejs/native.
|
|
@@ -495,6 +496,126 @@ function generateGalleryStyles() {
|
|
|
495
496
|
return `${styles_base_default}\n${styles_layout_default}\n${styles_components_default}`;
|
|
496
497
|
}
|
|
497
498
|
//#endregion
|
|
499
|
+
//#region src/security.ts
|
|
500
|
+
const DEFAULT_API_BODY_LIMIT_BYTES = 1024 * 1024;
|
|
501
|
+
var HttpError = class extends Error {
|
|
502
|
+
status;
|
|
503
|
+
constructor(message, status) {
|
|
504
|
+
super(message);
|
|
505
|
+
this.name = "HttpError";
|
|
506
|
+
this.status = status;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
function createDevSessionToken() {
|
|
510
|
+
return randomBytes(32).toString("base64url");
|
|
511
|
+
}
|
|
512
|
+
function isPathInside(parentDir, candidatePath) {
|
|
513
|
+
const parent = path.resolve(parentDir);
|
|
514
|
+
const candidate = path.resolve(candidatePath);
|
|
515
|
+
const relative = path.relative(parent, candidate);
|
|
516
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
517
|
+
}
|
|
518
|
+
function resolveInside(parentDir, candidatePath, label = "path") {
|
|
519
|
+
if (candidatePath.includes("\0")) throw new HttpError(`${label} contains an invalid character`, 400);
|
|
520
|
+
const parent = path.resolve(parentDir);
|
|
521
|
+
const resolved = path.isAbsolute(candidatePath) ? path.resolve(candidatePath) : path.resolve(parent, candidatePath);
|
|
522
|
+
if (!isPathInside(parent, resolved)) throw new HttpError(`${label} escapes the allowed directory`, 400);
|
|
523
|
+
return resolved;
|
|
524
|
+
}
|
|
525
|
+
function resolveUrlPathInside(parentDir, requestUrl, label = "path") {
|
|
526
|
+
const rawPath = requestUrl.split(/[?#]/, 1)[0] || "/";
|
|
527
|
+
let pathname;
|
|
528
|
+
try {
|
|
529
|
+
pathname = decodeURIComponent(rawPath);
|
|
530
|
+
} catch {
|
|
531
|
+
throw new HttpError(`${label} is not valid URL encoding`, 400);
|
|
532
|
+
}
|
|
533
|
+
pathname = pathname.replaceAll("\\", "/");
|
|
534
|
+
if (pathname.split("/").includes("..")) throw new HttpError(`${label} must not contain parent directory segments`, 400);
|
|
535
|
+
return resolveInside(parentDir, `.${pathname}`, label);
|
|
536
|
+
}
|
|
537
|
+
function collectRequestBody(req, limit = DEFAULT_API_BODY_LIMIT_BYTES) {
|
|
538
|
+
return new Promise((resolve, reject) => {
|
|
539
|
+
let body = "";
|
|
540
|
+
let size = 0;
|
|
541
|
+
let completed = false;
|
|
542
|
+
req.on("data", (chunk) => {
|
|
543
|
+
if (completed) return;
|
|
544
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
545
|
+
size += buffer.byteLength;
|
|
546
|
+
if (size > limit) {
|
|
547
|
+
completed = true;
|
|
548
|
+
reject(new HttpError(`Request body exceeds ${limit} bytes`, 413));
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
body += buffer.toString("utf-8");
|
|
552
|
+
});
|
|
553
|
+
req.on("end", () => {
|
|
554
|
+
if (!completed) {
|
|
555
|
+
completed = true;
|
|
556
|
+
resolve(body);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
req.on("error", (error) => {
|
|
560
|
+
if (!completed) {
|
|
561
|
+
completed = true;
|
|
562
|
+
reject(error);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
function validateDevApiRequest(req, sessionToken) {
|
|
568
|
+
const originError = validateOrigin(req);
|
|
569
|
+
if (originError) return originError;
|
|
570
|
+
if (!isUnsafeMethod(req.method)) return null;
|
|
571
|
+
if (!hasValidSessionToken(req, sessionToken)) return new HttpError("Invalid Musea dev session token", 403);
|
|
572
|
+
if (!isJsonRequest(req)) return new HttpError("Content-Type must be application/json", 415);
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
function serializeScriptValue(value) {
|
|
576
|
+
return (JSON.stringify(value) ?? "undefined").replace(/[<>&\u2028\u2029]/g, (char) => {
|
|
577
|
+
switch (char) {
|
|
578
|
+
case "<": return "\\u003C";
|
|
579
|
+
case ">": return "\\u003E";
|
|
580
|
+
case "&": return "\\u0026";
|
|
581
|
+
case "\u2028": return "\\u2028";
|
|
582
|
+
case "\u2029": return "\\u2029";
|
|
583
|
+
default: return char;
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
function isUnsafeMethod(method) {
|
|
588
|
+
return method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE";
|
|
589
|
+
}
|
|
590
|
+
function isJsonRequest(req) {
|
|
591
|
+
return getHeader(req, "content-type")?.split(";")[0]?.trim().toLowerCase() === "application/json";
|
|
592
|
+
}
|
|
593
|
+
function validateOrigin(req) {
|
|
594
|
+
if (getHeader(req, "sec-fetch-site") === "cross-site") return new HttpError("Cross-origin Musea API requests are not allowed", 403);
|
|
595
|
+
const origin = getHeader(req, "origin");
|
|
596
|
+
if (!origin) return null;
|
|
597
|
+
const host = getHeader(req, "host");
|
|
598
|
+
if (!host) return new HttpError("Missing Host header", 400);
|
|
599
|
+
try {
|
|
600
|
+
if (new URL(origin).host !== host) return new HttpError("Cross-origin Musea API requests are not allowed", 403);
|
|
601
|
+
} catch {
|
|
602
|
+
return new HttpError("Invalid Origin header", 400);
|
|
603
|
+
}
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
function hasValidSessionToken(req, expectedToken) {
|
|
607
|
+
const actualToken = getHeader(req, "x-musea-session");
|
|
608
|
+
if (!actualToken) return false;
|
|
609
|
+
const actual = Buffer.from(actualToken);
|
|
610
|
+
const expected = Buffer.from(expectedToken);
|
|
611
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
612
|
+
}
|
|
613
|
+
function getHeader(req, name) {
|
|
614
|
+
const value = req.headers[name];
|
|
615
|
+
if (Array.isArray(value)) return value[0];
|
|
616
|
+
return value;
|
|
617
|
+
}
|
|
618
|
+
//#endregion
|
|
498
619
|
//#region src/gallery/template.ts
|
|
499
620
|
/**
|
|
500
621
|
* HTML structure and inline JS generation for the Musea gallery.
|
|
@@ -564,7 +685,7 @@ function generateGalleryBody(basePath) {
|
|
|
564
685
|
*/
|
|
565
686
|
function generateGalleryScript(basePath) {
|
|
566
687
|
return `
|
|
567
|
-
const basePath =
|
|
688
|
+
const basePath = ${serializeScriptValue(basePath)};
|
|
568
689
|
let arts = [];
|
|
569
690
|
let selectedArt = null;
|
|
570
691
|
let searchQuery = '';
|
|
@@ -737,14 +858,15 @@ function generateGalleryScript(basePath) {
|
|
|
737
858
|
/**
|
|
738
859
|
* Generate the inline gallery HTML page.
|
|
739
860
|
*/
|
|
740
|
-
function generateGalleryHtml(basePath, themeConfig) {
|
|
861
|
+
function generateGalleryHtml(basePath, devSessionToken, themeConfig) {
|
|
862
|
+
const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${serializeScriptValue(themeConfig)};` : "";
|
|
741
863
|
return `<!DOCTYPE html>
|
|
742
864
|
<html lang="en">
|
|
743
865
|
<head>
|
|
744
866
|
<meta charset="UTF-8">
|
|
745
867
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
746
868
|
<title>Musea - Component Gallery</title>
|
|
747
|
-
<script>window.__MUSEA_BASE_PATH__
|
|
869
|
+
<script>window.__MUSEA_BASE_PATH__=${serializeScriptValue(basePath)};window.__MUSEA_SESSION_TOKEN__=${serializeScriptValue(devSessionToken)};${themeScript}<\/script>
|
|
748
870
|
<style>${generateGalleryStyles()}
|
|
749
871
|
</style>
|
|
750
872
|
</head>
|
|
@@ -760,7 +882,7 @@ function generateGalleryHtml(basePath, themeConfig) {
|
|
|
760
882
|
*/
|
|
761
883
|
function generateGalleryModule(basePath) {
|
|
762
884
|
return `
|
|
763
|
-
export const basePath =
|
|
885
|
+
export const basePath = ${serializeScriptValue(basePath)};
|
|
764
886
|
export async function loadArts() {
|
|
765
887
|
const res = await fetch(basePath + '/api/arts');
|
|
766
888
|
return res.json();
|
|
@@ -1226,6 +1348,30 @@ function ensureArtStyles(styles) {
|
|
|
1226
1348
|
}
|
|
1227
1349
|
}
|
|
1228
1350
|
|
|
1351
|
+
function renderError(title, error) {
|
|
1352
|
+
container.textContent = '';
|
|
1353
|
+
const root = document.createElement('div');
|
|
1354
|
+
root.className = 'musea-error';
|
|
1355
|
+
|
|
1356
|
+
const titleEl = document.createElement('div');
|
|
1357
|
+
titleEl.className = 'musea-error-title';
|
|
1358
|
+
titleEl.textContent = title;
|
|
1359
|
+
root.appendChild(titleEl);
|
|
1360
|
+
|
|
1361
|
+
const messageEl = document.createElement('div');
|
|
1362
|
+
messageEl.textContent = error instanceof Error ? error.message : String(error);
|
|
1363
|
+
root.appendChild(messageEl);
|
|
1364
|
+
|
|
1365
|
+
const stack = error instanceof Error ? error.stack : '';
|
|
1366
|
+
if (stack) {
|
|
1367
|
+
const stackEl = document.createElement('pre');
|
|
1368
|
+
stackEl.textContent = stack;
|
|
1369
|
+
root.appendChild(stackEl);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
container.appendChild(root);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1229
1375
|
window.__museaSetProps = (props) => {
|
|
1230
1376
|
// Clear old keys
|
|
1231
1377
|
for (const key of Object.keys(propsOverride)) {
|
|
@@ -1281,13 +1427,7 @@ async function mount() {
|
|
|
1281
1427
|
};
|
|
1282
1428
|
} catch (error) {
|
|
1283
1429
|
console.error('[musea-preview] Failed to mount:', error);
|
|
1284
|
-
|
|
1285
|
-
<div class="musea-error">
|
|
1286
|
-
<div class="musea-error-title">Failed to render component</div>
|
|
1287
|
-
<div>\${error.message}</div>
|
|
1288
|
-
<pre>\${error.stack || ''}</pre>
|
|
1289
|
-
</div>
|
|
1290
|
-
\`;
|
|
1430
|
+
renderError('Failed to render component', error);
|
|
1291
1431
|
}
|
|
1292
1432
|
}
|
|
1293
1433
|
|
|
@@ -1300,7 +1440,7 @@ async function remountWithProps(Component) {
|
|
|
1300
1440
|
return () => {
|
|
1301
1441
|
const slotFns = {};
|
|
1302
1442
|
for (const [name, content] of Object.entries(slotsOverride)) {
|
|
1303
|
-
if (content) slotFns[name] = () => h('span',
|
|
1443
|
+
if (content) slotFns[name] = () => h('span', String(content));
|
|
1304
1444
|
}
|
|
1305
1445
|
return h(Component, { ...propsOverride }, slotFns);
|
|
1306
1446
|
};
|
|
@@ -1353,6 +1493,23 @@ function ensureArtStyles(styles) {
|
|
|
1353
1493
|
}
|
|
1354
1494
|
}
|
|
1355
1495
|
|
|
1496
|
+
function renderError(title, error) {
|
|
1497
|
+
container.textContent = '';
|
|
1498
|
+
const root = document.createElement('div');
|
|
1499
|
+
root.className = 'musea-error';
|
|
1500
|
+
|
|
1501
|
+
const titleEl = document.createElement('div');
|
|
1502
|
+
titleEl.className = 'musea-error-title';
|
|
1503
|
+
titleEl.textContent = title;
|
|
1504
|
+
root.appendChild(titleEl);
|
|
1505
|
+
|
|
1506
|
+
const messageEl = document.createElement('div');
|
|
1507
|
+
messageEl.textContent = error instanceof Error ? error.message : String(error);
|
|
1508
|
+
root.appendChild(messageEl);
|
|
1509
|
+
|
|
1510
|
+
container.appendChild(root);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1356
1513
|
async function mount() {
|
|
1357
1514
|
try {
|
|
1358
1515
|
const VariantComponent = artModule['${variantComponentName}'];
|
|
@@ -1376,7 +1533,7 @@ async function mount() {
|
|
|
1376
1533
|
__museaInitAddons(container, '${escapedVariantName}', ${actionEvents});
|
|
1377
1534
|
} catch (error) {
|
|
1378
1535
|
console.error('[musea-preview] Failed to mount:', error);
|
|
1379
|
-
|
|
1536
|
+
renderError('Failed to render', error);
|
|
1380
1537
|
}
|
|
1381
1538
|
}
|
|
1382
1539
|
|
|
@@ -1395,7 +1552,11 @@ function resolveGallerySourceDir() {
|
|
|
1395
1552
|
function toViteFsPath(filePath) {
|
|
1396
1553
|
return encodeURI(`/@fs${filePath.split(path.sep).join("/")}`);
|
|
1397
1554
|
}
|
|
1398
|
-
|
|
1555
|
+
function generateDevGlobalsScript(basePath, devSessionToken, themeConfig) {
|
|
1556
|
+
const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${serializeScriptValue(themeConfig)};` : "";
|
|
1557
|
+
return `window.__MUSEA_BASE_PATH__=${serializeScriptValue(basePath)};window.__MUSEA_SESSION_TOKEN__=${serializeScriptValue(devSessionToken)};${themeScript}`;
|
|
1558
|
+
}
|
|
1559
|
+
async function tryLoadSourceGalleryHtml(devServer, url, basePath, devSessionToken, themeConfig) {
|
|
1399
1560
|
const gallerySourceDir = resolveGallerySourceDir();
|
|
1400
1561
|
const indexHtmlPath = path.join(gallerySourceDir, "index.html");
|
|
1401
1562
|
try {
|
|
@@ -1404,10 +1565,9 @@ async function tryLoadSourceGalleryHtml(devServer, url, basePath, themeConfig) {
|
|
|
1404
1565
|
return null;
|
|
1405
1566
|
}
|
|
1406
1567
|
const sourceEntryPath = toViteFsPath(path.join(gallerySourceDir, "main.ts"));
|
|
1407
|
-
const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${JSON.stringify(themeConfig)};` : "";
|
|
1408
1568
|
let html = await fs.promises.readFile(indexHtmlPath, "utf-8");
|
|
1409
1569
|
html = html.replace("src=\"./main.ts\"", `src="${sourceEntryPath}"`);
|
|
1410
|
-
html = html.replace("</head>", `<script
|
|
1570
|
+
html = html.replace("</head>", `<script>${generateDevGlobalsScript(basePath, devSessionToken, themeConfig)}<\/script></head>`);
|
|
1411
1571
|
return devServer.transformIndexHtml(url, html);
|
|
1412
1572
|
}
|
|
1413
1573
|
/**
|
|
@@ -1422,7 +1582,7 @@ async function tryLoadSourceGalleryHtml(devServer, url, basePath, themeConfig) {
|
|
|
1422
1582
|
* - Art module route
|
|
1423
1583
|
*/
|
|
1424
1584
|
function registerMiddleware(devServer, ctx) {
|
|
1425
|
-
const { basePath, themeConfig, artFiles } = ctx;
|
|
1585
|
+
const { basePath, devSessionToken, themeConfig, artFiles } = ctx;
|
|
1426
1586
|
devServer.middlewares.use(basePath, async (req, res, next) => {
|
|
1427
1587
|
const url = req.url || "/";
|
|
1428
1588
|
if (url === "/" || url === "/index.html" || url.startsWith("/tokens") || url.startsWith("/component/") || url.startsWith("/tests")) {
|
|
@@ -1431,19 +1591,18 @@ function registerMiddleware(devServer, ctx) {
|
|
|
1431
1591
|
try {
|
|
1432
1592
|
await fs.promises.access(indexHtmlPath);
|
|
1433
1593
|
let html = await fs.promises.readFile(indexHtmlPath, "utf-8");
|
|
1434
|
-
|
|
1435
|
-
html = html.replace("</head>", `<script>window.__MUSEA_BASE_PATH__='${basePath}';${themeScript}<\/script></head>`);
|
|
1594
|
+
html = html.replace("</head>", `<script>${generateDevGlobalsScript(basePath, devSessionToken, themeConfig)}<\/script></head>`);
|
|
1436
1595
|
res.setHeader("Content-Type", "text/html");
|
|
1437
1596
|
res.end(html);
|
|
1438
1597
|
return;
|
|
1439
1598
|
} catch {
|
|
1440
|
-
const sourceHtml = await tryLoadSourceGalleryHtml(devServer, url, basePath, themeConfig);
|
|
1599
|
+
const sourceHtml = await tryLoadSourceGalleryHtml(devServer, url, basePath, devSessionToken, themeConfig);
|
|
1441
1600
|
if (sourceHtml) {
|
|
1442
1601
|
res.setHeader("Content-Type", "text/html");
|
|
1443
1602
|
res.end(sourceHtml);
|
|
1444
1603
|
return;
|
|
1445
1604
|
}
|
|
1446
|
-
const html = generateGalleryHtml(basePath, themeConfig);
|
|
1605
|
+
const html = generateGalleryHtml(basePath, devSessionToken, themeConfig);
|
|
1447
1606
|
res.setHeader("Content-Type", "text/html");
|
|
1448
1607
|
res.end(html);
|
|
1449
1608
|
return;
|
|
@@ -1451,8 +1610,8 @@ function registerMiddleware(devServer, ctx) {
|
|
|
1451
1610
|
}
|
|
1452
1611
|
if (url.startsWith("/assets/")) {
|
|
1453
1612
|
const galleryDistDir = resolveGalleryDistDir();
|
|
1454
|
-
const filePath = path.join(galleryDistDir, url);
|
|
1455
1613
|
try {
|
|
1614
|
+
const filePath = resolveUrlPathInside(galleryDistDir, url, "asset path");
|
|
1456
1615
|
if ((await fs.promises.stat(filePath)).isFile()) {
|
|
1457
1616
|
const content = await fs.promises.readFile(filePath);
|
|
1458
1617
|
const ext = path.extname(filePath);
|
|
@@ -1469,7 +1628,13 @@ function registerMiddleware(devServer, ctx) {
|
|
|
1469
1628
|
res.end(content);
|
|
1470
1629
|
return;
|
|
1471
1630
|
}
|
|
1472
|
-
} catch {
|
|
1631
|
+
} catch (error) {
|
|
1632
|
+
if (error instanceof HttpError) {
|
|
1633
|
+
res.statusCode = error.status;
|
|
1634
|
+
res.end(error.message);
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1473
1638
|
}
|
|
1474
1639
|
next();
|
|
1475
1640
|
});
|
|
@@ -1792,6 +1957,18 @@ function scanTokenUsage(artFiles, tokenMap) {
|
|
|
1792
1957
|
*/
|
|
1793
1958
|
const REFERENCE_PATTERN = /^\{(.+)\}$/;
|
|
1794
1959
|
const MAX_RESOLVE_DEPTH = 10;
|
|
1960
|
+
const UNSAFE_TOKEN_PATH_SEGMENTS = new Set([
|
|
1961
|
+
"__proto__",
|
|
1962
|
+
"prototype",
|
|
1963
|
+
"constructor"
|
|
1964
|
+
]);
|
|
1965
|
+
function parseTokenPath(dotPath) {
|
|
1966
|
+
const parts = dotPath.split(".");
|
|
1967
|
+
if (parts.length === 0 || parts.some((part) => part.trim() === "")) throw new Error(`Invalid token path "${dotPath}"`);
|
|
1968
|
+
const unsafeSegment = parts.find((part) => UNSAFE_TOKEN_PATH_SEGMENTS.has(part));
|
|
1969
|
+
if (unsafeSegment) throw new Error(`Token path segment "${unsafeSegment}" is not allowed`);
|
|
1970
|
+
return parts;
|
|
1971
|
+
}
|
|
1795
1972
|
/**
|
|
1796
1973
|
* Flatten nested categories into a flat map keyed by dot-path.
|
|
1797
1974
|
*/
|
|
@@ -1862,7 +2039,7 @@ async function writeRawTokenFile(tokensPath, data) {
|
|
|
1862
2039
|
* Set a token at a dot-separated path in the raw JSON structure.
|
|
1863
2040
|
*/
|
|
1864
2041
|
function setTokenAtPath(data, dotPath, token) {
|
|
1865
|
-
const parts = dotPath
|
|
2042
|
+
const parts = parseTokenPath(dotPath);
|
|
1866
2043
|
let current = data;
|
|
1867
2044
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
1868
2045
|
const key = parts[i];
|
|
@@ -1882,7 +2059,7 @@ function setTokenAtPath(data, dotPath, token) {
|
|
|
1882
2059
|
* Delete a token at a dot-separated path, cleaning empty parents.
|
|
1883
2060
|
*/
|
|
1884
2061
|
function deleteTokenAtPath(data, dotPath) {
|
|
1885
|
-
const parts = dotPath
|
|
2062
|
+
const parts = parseTokenPath(dotPath);
|
|
1886
2063
|
const parents = [];
|
|
1887
2064
|
let current = data;
|
|
1888
2065
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
@@ -1960,27 +2137,34 @@ function findDependentTokens(tokenMap, targetPath) {
|
|
|
1960
2137
|
* Generates HTML, Markdown, and JSON documentation from parsed token categories,
|
|
1961
2138
|
* and provides the main processStyleDictionary orchestrator function.
|
|
1962
2139
|
*/
|
|
2140
|
+
const SAFE_CSS_COLOR_PATTERN = /^(?:#[0-9a-fA-F]{3,8}|(?:rgb|hsl)a?\(\s*[-+.\d,%\s]+\))$/;
|
|
2141
|
+
function safeCssColor(value, type) {
|
|
2142
|
+
if (typeof value !== "string") return null;
|
|
2143
|
+
const trimmed = value.trim();
|
|
2144
|
+
return (type === "color" || trimmed.startsWith("#") || trimmed.startsWith("rgb") || trimmed.startsWith("hsl")) && SAFE_CSS_COLOR_PATTERN.test(trimmed) ? trimmed : null;
|
|
2145
|
+
}
|
|
1963
2146
|
/**
|
|
1964
2147
|
* Generate HTML documentation for tokens.
|
|
1965
2148
|
*/
|
|
1966
2149
|
function generateTokensHtml(categories) {
|
|
1967
2150
|
const renderToken = (name, token) => {
|
|
2151
|
+
const color = safeCssColor(token.value, token.type);
|
|
1968
2152
|
return `
|
|
1969
2153
|
<div class="token">
|
|
1970
2154
|
<div class="token-preview">
|
|
1971
|
-
${
|
|
2155
|
+
${color ? `<div class="color-swatch" style="background: ${color}"></div>` : ""}
|
|
1972
2156
|
</div>
|
|
1973
2157
|
<div class="token-info">
|
|
1974
|
-
<div class="token-name">${name}</div>
|
|
1975
|
-
<div class="token-value">${token.value}</div>
|
|
1976
|
-
${token.description ? `<div class="token-description">${token.description}</div>` : ""}
|
|
2158
|
+
<div class="token-name">${escapeHtml(name)}</div>
|
|
2159
|
+
<div class="token-value">${escapeHtml(String(token.value))}</div>
|
|
2160
|
+
${token.description ? `<div class="token-description">${escapeHtml(token.description)}</div>` : ""}
|
|
1977
2161
|
</div>
|
|
1978
2162
|
</div>
|
|
1979
2163
|
`;
|
|
1980
2164
|
};
|
|
1981
2165
|
const renderCategory = (category, level = 2) => {
|
|
1982
2166
|
const heading = `h${Math.min(level, 6)}`;
|
|
1983
|
-
let html = `<${heading}>${category.name}</${heading}>`;
|
|
2167
|
+
let html = `<${heading}>${escapeHtml(category.name)}</${heading}>`;
|
|
1984
2168
|
html += "<div class=\"tokens-grid\">";
|
|
1985
2169
|
for (const [name, token] of Object.entries(category.tokens)) html += renderToken(name, token);
|
|
1986
2170
|
html += "</div>";
|
|
@@ -2130,16 +2314,20 @@ async function processStyleDictionary(config) {
|
|
|
2130
2314
|
}
|
|
2131
2315
|
//#endregion
|
|
2132
2316
|
//#region src/api-tokens.ts
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2317
|
+
function resolveTokensPath(ctx) {
|
|
2318
|
+
return resolveInside(ctx.config.root, ctx.tokensPath, "tokensPath");
|
|
2319
|
+
}
|
|
2320
|
+
function sendTokenMutationError(e, sendError) {
|
|
2321
|
+
if (e instanceof HttpError) {
|
|
2322
|
+
sendError(e.message, e.status);
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
if (e instanceof Error && /token path/i.test(e.message)) {
|
|
2326
|
+
sendError(e.message, 400);
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2329
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
2330
|
+
}
|
|
2143
2331
|
/** GET /api/tokens/usage */
|
|
2144
2332
|
async function handleTokensUsage(ctx, sendJson) {
|
|
2145
2333
|
if (!ctx.tokensPath) {
|
|
@@ -2147,7 +2335,7 @@ async function handleTokensUsage(ctx, sendJson) {
|
|
|
2147
2335
|
return;
|
|
2148
2336
|
}
|
|
2149
2337
|
try {
|
|
2150
|
-
const categories = await parseTokens(
|
|
2338
|
+
const categories = await parseTokens(resolveTokensPath(ctx));
|
|
2151
2339
|
resolveReferences(categories, buildTokenMap(categories));
|
|
2152
2340
|
const resolvedTokenMap = buildTokenMap(categories);
|
|
2153
2341
|
sendJson(scanTokenUsage(ctx.artFiles, resolvedTokenMap));
|
|
@@ -2172,7 +2360,7 @@ async function handleTokensGet(ctx, sendJson) {
|
|
|
2172
2360
|
return;
|
|
2173
2361
|
}
|
|
2174
2362
|
try {
|
|
2175
|
-
const absoluteTokensPath =
|
|
2363
|
+
const absoluteTokensPath = resolveTokensPath(ctx);
|
|
2176
2364
|
const categories = await parseTokens(absoluteTokensPath);
|
|
2177
2365
|
resolveReferences(categories, buildTokenMap(categories));
|
|
2178
2366
|
const resolvedTokenMap = buildTokenMap(categories);
|
|
@@ -2212,7 +2400,7 @@ async function handleTokensCreate(ctx, readBody, sendJson, sendError) {
|
|
|
2212
2400
|
sendError("Missing required fields: path, token.value", 400);
|
|
2213
2401
|
return;
|
|
2214
2402
|
}
|
|
2215
|
-
const absoluteTokensPath =
|
|
2403
|
+
const absoluteTokensPath = resolveTokensPath(ctx);
|
|
2216
2404
|
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
2217
2405
|
const currentMap = buildTokenMap(await parseTokens(absoluteTokensPath));
|
|
2218
2406
|
if (currentMap[dotPath]) {
|
|
@@ -2237,7 +2425,7 @@ async function handleTokensCreate(ctx, readBody, sendJson, sendError) {
|
|
|
2237
2425
|
tokenMap: buildTokenMap(categories)
|
|
2238
2426
|
}, 201);
|
|
2239
2427
|
} catch (e) {
|
|
2240
|
-
|
|
2428
|
+
sendTokenMutationError(e, sendError);
|
|
2241
2429
|
}
|
|
2242
2430
|
}
|
|
2243
2431
|
/** PUT /api/tokens (update) */
|
|
@@ -2253,7 +2441,7 @@ async function handleTokensUpdate(ctx, readBody, sendJson, sendError) {
|
|
|
2253
2441
|
sendError("Missing required fields: path, token.value", 400);
|
|
2254
2442
|
return;
|
|
2255
2443
|
}
|
|
2256
|
-
const absoluteTokensPath =
|
|
2444
|
+
const absoluteTokensPath = resolveTokensPath(ctx);
|
|
2257
2445
|
if (token.$reference) {
|
|
2258
2446
|
const validation = validateSemanticReference(buildTokenMap(await parseTokens(absoluteTokensPath)), token.$reference, dotPath);
|
|
2259
2447
|
if (!validation.valid) {
|
|
@@ -2273,7 +2461,7 @@ async function handleTokensUpdate(ctx, readBody, sendJson, sendError) {
|
|
|
2273
2461
|
tokenMap: buildTokenMap(categories)
|
|
2274
2462
|
});
|
|
2275
2463
|
} catch (e) {
|
|
2276
|
-
|
|
2464
|
+
sendTokenMutationError(e, sendError);
|
|
2277
2465
|
}
|
|
2278
2466
|
}
|
|
2279
2467
|
/** DELETE /api/tokens */
|
|
@@ -2289,7 +2477,7 @@ async function handleTokensDelete(ctx, readBody, sendJson, sendError) {
|
|
|
2289
2477
|
sendError("Missing required field: path", 400);
|
|
2290
2478
|
return;
|
|
2291
2479
|
}
|
|
2292
|
-
const absoluteTokensPath =
|
|
2480
|
+
const absoluteTokensPath = resolveTokensPath(ctx);
|
|
2293
2481
|
const dependents = findDependentTokens(buildTokenMap(await parseTokens(absoluteTokensPath)), dotPath);
|
|
2294
2482
|
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
2295
2483
|
if (!deleteTokenAtPath(rawData, dotPath)) {
|
|
@@ -2305,7 +2493,7 @@ async function handleTokensDelete(ctx, readBody, sendJson, sendError) {
|
|
|
2305
2493
|
dependentsWarning: dependents.length > 0 ? dependents : void 0
|
|
2306
2494
|
});
|
|
2307
2495
|
} catch (e) {
|
|
2308
|
-
|
|
2496
|
+
sendTokenMutationError(e, sendError);
|
|
2309
2497
|
}
|
|
2310
2498
|
}
|
|
2311
2499
|
//#endregion
|
|
@@ -2512,15 +2700,23 @@ function handlePreviewWithProps(ctx, body, res, sendJson, sendError) {
|
|
|
2512
2700
|
res.setHeader("Content-Type", "application/javascript");
|
|
2513
2701
|
res.end(moduleCode);
|
|
2514
2702
|
} catch (e) {
|
|
2703
|
+
if (e instanceof HttpError) {
|
|
2704
|
+
sendError(e.message, e.status);
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2515
2707
|
sendError(e instanceof Error ? e.message : String(e));
|
|
2516
2708
|
}
|
|
2517
2709
|
}
|
|
2518
2710
|
/** POST /api/generate */
|
|
2519
|
-
async function handleGenerate(body, sendJson, sendError) {
|
|
2711
|
+
async function handleGenerate(ctx, body, sendJson, sendError) {
|
|
2520
2712
|
try {
|
|
2521
2713
|
const { componentPath: reqComponentPath, options: autogenOptions } = JSON.parse(body);
|
|
2714
|
+
if (typeof reqComponentPath !== "string") {
|
|
2715
|
+
sendError("Missing required field: componentPath", 400);
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2522
2718
|
const { generateArtFile: genArt } = await import("./autogen/index.mjs");
|
|
2523
|
-
const result = await genArt(reqComponentPath, autogenOptions);
|
|
2719
|
+
const result = await genArt(resolveInside(ctx.config.root, reqComponentPath, "componentPath"), autogenOptions);
|
|
2524
2720
|
sendJson({
|
|
2525
2721
|
generated: true,
|
|
2526
2722
|
componentName: result.componentName,
|
|
@@ -2528,6 +2724,10 @@ async function handleGenerate(body, sendJson, sendError) {
|
|
|
2528
2724
|
artFileContent: result.artFileContent
|
|
2529
2725
|
});
|
|
2530
2726
|
} catch (e) {
|
|
2727
|
+
if (e instanceof HttpError) {
|
|
2728
|
+
sendError(e.message, e.status);
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2531
2731
|
sendError(e instanceof Error ? e.message : String(e));
|
|
2532
2732
|
}
|
|
2533
2733
|
}
|
|
@@ -2590,16 +2790,6 @@ async function handleRunVrt(ctx, body, sendJson, sendError) {
|
|
|
2590
2790
|
}
|
|
2591
2791
|
//#endregion
|
|
2592
2792
|
//#region src/api-routes/index.ts
|
|
2593
|
-
/** Helper to read the full request body as a string. */
|
|
2594
|
-
function collectBody(req) {
|
|
2595
|
-
return new Promise((resolve) => {
|
|
2596
|
-
let body = "";
|
|
2597
|
-
req.on("data", (chunk) => {
|
|
2598
|
-
body += chunk;
|
|
2599
|
-
});
|
|
2600
|
-
req.on("end", () => resolve(body));
|
|
2601
|
-
});
|
|
2602
|
-
}
|
|
2603
2793
|
/**
|
|
2604
2794
|
* Create the API middleware handler for the Musea gallery.
|
|
2605
2795
|
*
|
|
@@ -2616,107 +2806,117 @@ function createApiMiddleware(ctx) {
|
|
|
2616
2806
|
const sendError = (message, status = 500) => {
|
|
2617
2807
|
sendJson({ error: message }, status);
|
|
2618
2808
|
};
|
|
2619
|
-
const readBody = () =>
|
|
2809
|
+
const readBody = () => collectRequestBody(req, ctx.apiBodyLimit ?? 1048576);
|
|
2620
2810
|
const url = req.url || "/";
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
if (url === "/tokens/usage" && req.method === "GET") {
|
|
2626
|
-
await handleTokensUsage(ctx, sendJson);
|
|
2627
|
-
return;
|
|
2628
|
-
}
|
|
2629
|
-
if (url === "/tokens" && req.method === "GET") {
|
|
2630
|
-
await handleTokensGet(ctx, sendJson);
|
|
2631
|
-
return;
|
|
2632
|
-
}
|
|
2633
|
-
if (url === "/tokens" && req.method === "POST") {
|
|
2634
|
-
await handleTokensCreate(ctx, readBody, sendJson, sendError);
|
|
2635
|
-
return;
|
|
2636
|
-
}
|
|
2637
|
-
if (url === "/tokens" && req.method === "PUT") {
|
|
2638
|
-
await handleTokensUpdate(ctx, readBody, sendJson, sendError);
|
|
2639
|
-
return;
|
|
2640
|
-
}
|
|
2641
|
-
if (url === "/tokens" && req.method === "DELETE") {
|
|
2642
|
-
await handleTokensDelete(ctx, readBody, sendJson, sendError);
|
|
2643
|
-
return;
|
|
2644
|
-
}
|
|
2645
|
-
if (url?.startsWith("/arts/") && req.method === "PUT") {
|
|
2646
|
-
const sourceMatch = url.slice(6).match(/^(.+)\/source$/);
|
|
2647
|
-
if (sourceMatch) {
|
|
2648
|
-
const artPath = decodeURIComponent(sourceMatch[1]);
|
|
2649
|
-
if (!ctx.artFiles.get(artPath)) {
|
|
2650
|
-
sendError("Art not found", 404);
|
|
2651
|
-
return;
|
|
2652
|
-
}
|
|
2653
|
-
const body = await collectBody(req);
|
|
2654
|
-
try {
|
|
2655
|
-
const { source } = JSON.parse(body);
|
|
2656
|
-
if (typeof source !== "string") {
|
|
2657
|
-
sendError("Missing required field: source", 400);
|
|
2658
|
-
return;
|
|
2659
|
-
}
|
|
2660
|
-
await fs.promises.writeFile(artPath, source, "utf-8");
|
|
2661
|
-
await ctx.processArtFile(artPath);
|
|
2662
|
-
sendJson({ success: true });
|
|
2663
|
-
} catch (e) {
|
|
2664
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2665
|
-
}
|
|
2811
|
+
try {
|
|
2812
|
+
const requestError = validateDevApiRequest(req, ctx.devSessionToken);
|
|
2813
|
+
if (requestError) {
|
|
2814
|
+
sendError(requestError.message, requestError.status);
|
|
2666
2815
|
return;
|
|
2667
2816
|
}
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
}
|
|
2671
|
-
if (url?.startsWith("/arts/") && req.method === "GET") {
|
|
2672
|
-
const rest = url.slice(6);
|
|
2673
|
-
const sourceMatch = rest.match(/^(.+)\/source$/);
|
|
2674
|
-
const paletteMatch = rest.match(/^(.+)\/palette$/);
|
|
2675
|
-
const analysisMatch = rest.match(/^(.+)\/analysis$/);
|
|
2676
|
-
const docsMatch = rest.match(/^(.+)\/docs$/);
|
|
2677
|
-
const a11yMatch = rest.match(/^(.+)\/variants\/([^/]+)\/a11y$/);
|
|
2678
|
-
if (sourceMatch) {
|
|
2679
|
-
await handleArtSource(ctx, sourceMatch, sendJson, sendError);
|
|
2817
|
+
if (url === "/arts" && req.method === "GET") {
|
|
2818
|
+
sendJson(Array.from(ctx.artFiles.values()));
|
|
2680
2819
|
return;
|
|
2681
2820
|
}
|
|
2682
|
-
if (
|
|
2683
|
-
await
|
|
2821
|
+
if (url === "/tokens/usage" && req.method === "GET") {
|
|
2822
|
+
await handleTokensUsage(ctx, sendJson);
|
|
2684
2823
|
return;
|
|
2685
2824
|
}
|
|
2686
|
-
if (
|
|
2687
|
-
await
|
|
2825
|
+
if (url === "/tokens" && req.method === "GET") {
|
|
2826
|
+
await handleTokensGet(ctx, sendJson);
|
|
2688
2827
|
return;
|
|
2689
2828
|
}
|
|
2690
|
-
if (
|
|
2691
|
-
await
|
|
2829
|
+
if (url === "/tokens" && req.method === "POST") {
|
|
2830
|
+
await handleTokensCreate(ctx, readBody, sendJson, sendError);
|
|
2692
2831
|
return;
|
|
2693
2832
|
}
|
|
2694
|
-
if (
|
|
2695
|
-
|
|
2833
|
+
if (url === "/tokens" && req.method === "PUT") {
|
|
2834
|
+
await handleTokensUpdate(ctx, readBody, sendJson, sendError);
|
|
2696
2835
|
return;
|
|
2697
2836
|
}
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
if (art) sendJson(art);
|
|
2701
|
-
else sendError("Art not found", 404);
|
|
2702
|
-
return;
|
|
2703
|
-
}
|
|
2704
|
-
if (req.method === "POST") {
|
|
2705
|
-
const body = await collectBody(req);
|
|
2706
|
-
if (url === "/preview-with-props") {
|
|
2707
|
-
handlePreviewWithProps(ctx, body, res, sendJson, sendError);
|
|
2837
|
+
if (url === "/tokens" && req.method === "DELETE") {
|
|
2838
|
+
await handleTokensDelete(ctx, readBody, sendJson, sendError);
|
|
2708
2839
|
return;
|
|
2709
2840
|
}
|
|
2710
|
-
if (url === "
|
|
2711
|
-
|
|
2841
|
+
if (url?.startsWith("/arts/") && req.method === "PUT") {
|
|
2842
|
+
const sourceMatch = url.slice(6).match(/^(.+)\/source$/);
|
|
2843
|
+
if (sourceMatch) {
|
|
2844
|
+
const artPath = decodeURIComponent(sourceMatch[1]);
|
|
2845
|
+
if (!ctx.artFiles.get(artPath)) {
|
|
2846
|
+
sendError("Art not found", 404);
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
const safeArtPath = resolveInside(ctx.config.root, artPath, "art path");
|
|
2850
|
+
const body = await readBody();
|
|
2851
|
+
const { source } = JSON.parse(body);
|
|
2852
|
+
if (typeof source !== "string") {
|
|
2853
|
+
sendError("Missing required field: source", 400);
|
|
2854
|
+
return;
|
|
2855
|
+
}
|
|
2856
|
+
await fs.promises.writeFile(safeArtPath, source, "utf-8");
|
|
2857
|
+
await ctx.processArtFile(safeArtPath);
|
|
2858
|
+
sendJson({ success: true });
|
|
2859
|
+
return;
|
|
2860
|
+
}
|
|
2861
|
+
next();
|
|
2712
2862
|
return;
|
|
2713
2863
|
}
|
|
2714
|
-
if (url === "
|
|
2715
|
-
|
|
2864
|
+
if (url?.startsWith("/arts/") && req.method === "GET") {
|
|
2865
|
+
const rest = url.slice(6);
|
|
2866
|
+
const sourceMatch = rest.match(/^(.+)\/source$/);
|
|
2867
|
+
const paletteMatch = rest.match(/^(.+)\/palette$/);
|
|
2868
|
+
const analysisMatch = rest.match(/^(.+)\/analysis$/);
|
|
2869
|
+
const docsMatch = rest.match(/^(.+)\/docs$/);
|
|
2870
|
+
const a11yMatch = rest.match(/^(.+)\/variants\/([^/]+)\/a11y$/);
|
|
2871
|
+
if (sourceMatch) {
|
|
2872
|
+
await handleArtSource(ctx, sourceMatch, sendJson, sendError);
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
if (paletteMatch) {
|
|
2876
|
+
await handleArtPalette(ctx, paletteMatch, sendJson, sendError);
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
if (analysisMatch) {
|
|
2880
|
+
await handleArtAnalysis(ctx, analysisMatch, sendJson, sendError);
|
|
2881
|
+
return;
|
|
2882
|
+
}
|
|
2883
|
+
if (docsMatch) {
|
|
2884
|
+
await handleArtDocs(ctx, docsMatch, sendJson, sendError);
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
if (a11yMatch) {
|
|
2888
|
+
handleArtA11y(ctx, a11yMatch, sendJson, sendError);
|
|
2889
|
+
return;
|
|
2890
|
+
}
|
|
2891
|
+
const artPath = decodeURIComponent(rest);
|
|
2892
|
+
const art = ctx.artFiles.get(artPath);
|
|
2893
|
+
if (art) sendJson(art);
|
|
2894
|
+
else sendError("Art not found", 404);
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
if (req.method === "POST") {
|
|
2898
|
+
const body = await readBody();
|
|
2899
|
+
if (url === "/preview-with-props") {
|
|
2900
|
+
handlePreviewWithProps(ctx, body, res, sendJson, sendError);
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
if (url === "/generate") {
|
|
2904
|
+
await handleGenerate(ctx, body, sendJson, sendError);
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2907
|
+
if (url === "/run-vrt") {
|
|
2908
|
+
await handleRunVrt(ctx, body, sendJson, sendError);
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
next();
|
|
2913
|
+
} catch (e) {
|
|
2914
|
+
if (e instanceof HttpError) {
|
|
2915
|
+
sendError(e.message, e.status);
|
|
2716
2916
|
return;
|
|
2717
2917
|
}
|
|
2918
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
2718
2919
|
}
|
|
2719
|
-
next();
|
|
2720
2920
|
};
|
|
2721
2921
|
}
|
|
2722
2922
|
//#endregion
|
|
@@ -2855,6 +3055,7 @@ function musea(options = {}) {
|
|
|
2855
3055
|
const themeConfig = buildThemeConfig(options.theme);
|
|
2856
3056
|
const previewCss = options.previewCss ?? [];
|
|
2857
3057
|
const previewSetup = options.previewSetup;
|
|
3058
|
+
const devSessionToken = createDevSessionToken();
|
|
2858
3059
|
let config;
|
|
2859
3060
|
let server = null;
|
|
2860
3061
|
const artFiles = /* @__PURE__ */ new Map();
|
|
@@ -2902,6 +3103,7 @@ function musea(options = {}) {
|
|
|
2902
3103
|
devServer.watcher.add(scanRoots);
|
|
2903
3104
|
registerMiddleware(devServer, {
|
|
2904
3105
|
basePath,
|
|
3106
|
+
devSessionToken,
|
|
2905
3107
|
themeConfig,
|
|
2906
3108
|
artFiles,
|
|
2907
3109
|
resolvedPreviewCss,
|
|
@@ -2914,6 +3116,7 @@ function musea(options = {}) {
|
|
|
2914
3116
|
basePath,
|
|
2915
3117
|
resolvedPreviewCss,
|
|
2916
3118
|
resolvedPreviewSetup,
|
|
3119
|
+
devSessionToken,
|
|
2917
3120
|
processArtFile,
|
|
2918
3121
|
getDevServerPort: () => devServer.config.server.port || 5173
|
|
2919
3122
|
}));
|