@vizejs/vite-plugin-musea 0.81.0 → 0.83.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-VccI_xm1.js} +2 -2
- package/dist/gallery/assets/{cssMode-DTjSpNAL.js → cssMode-DYl_gbi2.js} +1 -1
- package/dist/gallery/assets/{editor.api2-CIEJpqKT.js → editor.api2-C1iuJsz5.js} +1 -1
- package/dist/gallery/assets/{editor.main-DU-sPy-k.js → editor.main-CB_LUZEd.js} +2 -2
- package/dist/gallery/assets/{freemarker2-DZuwWwqM.js → freemarker2-CwuG5RrE.js} +1 -1
- package/dist/gallery/assets/{handlebars-C6CWcO31.js → handlebars-DQnqIqtZ.js} +1 -1
- package/dist/gallery/assets/{html-DTPfnMqY.js → html-Cj3GxbMF.js} +1 -1
- package/dist/gallery/assets/{htmlMode-DmLB9Bik.js → htmlMode-ckJrF6jc.js} +1 -1
- package/dist/gallery/assets/{index-DjSpyxD0.js → index-6QLn8Kxp.js} +22 -22
- package/dist/gallery/assets/{index-CwT3Ex21.css → index-P1L8IaBA.css} +1 -1
- package/dist/gallery/assets/{javascript-dFy7cqCx.js → javascript-DNOPgN7f.js} +1 -1
- package/dist/gallery/assets/{jsonMode-Bge74JBI.js → jsonMode-BMPHk4yv.js} +1 -1
- package/dist/gallery/assets/{liquid-H5lVD6p3.js → liquid-cUC3T7D4.js} +1 -1
- package/dist/gallery/assets/{lspLanguageFeatures-CkkzJ5B0.js → lspLanguageFeatures-DnNJLDpx.js} +1 -1
- package/dist/gallery/assets/{mdx-DuMAerqf.js → mdx-DDDAX75G.js} +1 -1
- package/dist/gallery/assets/{monaco.contribution-Cn9RKjKZ.js → monaco.contribution-BsZfIZIS.js} +2 -2
- package/dist/gallery/assets/{python-BqM-0Ttj.js → python-CkEL94qN.js} +1 -1
- package/dist/gallery/assets/{razor-BDNVe10U.js → razor-DpH3myLF.js} +1 -1
- package/dist/gallery/assets/{tsMode-uoOez2iL.js → tsMode-D4vJtygH.js} +1 -1
- package/dist/gallery/assets/{typescript-DI6pcvqw.js → typescript-f3jEZleA.js} +1 -1
- package/dist/gallery/assets/{workers-DS42og38.js → workers-7fp33vpu.js} +1 -1
- package/dist/gallery/assets/{xml-ChP0eqUe.js → xml-CrndFNV8.js} +1 -1
- package/dist/gallery/assets/{yaml-DCdtNHC4.js → yaml-BAl6eAY9.js} +1 -1
- package/dist/gallery/index.html +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +450 -194
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { transformWithEsbuild } from "vite";
|
|
|
6
6
|
import fs from "node:fs";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { vizeConfigStore } from "@vizejs/vite-plugin";
|
|
9
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
10
11
|
//#region src/native-loader.ts
|
|
11
12
|
/**
|
|
@@ -90,6 +91,156 @@ function analyzeSfcFallback(source, _options) {
|
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
//#endregion
|
|
94
|
+
//#region src/security.ts
|
|
95
|
+
const DEFAULT_API_BODY_LIMIT_BYTES = 1024 * 1024;
|
|
96
|
+
var HttpError = class extends Error {
|
|
97
|
+
status;
|
|
98
|
+
constructor(message, status) {
|
|
99
|
+
super(message);
|
|
100
|
+
this.name = "HttpError";
|
|
101
|
+
this.status = status;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
function createDevSessionToken() {
|
|
105
|
+
return randomBytes(32).toString("base64url");
|
|
106
|
+
}
|
|
107
|
+
function realpathNearest(targetPath) {
|
|
108
|
+
let current = path.resolve(targetPath);
|
|
109
|
+
const missingParts = [];
|
|
110
|
+
while (true) try {
|
|
111
|
+
const real = fs.realpathSync.native(current);
|
|
112
|
+
return missingParts.length > 0 ? path.join(real, ...missingParts.reverse()) : real;
|
|
113
|
+
} catch {
|
|
114
|
+
const parent = path.dirname(current);
|
|
115
|
+
if (parent === current) return path.resolve(targetPath);
|
|
116
|
+
missingParts.push(path.basename(current));
|
|
117
|
+
current = parent;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function isResolvedPathInside(parentDir, candidatePath) {
|
|
121
|
+
const parent = path.resolve(parentDir);
|
|
122
|
+
const candidate = path.resolve(candidatePath);
|
|
123
|
+
const relative = path.relative(parent, candidate);
|
|
124
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
125
|
+
}
|
|
126
|
+
function isPathInsideAny(parentDirs, candidatePath) {
|
|
127
|
+
const candidate = realpathNearest(candidatePath);
|
|
128
|
+
return parentDirs.some((parentDir) => isResolvedPathInside(realpathNearest(parentDir), candidate));
|
|
129
|
+
}
|
|
130
|
+
function resolveInside(parentDir, candidatePath, label = "path") {
|
|
131
|
+
return resolveInsideAny([parentDir], candidatePath, label);
|
|
132
|
+
}
|
|
133
|
+
function resolveInsideAny(parentDirs, candidatePath, label = "path") {
|
|
134
|
+
if (candidatePath.includes("\0")) throw new HttpError(`${label} contains an invalid character`, 400);
|
|
135
|
+
if (parentDirs.length === 0) throw new HttpError(`No allowed directories configured for ${label}`, 500);
|
|
136
|
+
const parent = path.resolve(parentDirs[0] ?? ".");
|
|
137
|
+
const resolved = path.isAbsolute(candidatePath) ? path.resolve(candidatePath) : path.resolve(parent, candidatePath);
|
|
138
|
+
if (!isPathInsideAny(parentDirs, resolved)) throw new HttpError(`${label} escapes the allowed directory`, 400);
|
|
139
|
+
return resolved;
|
|
140
|
+
}
|
|
141
|
+
function resolveUrlPathInside(parentDir, requestUrl, label = "path") {
|
|
142
|
+
const rawPath = requestUrl.split(/[?#]/, 1)[0] || "/";
|
|
143
|
+
let pathname;
|
|
144
|
+
try {
|
|
145
|
+
pathname = decodeURIComponent(rawPath);
|
|
146
|
+
} catch {
|
|
147
|
+
throw new HttpError(`${label} is not valid URL encoding`, 400);
|
|
148
|
+
}
|
|
149
|
+
pathname = pathname.replaceAll("\\", "/");
|
|
150
|
+
if (pathname.split("/").includes("..")) throw new HttpError(`${label} must not contain parent directory segments`, 400);
|
|
151
|
+
return resolveInside(parentDir, `.${pathname}`, label);
|
|
152
|
+
}
|
|
153
|
+
function collectRequestBody(req, limit = DEFAULT_API_BODY_LIMIT_BYTES) {
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
let body = "";
|
|
156
|
+
let size = 0;
|
|
157
|
+
let completed = false;
|
|
158
|
+
req.on("data", (chunk) => {
|
|
159
|
+
if (completed) return;
|
|
160
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
161
|
+
size += buffer.byteLength;
|
|
162
|
+
if (size > limit) {
|
|
163
|
+
completed = true;
|
|
164
|
+
reject(new HttpError(`Request body exceeds ${limit} bytes`, 413));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
body += buffer.toString("utf-8");
|
|
168
|
+
});
|
|
169
|
+
req.on("end", () => {
|
|
170
|
+
if (!completed) {
|
|
171
|
+
completed = true;
|
|
172
|
+
resolve(body);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
req.on("error", (error) => {
|
|
176
|
+
if (!completed) {
|
|
177
|
+
completed = true;
|
|
178
|
+
reject(error);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function validateDevApiRequest(req, sessionToken) {
|
|
184
|
+
const originError = validateOrigin(req);
|
|
185
|
+
if (originError) return originError;
|
|
186
|
+
if (!isUnsafeMethod(req.method)) return null;
|
|
187
|
+
if (!hasValidSessionToken(req, sessionToken)) return new HttpError("Invalid Musea dev session token", 403);
|
|
188
|
+
if (!isJsonRequest(req)) return new HttpError("Content-Type must be application/json", 415);
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
function serializeScriptValue(value) {
|
|
192
|
+
return (JSON.stringify(value) ?? "undefined").replace(/[<>&\u2028\u2029]/g, (char) => {
|
|
193
|
+
switch (char) {
|
|
194
|
+
case "<": return "\\u003C";
|
|
195
|
+
case ">": return "\\u003E";
|
|
196
|
+
case "&": return "\\u0026";
|
|
197
|
+
case "\u2028": return "\\u2028";
|
|
198
|
+
case "\u2029": return "\\u2029";
|
|
199
|
+
default: return char;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function isUnsafeMethod(method) {
|
|
204
|
+
return method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE";
|
|
205
|
+
}
|
|
206
|
+
function isJsonRequest(req) {
|
|
207
|
+
return getHeader(req, "content-type")?.split(";")[0]?.trim().toLowerCase() === "application/json";
|
|
208
|
+
}
|
|
209
|
+
function validateOrigin(req) {
|
|
210
|
+
if (getHeader(req, "sec-fetch-site") === "cross-site") return new HttpError("Cross-origin Musea API requests are not allowed", 403);
|
|
211
|
+
const origin = getHeader(req, "origin");
|
|
212
|
+
if (!origin) return null;
|
|
213
|
+
const host = getHeader(req, "host");
|
|
214
|
+
if (!host) return new HttpError("Missing Host header", 400);
|
|
215
|
+
try {
|
|
216
|
+
if (new URL(origin).host !== host) return new HttpError("Cross-origin Musea API requests are not allowed", 403);
|
|
217
|
+
} catch {
|
|
218
|
+
return new HttpError("Invalid Origin header", 400);
|
|
219
|
+
}
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
function hasValidSessionToken(req, expectedToken) {
|
|
223
|
+
const actualToken = getHeader(req, "x-musea-session");
|
|
224
|
+
if (!actualToken) return false;
|
|
225
|
+
const actual = Buffer.from(actualToken);
|
|
226
|
+
const expected = Buffer.from(expectedToken);
|
|
227
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
228
|
+
}
|
|
229
|
+
function getHeader(req, name) {
|
|
230
|
+
const value = req.headers[name];
|
|
231
|
+
if (Array.isArray(value)) return value[0];
|
|
232
|
+
return value;
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
//#region src/component-source.ts
|
|
236
|
+
function allowedSourceRoots(root, scanRoots = []) {
|
|
237
|
+
return [...new Set([root, ...scanRoots].map((sourceRoot) => path.resolve(sourceRoot)))];
|
|
238
|
+
}
|
|
239
|
+
function resolveComponentSourcePath(art, artPath, sourceRoots) {
|
|
240
|
+
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;
|
|
241
|
+
return componentPath ? resolveInsideAny(sourceRoots, componentPath, "component path") : null;
|
|
242
|
+
}
|
|
243
|
+
//#endregion
|
|
93
244
|
//#region src/utils.ts
|
|
94
245
|
/**
|
|
95
246
|
* Shared utility functions for the Musea Vite plugin.
|
|
@@ -182,9 +333,6 @@ function toPascalCase(str) {
|
|
|
182
333
|
if (!pascal) return "Variant";
|
|
183
334
|
return /^[\p{L}_$]/u.test(pascal) ? pascal : `Variant${pascal}`;
|
|
184
335
|
}
|
|
185
|
-
function escapeTemplate(str) {
|
|
186
|
-
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
|
|
187
|
-
}
|
|
188
336
|
function escapeHtml(str) {
|
|
189
337
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
190
338
|
}
|
|
@@ -226,6 +374,9 @@ function resolveRelativeSpecifier(specifier, artDir) {
|
|
|
226
374
|
function rewriteRelativeImportStatement(statement, artDir) {
|
|
227
375
|
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}`);
|
|
228
376
|
}
|
|
377
|
+
function escapeTemplateLiteral(str) {
|
|
378
|
+
return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
379
|
+
}
|
|
229
380
|
function countCharBalance(source, openChar, closeChar) {
|
|
230
381
|
let balance = 0;
|
|
231
382
|
for (const char of source) if (char === openChar) balance++;
|
|
@@ -394,16 +545,16 @@ function parseScriptSetupForArt(content) {
|
|
|
394
545
|
returnNames: [...returnNames]
|
|
395
546
|
};
|
|
396
547
|
}
|
|
397
|
-
function generateArtModule(art, filePath) {
|
|
548
|
+
function generateArtModule(art, filePath, options = {}) {
|
|
398
549
|
let componentImportPath;
|
|
399
|
-
let
|
|
550
|
+
let componentTagName;
|
|
551
|
+
const componentBindingName = "__MuseaComponent";
|
|
400
552
|
if (art.isInline && art.componentPath) {
|
|
401
|
-
componentImportPath = art.componentPath;
|
|
402
|
-
|
|
553
|
+
componentImportPath = options.root ? resolveComponentSourcePath(art, filePath, allowedSourceRoots(options.root, options.scanRoots ?? [])) ?? void 0 : art.componentPath;
|
|
554
|
+
componentTagName = "MuseaComponent";
|
|
403
555
|
} else if (art.metadata.component) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
componentName = path.basename(comp, ".vue");
|
|
556
|
+
componentImportPath = options.root ? resolveComponentSourcePath(art, filePath, allowedSourceRoots(options.root, options.scanRoots ?? [])) ?? void 0 : path.isAbsolute(art.metadata.component) ? art.metadata.component : path.resolve(path.dirname(filePath), art.metadata.component);
|
|
557
|
+
componentTagName = "MuseaComponent";
|
|
407
558
|
}
|
|
408
559
|
const scriptSetup = art.scriptSetupContent ? parseScriptSetupForArt(art.scriptSetupContent) : null;
|
|
409
560
|
let code = `
|
|
@@ -417,12 +568,9 @@ import { defineComponent, h } from 'vue';
|
|
|
417
568
|
code += `${resolved}\n`;
|
|
418
569
|
}
|
|
419
570
|
}
|
|
420
|
-
if (componentImportPath &&
|
|
421
|
-
if (!scriptSetup?.imports.some((imp) => {
|
|
422
|
-
|
|
423
|
-
return new RegExp(`^import\\s+${componentName}[\\s,]`).test(imp.trim());
|
|
424
|
-
})) code += `import ${componentName} from '${componentImportPath}';\n`;
|
|
425
|
-
code += `export const __component__ = ${componentName};\n`;
|
|
571
|
+
if (componentImportPath && componentTagName) {
|
|
572
|
+
if (!scriptSetup?.imports.some((imp) => new RegExp(`^import\\s+${componentBindingName}[\\s,]`).test(imp.trim()))) code += `import ${componentBindingName} from ${JSON.stringify(componentImportPath)};\n`;
|
|
573
|
+
code += `export const __component__ = ${componentBindingName};\n`;
|
|
426
574
|
}
|
|
427
575
|
code += `
|
|
428
576
|
export const metadata = ${JSON.stringify(art.metadata)};
|
|
@@ -432,15 +580,15 @@ export const __styles__ = ${JSON.stringify(art.styleBlocks ?? [])};
|
|
|
432
580
|
for (const variant of art.variants) {
|
|
433
581
|
const variantComponentName = toPascalCase(variant.name);
|
|
434
582
|
let template = variant.template;
|
|
435
|
-
if (
|
|
436
|
-
const escapedTemplate = template
|
|
437
|
-
const fullTemplate = `<div data-variant="${variant.name}">${escapedTemplate}</div>`;
|
|
438
|
-
const componentNames = /* @__PURE__ */ new
|
|
439
|
-
if (
|
|
583
|
+
if (componentTagName) template = template.replace(/<Self/g, `<${componentTagName}`).replace(/<\/Self>/g, `</${componentTagName}>`);
|
|
584
|
+
const escapedTemplate = escapeTemplateLiteral(template);
|
|
585
|
+
const fullTemplate = `<div data-variant="${escapeTemplateLiteral(escapeHtml(variant.name))}">${escapedTemplate}</div>`;
|
|
586
|
+
const componentNames = /* @__PURE__ */ new Map();
|
|
587
|
+
if (componentTagName) componentNames.set(componentTagName, componentBindingName);
|
|
440
588
|
if (scriptSetup) {
|
|
441
|
-
for (const name of scriptSetup.returnNames) if (/^[A-Z]/.test(name)) componentNames.
|
|
589
|
+
for (const name of scriptSetup.returnNames) if (/^[A-Z]/.test(name)) componentNames.set(name, name);
|
|
442
590
|
}
|
|
443
|
-
const components = componentNames.size > 0 ? ` components: { ${[...componentNames].join(", ")} },\n` : "";
|
|
591
|
+
const components = componentNames.size > 0 ? ` components: { ${[...componentNames].map(([name, value]) => `${JSON.stringify(name)}: ${value}`).join(", ")} },\n` : "";
|
|
444
592
|
const hasSetupBody = scriptSetup?.setupBody.some((line) => line.trim().length > 0) ?? false;
|
|
445
593
|
if (scriptSetup && (hasSetupBody || scriptSetup.returnNames.length > 0)) code += `
|
|
446
594
|
export const ${variantComponentName} = defineComponent({
|
|
@@ -452,7 +600,7 @@ ${scriptSetup.setupBody.map((l) => ` ${l}`).join("\n")}
|
|
|
452
600
|
template: \`${fullTemplate}\`,
|
|
453
601
|
});
|
|
454
602
|
`;
|
|
455
|
-
else if (
|
|
603
|
+
else if (componentTagName) code += `
|
|
456
604
|
export const ${variantComponentName} = {
|
|
457
605
|
name: '${variantComponentName}',
|
|
458
606
|
${components} template: \`${fullTemplate}\`,
|
|
@@ -564,7 +712,7 @@ function generateGalleryBody(basePath) {
|
|
|
564
712
|
*/
|
|
565
713
|
function generateGalleryScript(basePath) {
|
|
566
714
|
return `
|
|
567
|
-
const basePath =
|
|
715
|
+
const basePath = ${serializeScriptValue(basePath)};
|
|
568
716
|
let arts = [];
|
|
569
717
|
let selectedArt = null;
|
|
570
718
|
let searchQuery = '';
|
|
@@ -601,17 +749,18 @@ function generateGalleryScript(basePath) {
|
|
|
601
749
|
|
|
602
750
|
let html = '';
|
|
603
751
|
for (const [category, items] of Object.entries(categories)) {
|
|
752
|
+
const escapedCategory = escapeHtml(category);
|
|
604
753
|
html += '<div class="sidebar-section">';
|
|
605
|
-
html += '<div class="category-header" data-category="' +
|
|
754
|
+
html += '<div class="category-header" data-category="' + escapedCategory + '">';
|
|
606
755
|
html += '<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg>';
|
|
607
|
-
html += '<span>' +
|
|
756
|
+
html += '<span>' + escapedCategory + '</span>';
|
|
608
757
|
html += '<span class="category-count">' + items.length + '</span>';
|
|
609
758
|
html += '</div>';
|
|
610
|
-
html += '<ul class="art-list" data-category="' +
|
|
759
|
+
html += '<ul class="art-list" data-category="' + escapedCategory + '">';
|
|
611
760
|
for (const art of items) {
|
|
612
761
|
const active = selectedArt?.path === art.path ? 'active' : '';
|
|
613
762
|
const variantCount = art.variants?.length || 0;
|
|
614
|
-
html += '<li class="art-item ' + active + '" data-path="' + art.path + '">';
|
|
763
|
+
html += '<li class="art-item ' + active + '" data-path="' + escapeHtml(art.path) + '">';
|
|
615
764
|
html += '<span>' + escapeHtml(art.metadata.title) + '</span>';
|
|
616
765
|
html += '<span class="art-variant-count">' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';
|
|
617
766
|
html += '</li>';
|
|
@@ -634,7 +783,7 @@ function generateGalleryScript(basePath) {
|
|
|
634
783
|
sidebar.querySelectorAll('.category-header').forEach(header => {
|
|
635
784
|
header.addEventListener('click', () => {
|
|
636
785
|
header.classList.toggle('collapsed');
|
|
637
|
-
const list =
|
|
786
|
+
const list = header.parentElement?.querySelector('.art-list');
|
|
638
787
|
if (list) list.style.display = header.classList.contains('collapsed') ? 'none' : 'block';
|
|
639
788
|
});
|
|
640
789
|
});
|
|
@@ -737,14 +886,15 @@ function generateGalleryScript(basePath) {
|
|
|
737
886
|
/**
|
|
738
887
|
* Generate the inline gallery HTML page.
|
|
739
888
|
*/
|
|
740
|
-
function generateGalleryHtml(basePath, themeConfig) {
|
|
889
|
+
function generateGalleryHtml(basePath, devSessionToken, themeConfig) {
|
|
890
|
+
const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${serializeScriptValue(themeConfig)};` : "";
|
|
741
891
|
return `<!DOCTYPE html>
|
|
742
892
|
<html lang="en">
|
|
743
893
|
<head>
|
|
744
894
|
<meta charset="UTF-8">
|
|
745
895
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
746
896
|
<title>Musea - Component Gallery</title>
|
|
747
|
-
<script>window.__MUSEA_BASE_PATH__
|
|
897
|
+
<script>window.__MUSEA_BASE_PATH__=${serializeScriptValue(basePath)};window.__MUSEA_SESSION_TOKEN__=${serializeScriptValue(devSessionToken)};${themeScript}<\/script>
|
|
748
898
|
<style>${generateGalleryStyles()}
|
|
749
899
|
</style>
|
|
750
900
|
</head>
|
|
@@ -760,7 +910,7 @@ function generateGalleryHtml(basePath, themeConfig) {
|
|
|
760
910
|
*/
|
|
761
911
|
function generateGalleryModule(basePath) {
|
|
762
912
|
return `
|
|
763
|
-
export const basePath =
|
|
913
|
+
export const basePath = ${serializeScriptValue(basePath)};
|
|
764
914
|
export async function loadArts() {
|
|
765
915
|
const res = await fetch(basePath + '/api/arts');
|
|
766
916
|
return res.json();
|
|
@@ -1189,16 +1339,19 @@ function generatePreviewHtml(art, variant, _basePath, viteBase) {
|
|
|
1189
1339
|
//#region src/preview/index.ts
|
|
1190
1340
|
function generatePreviewModule(art, variantComponentName, variantName, cssImports = [], previewSetup = null) {
|
|
1191
1341
|
const artModuleId = `virtual:musea-art:${art.path}`;
|
|
1192
|
-
const
|
|
1193
|
-
const
|
|
1194
|
-
const
|
|
1342
|
+
const artModuleIdLiteral = JSON.stringify(artModuleId);
|
|
1343
|
+
const variantNameLiteral = JSON.stringify(variantName);
|
|
1344
|
+
const variantComponentNameLiteral = JSON.stringify(variantComponentName);
|
|
1345
|
+
const cssImportStatements = cssImports.map((cssPath) => `import ${JSON.stringify(cssPath)};`).join("\n");
|
|
1346
|
+
const setupImport = previewSetup ? `import __museaPreviewSetup from ${JSON.stringify(previewSetup)};` : "";
|
|
1195
1347
|
const setupCall = previewSetup ? "await __museaPreviewSetup(app);" : "";
|
|
1196
1348
|
const actionEvents = JSON.stringify(art.metadata.actionEvents ?? []);
|
|
1349
|
+
const artStyleId = `musea-art-styles-${art.path.replace(/[^\w-]+/g, "_")}`;
|
|
1197
1350
|
return `
|
|
1198
1351
|
${cssImportStatements}
|
|
1199
1352
|
${setupImport}
|
|
1200
1353
|
import { createApp, reactive, h } from 'vue';
|
|
1201
|
-
import * as artModule from
|
|
1354
|
+
import * as artModule from ${artModuleIdLiteral};
|
|
1202
1355
|
|
|
1203
1356
|
const container = document.getElementById('app');
|
|
1204
1357
|
|
|
@@ -1209,7 +1362,7 @@ const propsOverride = reactive({});
|
|
|
1209
1362
|
const slotsOverride = reactive({ default: '' });
|
|
1210
1363
|
|
|
1211
1364
|
function ensureArtStyles(styles) {
|
|
1212
|
-
const styleId =
|
|
1365
|
+
const styleId = ${JSON.stringify(artStyleId)};
|
|
1213
1366
|
const existing = document.getElementById(styleId);
|
|
1214
1367
|
|
|
1215
1368
|
if (!Array.isArray(styles) || styles.length === 0) {
|
|
@@ -1226,6 +1379,30 @@ function ensureArtStyles(styles) {
|
|
|
1226
1379
|
}
|
|
1227
1380
|
}
|
|
1228
1381
|
|
|
1382
|
+
function renderError(title, error) {
|
|
1383
|
+
container.textContent = '';
|
|
1384
|
+
const root = document.createElement('div');
|
|
1385
|
+
root.className = 'musea-error';
|
|
1386
|
+
|
|
1387
|
+
const titleEl = document.createElement('div');
|
|
1388
|
+
titleEl.className = 'musea-error-title';
|
|
1389
|
+
titleEl.textContent = title;
|
|
1390
|
+
root.appendChild(titleEl);
|
|
1391
|
+
|
|
1392
|
+
const messageEl = document.createElement('div');
|
|
1393
|
+
messageEl.textContent = error instanceof Error ? error.message : String(error);
|
|
1394
|
+
root.appendChild(messageEl);
|
|
1395
|
+
|
|
1396
|
+
const stack = error instanceof Error ? error.stack : '';
|
|
1397
|
+
if (stack) {
|
|
1398
|
+
const stackEl = document.createElement('pre');
|
|
1399
|
+
stackEl.textContent = stack;
|
|
1400
|
+
root.appendChild(stackEl);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
container.appendChild(root);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1229
1406
|
window.__museaSetProps = (props) => {
|
|
1230
1407
|
// Clear old keys
|
|
1231
1408
|
for (const key of Object.keys(propsOverride)) {
|
|
@@ -1244,11 +1421,11 @@ window.__museaSetSlots = (slots) => {
|
|
|
1244
1421
|
async function mount() {
|
|
1245
1422
|
try {
|
|
1246
1423
|
// Get the specific variant component
|
|
1247
|
-
const VariantComponent = artModule[
|
|
1424
|
+
const VariantComponent = artModule[${variantComponentNameLiteral}];
|
|
1248
1425
|
const RawComponent = artModule.__component__;
|
|
1249
1426
|
|
|
1250
1427
|
if (!VariantComponent) {
|
|
1251
|
-
throw new Error('Variant component
|
|
1428
|
+
throw new Error('Variant component ' + ${variantComponentNameLiteral} + ' not found in art module');
|
|
1252
1429
|
}
|
|
1253
1430
|
|
|
1254
1431
|
// Create and mount the app
|
|
@@ -1260,8 +1437,8 @@ async function mount() {
|
|
|
1260
1437
|
app.mount(container);
|
|
1261
1438
|
currentApp = app;
|
|
1262
1439
|
|
|
1263
|
-
console.log('[musea-preview] Mounted variant: ${
|
|
1264
|
-
__museaInitAddons(container,
|
|
1440
|
+
console.log('[musea-preview] Mounted variant:', ${variantNameLiteral});
|
|
1441
|
+
__museaInitAddons(container, ${variantNameLiteral}, ${actionEvents});
|
|
1265
1442
|
|
|
1266
1443
|
// Override set-props to remount with raw component + props
|
|
1267
1444
|
const TargetComponent = RawComponent || VariantComponent;
|
|
@@ -1281,13 +1458,7 @@ async function mount() {
|
|
|
1281
1458
|
};
|
|
1282
1459
|
} catch (error) {
|
|
1283
1460
|
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
|
-
\`;
|
|
1461
|
+
renderError('Failed to render component', error);
|
|
1291
1462
|
}
|
|
1292
1463
|
}
|
|
1293
1464
|
|
|
@@ -1300,7 +1471,7 @@ async function remountWithProps(Component) {
|
|
|
1300
1471
|
return () => {
|
|
1301
1472
|
const slotFns = {};
|
|
1302
1473
|
for (const [name, content] of Object.entries(slotsOverride)) {
|
|
1303
|
-
if (content) slotFns[name] = () => h('span',
|
|
1474
|
+
if (content) slotFns[name] = () => h('span', String(content));
|
|
1304
1475
|
}
|
|
1305
1476
|
return h(Component, { ...propsOverride }, slotFns);
|
|
1306
1477
|
};
|
|
@@ -1318,17 +1489,20 @@ mount();
|
|
|
1318
1489
|
}
|
|
1319
1490
|
function generatePreviewModuleWithProps(art, variantComponentName, variantName, propsOverride, cssImports = [], previewSetup = null) {
|
|
1320
1491
|
const artModuleId = `virtual:musea-art:${art.path}`;
|
|
1321
|
-
const
|
|
1492
|
+
const artModuleIdLiteral = JSON.stringify(artModuleId);
|
|
1493
|
+
const variantNameLiteral = JSON.stringify(variantName);
|
|
1494
|
+
const variantComponentNameLiteral = JSON.stringify(variantComponentName);
|
|
1322
1495
|
const propsJson = JSON.stringify(propsOverride);
|
|
1323
|
-
const cssImportStatements = cssImports.map((cssPath) => `import
|
|
1324
|
-
const setupImport = previewSetup ? `import __museaPreviewSetup from
|
|
1496
|
+
const cssImportStatements = cssImports.map((cssPath) => `import ${JSON.stringify(cssPath)};`).join("\n");
|
|
1497
|
+
const setupImport = previewSetup ? `import __museaPreviewSetup from ${JSON.stringify(previewSetup)};` : "";
|
|
1325
1498
|
const setupCall = previewSetup ? "await __museaPreviewSetup(app);" : "";
|
|
1326
1499
|
const actionEvents = JSON.stringify(art.metadata.actionEvents ?? []);
|
|
1500
|
+
const artStyleId = `musea-art-styles-${art.path.replace(/[^\w-]+/g, "_")}`;
|
|
1327
1501
|
return `
|
|
1328
1502
|
${cssImportStatements}
|
|
1329
1503
|
${setupImport}
|
|
1330
1504
|
import { createApp, h } from 'vue';
|
|
1331
|
-
import * as artModule from
|
|
1505
|
+
import * as artModule from ${artModuleIdLiteral};
|
|
1332
1506
|
|
|
1333
1507
|
const container = document.getElementById('app');
|
|
1334
1508
|
const propsOverride = ${propsJson};
|
|
@@ -1336,7 +1510,7 @@ const propsOverride = ${propsJson};
|
|
|
1336
1510
|
${MUSEA_ADDONS_INIT_CODE}
|
|
1337
1511
|
|
|
1338
1512
|
function ensureArtStyles(styles) {
|
|
1339
|
-
const styleId =
|
|
1513
|
+
const styleId = ${JSON.stringify(artStyleId)};
|
|
1340
1514
|
const existing = document.getElementById(styleId);
|
|
1341
1515
|
|
|
1342
1516
|
if (!Array.isArray(styles) || styles.length === 0) {
|
|
@@ -1353,11 +1527,28 @@ function ensureArtStyles(styles) {
|
|
|
1353
1527
|
}
|
|
1354
1528
|
}
|
|
1355
1529
|
|
|
1530
|
+
function renderError(title, error) {
|
|
1531
|
+
container.textContent = '';
|
|
1532
|
+
const root = document.createElement('div');
|
|
1533
|
+
root.className = 'musea-error';
|
|
1534
|
+
|
|
1535
|
+
const titleEl = document.createElement('div');
|
|
1536
|
+
titleEl.className = 'musea-error-title';
|
|
1537
|
+
titleEl.textContent = title;
|
|
1538
|
+
root.appendChild(titleEl);
|
|
1539
|
+
|
|
1540
|
+
const messageEl = document.createElement('div');
|
|
1541
|
+
messageEl.textContent = error instanceof Error ? error.message : String(error);
|
|
1542
|
+
root.appendChild(messageEl);
|
|
1543
|
+
|
|
1544
|
+
container.appendChild(root);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1356
1547
|
async function mount() {
|
|
1357
1548
|
try {
|
|
1358
|
-
const VariantComponent = artModule[
|
|
1549
|
+
const VariantComponent = artModule[${variantComponentNameLiteral}];
|
|
1359
1550
|
if (!VariantComponent) {
|
|
1360
|
-
throw new Error('Variant component
|
|
1551
|
+
throw new Error('Variant component ' + ${variantComponentNameLiteral} + ' not found');
|
|
1361
1552
|
}
|
|
1362
1553
|
|
|
1363
1554
|
const WrappedComponent = {
|
|
@@ -1372,11 +1563,11 @@ async function mount() {
|
|
|
1372
1563
|
container.innerHTML = '';
|
|
1373
1564
|
container.className = 'musea-variant';
|
|
1374
1565
|
app.mount(container);
|
|
1375
|
-
console.log('[musea-preview] Mounted variant
|
|
1376
|
-
__museaInitAddons(container,
|
|
1566
|
+
console.log('[musea-preview] Mounted variant with props override:', ${variantNameLiteral});
|
|
1567
|
+
__museaInitAddons(container, ${variantNameLiteral}, ${actionEvents});
|
|
1377
1568
|
} catch (error) {
|
|
1378
1569
|
console.error('[musea-preview] Failed to mount:', error);
|
|
1379
|
-
|
|
1570
|
+
renderError('Failed to render', error);
|
|
1380
1571
|
}
|
|
1381
1572
|
}
|
|
1382
1573
|
|
|
@@ -1395,7 +1586,11 @@ function resolveGallerySourceDir() {
|
|
|
1395
1586
|
function toViteFsPath(filePath) {
|
|
1396
1587
|
return encodeURI(`/@fs${filePath.split(path.sep).join("/")}`);
|
|
1397
1588
|
}
|
|
1398
|
-
|
|
1589
|
+
function generateDevGlobalsScript(basePath, devSessionToken, themeConfig) {
|
|
1590
|
+
const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${serializeScriptValue(themeConfig)};` : "";
|
|
1591
|
+
return `window.__MUSEA_BASE_PATH__=${serializeScriptValue(basePath)};window.__MUSEA_SESSION_TOKEN__=${serializeScriptValue(devSessionToken)};${themeScript}`;
|
|
1592
|
+
}
|
|
1593
|
+
async function tryLoadSourceGalleryHtml(devServer, url, basePath, devSessionToken, themeConfig) {
|
|
1399
1594
|
const gallerySourceDir = resolveGallerySourceDir();
|
|
1400
1595
|
const indexHtmlPath = path.join(gallerySourceDir, "index.html");
|
|
1401
1596
|
try {
|
|
@@ -1404,10 +1599,9 @@ async function tryLoadSourceGalleryHtml(devServer, url, basePath, themeConfig) {
|
|
|
1404
1599
|
return null;
|
|
1405
1600
|
}
|
|
1406
1601
|
const sourceEntryPath = toViteFsPath(path.join(gallerySourceDir, "main.ts"));
|
|
1407
|
-
const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${JSON.stringify(themeConfig)};` : "";
|
|
1408
1602
|
let html = await fs.promises.readFile(indexHtmlPath, "utf-8");
|
|
1409
1603
|
html = html.replace("src=\"./main.ts\"", `src="${sourceEntryPath}"`);
|
|
1410
|
-
html = html.replace("</head>", `<script
|
|
1604
|
+
html = html.replace("</head>", `<script>${generateDevGlobalsScript(basePath, devSessionToken, themeConfig)}<\/script></head>`);
|
|
1411
1605
|
return devServer.transformIndexHtml(url, html);
|
|
1412
1606
|
}
|
|
1413
1607
|
/**
|
|
@@ -1422,7 +1616,7 @@ async function tryLoadSourceGalleryHtml(devServer, url, basePath, themeConfig) {
|
|
|
1422
1616
|
* - Art module route
|
|
1423
1617
|
*/
|
|
1424
1618
|
function registerMiddleware(devServer, ctx) {
|
|
1425
|
-
const { basePath, themeConfig, artFiles } = ctx;
|
|
1619
|
+
const { basePath, devSessionToken, themeConfig, artFiles } = ctx;
|
|
1426
1620
|
devServer.middlewares.use(basePath, async (req, res, next) => {
|
|
1427
1621
|
const url = req.url || "/";
|
|
1428
1622
|
if (url === "/" || url === "/index.html" || url.startsWith("/tokens") || url.startsWith("/component/") || url.startsWith("/tests")) {
|
|
@@ -1431,19 +1625,18 @@ function registerMiddleware(devServer, ctx) {
|
|
|
1431
1625
|
try {
|
|
1432
1626
|
await fs.promises.access(indexHtmlPath);
|
|
1433
1627
|
let html = await fs.promises.readFile(indexHtmlPath, "utf-8");
|
|
1434
|
-
|
|
1435
|
-
html = html.replace("</head>", `<script>window.__MUSEA_BASE_PATH__='${basePath}';${themeScript}<\/script></head>`);
|
|
1628
|
+
html = html.replace("</head>", `<script>${generateDevGlobalsScript(basePath, devSessionToken, themeConfig)}<\/script></head>`);
|
|
1436
1629
|
res.setHeader("Content-Type", "text/html");
|
|
1437
1630
|
res.end(html);
|
|
1438
1631
|
return;
|
|
1439
1632
|
} catch {
|
|
1440
|
-
const sourceHtml = await tryLoadSourceGalleryHtml(devServer, url, basePath, themeConfig);
|
|
1633
|
+
const sourceHtml = await tryLoadSourceGalleryHtml(devServer, url, basePath, devSessionToken, themeConfig);
|
|
1441
1634
|
if (sourceHtml) {
|
|
1442
1635
|
res.setHeader("Content-Type", "text/html");
|
|
1443
1636
|
res.end(sourceHtml);
|
|
1444
1637
|
return;
|
|
1445
1638
|
}
|
|
1446
|
-
const html = generateGalleryHtml(basePath, themeConfig);
|
|
1639
|
+
const html = generateGalleryHtml(basePath, devSessionToken, themeConfig);
|
|
1447
1640
|
res.setHeader("Content-Type", "text/html");
|
|
1448
1641
|
res.end(html);
|
|
1449
1642
|
return;
|
|
@@ -1451,8 +1644,8 @@ function registerMiddleware(devServer, ctx) {
|
|
|
1451
1644
|
}
|
|
1452
1645
|
if (url.startsWith("/assets/")) {
|
|
1453
1646
|
const galleryDistDir = resolveGalleryDistDir();
|
|
1454
|
-
const filePath = path.join(galleryDistDir, url);
|
|
1455
1647
|
try {
|
|
1648
|
+
const filePath = resolveUrlPathInside(galleryDistDir, url, "asset path");
|
|
1456
1649
|
if ((await fs.promises.stat(filePath)).isFile()) {
|
|
1457
1650
|
const content = await fs.promises.readFile(filePath);
|
|
1458
1651
|
const ext = path.extname(filePath);
|
|
@@ -1469,7 +1662,13 @@ function registerMiddleware(devServer, ctx) {
|
|
|
1469
1662
|
res.end(content);
|
|
1470
1663
|
return;
|
|
1471
1664
|
}
|
|
1472
|
-
} catch {
|
|
1665
|
+
} catch (error) {
|
|
1666
|
+
if (error instanceof HttpError) {
|
|
1667
|
+
res.statusCode = error.status;
|
|
1668
|
+
res.end(error.message);
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1473
1672
|
}
|
|
1474
1673
|
next();
|
|
1475
1674
|
});
|
|
@@ -1567,13 +1766,19 @@ function registerMiddleware(devServer, ctx) {
|
|
|
1567
1766
|
res.setHeader("Cache-Control", "no-cache");
|
|
1568
1767
|
res.end(result.code);
|
|
1569
1768
|
} else {
|
|
1570
|
-
const moduleCode = generateArtModule(art, artPath
|
|
1769
|
+
const moduleCode = generateArtModule(art, artPath, {
|
|
1770
|
+
root: devServer.config.root,
|
|
1771
|
+
scanRoots: ctx.scanRoots
|
|
1772
|
+
});
|
|
1571
1773
|
res.setHeader("Content-Type", "application/javascript");
|
|
1572
1774
|
res.end(moduleCode);
|
|
1573
1775
|
}
|
|
1574
1776
|
} catch (err) {
|
|
1575
1777
|
console.error("[musea] Failed to transform art module:", err);
|
|
1576
|
-
const moduleCode = generateArtModule(art, artPath
|
|
1778
|
+
const moduleCode = generateArtModule(art, artPath, {
|
|
1779
|
+
root: devServer.config.root,
|
|
1780
|
+
scanRoots: ctx.scanRoots
|
|
1781
|
+
});
|
|
1577
1782
|
res.setHeader("Content-Type", "application/javascript");
|
|
1578
1783
|
res.end(moduleCode);
|
|
1579
1784
|
}
|
|
@@ -1792,6 +1997,18 @@ function scanTokenUsage(artFiles, tokenMap) {
|
|
|
1792
1997
|
*/
|
|
1793
1998
|
const REFERENCE_PATTERN = /^\{(.+)\}$/;
|
|
1794
1999
|
const MAX_RESOLVE_DEPTH = 10;
|
|
2000
|
+
const UNSAFE_TOKEN_PATH_SEGMENTS = new Set([
|
|
2001
|
+
"__proto__",
|
|
2002
|
+
"prototype",
|
|
2003
|
+
"constructor"
|
|
2004
|
+
]);
|
|
2005
|
+
function parseTokenPath(dotPath) {
|
|
2006
|
+
const parts = dotPath.split(".");
|
|
2007
|
+
if (parts.length === 0 || parts.some((part) => part.trim() === "")) throw new Error(`Invalid token path "${dotPath}"`);
|
|
2008
|
+
const unsafeSegment = parts.find((part) => UNSAFE_TOKEN_PATH_SEGMENTS.has(part));
|
|
2009
|
+
if (unsafeSegment) throw new Error(`Token path segment "${unsafeSegment}" is not allowed`);
|
|
2010
|
+
return parts;
|
|
2011
|
+
}
|
|
1795
2012
|
/**
|
|
1796
2013
|
* Flatten nested categories into a flat map keyed by dot-path.
|
|
1797
2014
|
*/
|
|
@@ -1862,7 +2079,7 @@ async function writeRawTokenFile(tokensPath, data) {
|
|
|
1862
2079
|
* Set a token at a dot-separated path in the raw JSON structure.
|
|
1863
2080
|
*/
|
|
1864
2081
|
function setTokenAtPath(data, dotPath, token) {
|
|
1865
|
-
const parts = dotPath
|
|
2082
|
+
const parts = parseTokenPath(dotPath);
|
|
1866
2083
|
let current = data;
|
|
1867
2084
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
1868
2085
|
const key = parts[i];
|
|
@@ -1882,7 +2099,7 @@ function setTokenAtPath(data, dotPath, token) {
|
|
|
1882
2099
|
* Delete a token at a dot-separated path, cleaning empty parents.
|
|
1883
2100
|
*/
|
|
1884
2101
|
function deleteTokenAtPath(data, dotPath) {
|
|
1885
|
-
const parts = dotPath
|
|
2102
|
+
const parts = parseTokenPath(dotPath);
|
|
1886
2103
|
const parents = [];
|
|
1887
2104
|
let current = data;
|
|
1888
2105
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
@@ -1960,27 +2177,34 @@ function findDependentTokens(tokenMap, targetPath) {
|
|
|
1960
2177
|
* Generates HTML, Markdown, and JSON documentation from parsed token categories,
|
|
1961
2178
|
* and provides the main processStyleDictionary orchestrator function.
|
|
1962
2179
|
*/
|
|
2180
|
+
const SAFE_CSS_COLOR_PATTERN = /^(?:#[0-9a-fA-F]{3,8}|(?:rgb|hsl)a?\(\s*[-+.\d,%\s]+\))$/;
|
|
2181
|
+
function safeCssColor(value, type) {
|
|
2182
|
+
if (typeof value !== "string") return null;
|
|
2183
|
+
const trimmed = value.trim();
|
|
2184
|
+
return (type === "color" || trimmed.startsWith("#") || trimmed.startsWith("rgb") || trimmed.startsWith("hsl")) && SAFE_CSS_COLOR_PATTERN.test(trimmed) ? trimmed : null;
|
|
2185
|
+
}
|
|
1963
2186
|
/**
|
|
1964
2187
|
* Generate HTML documentation for tokens.
|
|
1965
2188
|
*/
|
|
1966
2189
|
function generateTokensHtml(categories) {
|
|
1967
2190
|
const renderToken = (name, token) => {
|
|
2191
|
+
const color = safeCssColor(token.value, token.type);
|
|
1968
2192
|
return `
|
|
1969
2193
|
<div class="token">
|
|
1970
2194
|
<div class="token-preview">
|
|
1971
|
-
${
|
|
2195
|
+
${color ? `<div class="color-swatch" style="background: ${color}"></div>` : ""}
|
|
1972
2196
|
</div>
|
|
1973
2197
|
<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>` : ""}
|
|
2198
|
+
<div class="token-name">${escapeHtml(name)}</div>
|
|
2199
|
+
<div class="token-value">${escapeHtml(String(token.value))}</div>
|
|
2200
|
+
${token.description ? `<div class="token-description">${escapeHtml(token.description)}</div>` : ""}
|
|
1977
2201
|
</div>
|
|
1978
2202
|
</div>
|
|
1979
2203
|
`;
|
|
1980
2204
|
};
|
|
1981
2205
|
const renderCategory = (category, level = 2) => {
|
|
1982
2206
|
const heading = `h${Math.min(level, 6)}`;
|
|
1983
|
-
let html = `<${heading}>${category.name}</${heading}>`;
|
|
2207
|
+
let html = `<${heading}>${escapeHtml(category.name)}</${heading}>`;
|
|
1984
2208
|
html += "<div class=\"tokens-grid\">";
|
|
1985
2209
|
for (const [name, token] of Object.entries(category.tokens)) html += renderToken(name, token);
|
|
1986
2210
|
html += "</div>";
|
|
@@ -2130,16 +2354,20 @@ async function processStyleDictionary(config) {
|
|
|
2130
2354
|
}
|
|
2131
2355
|
//#endregion
|
|
2132
2356
|
//#region src/api-tokens.ts
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2357
|
+
function resolveTokensPath(ctx) {
|
|
2358
|
+
return resolveInside(ctx.config.root, ctx.tokensPath, "tokensPath");
|
|
2359
|
+
}
|
|
2360
|
+
function sendTokenMutationError(e, sendError) {
|
|
2361
|
+
if (e instanceof HttpError) {
|
|
2362
|
+
sendError(e.message, e.status);
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
if (e instanceof Error && /token path/i.test(e.message)) {
|
|
2366
|
+
sendError(e.message, 400);
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
2370
|
+
}
|
|
2143
2371
|
/** GET /api/tokens/usage */
|
|
2144
2372
|
async function handleTokensUsage(ctx, sendJson) {
|
|
2145
2373
|
if (!ctx.tokensPath) {
|
|
@@ -2147,7 +2375,7 @@ async function handleTokensUsage(ctx, sendJson) {
|
|
|
2147
2375
|
return;
|
|
2148
2376
|
}
|
|
2149
2377
|
try {
|
|
2150
|
-
const categories = await parseTokens(
|
|
2378
|
+
const categories = await parseTokens(resolveTokensPath(ctx));
|
|
2151
2379
|
resolveReferences(categories, buildTokenMap(categories));
|
|
2152
2380
|
const resolvedTokenMap = buildTokenMap(categories);
|
|
2153
2381
|
sendJson(scanTokenUsage(ctx.artFiles, resolvedTokenMap));
|
|
@@ -2172,7 +2400,7 @@ async function handleTokensGet(ctx, sendJson) {
|
|
|
2172
2400
|
return;
|
|
2173
2401
|
}
|
|
2174
2402
|
try {
|
|
2175
|
-
const absoluteTokensPath =
|
|
2403
|
+
const absoluteTokensPath = resolveTokensPath(ctx);
|
|
2176
2404
|
const categories = await parseTokens(absoluteTokensPath);
|
|
2177
2405
|
resolveReferences(categories, buildTokenMap(categories));
|
|
2178
2406
|
const resolvedTokenMap = buildTokenMap(categories);
|
|
@@ -2212,7 +2440,7 @@ async function handleTokensCreate(ctx, readBody, sendJson, sendError) {
|
|
|
2212
2440
|
sendError("Missing required fields: path, token.value", 400);
|
|
2213
2441
|
return;
|
|
2214
2442
|
}
|
|
2215
|
-
const absoluteTokensPath =
|
|
2443
|
+
const absoluteTokensPath = resolveTokensPath(ctx);
|
|
2216
2444
|
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
2217
2445
|
const currentMap = buildTokenMap(await parseTokens(absoluteTokensPath));
|
|
2218
2446
|
if (currentMap[dotPath]) {
|
|
@@ -2237,7 +2465,7 @@ async function handleTokensCreate(ctx, readBody, sendJson, sendError) {
|
|
|
2237
2465
|
tokenMap: buildTokenMap(categories)
|
|
2238
2466
|
}, 201);
|
|
2239
2467
|
} catch (e) {
|
|
2240
|
-
|
|
2468
|
+
sendTokenMutationError(e, sendError);
|
|
2241
2469
|
}
|
|
2242
2470
|
}
|
|
2243
2471
|
/** PUT /api/tokens (update) */
|
|
@@ -2253,7 +2481,7 @@ async function handleTokensUpdate(ctx, readBody, sendJson, sendError) {
|
|
|
2253
2481
|
sendError("Missing required fields: path, token.value", 400);
|
|
2254
2482
|
return;
|
|
2255
2483
|
}
|
|
2256
|
-
const absoluteTokensPath =
|
|
2484
|
+
const absoluteTokensPath = resolveTokensPath(ctx);
|
|
2257
2485
|
if (token.$reference) {
|
|
2258
2486
|
const validation = validateSemanticReference(buildTokenMap(await parseTokens(absoluteTokensPath)), token.$reference, dotPath);
|
|
2259
2487
|
if (!validation.valid) {
|
|
@@ -2273,7 +2501,7 @@ async function handleTokensUpdate(ctx, readBody, sendJson, sendError) {
|
|
|
2273
2501
|
tokenMap: buildTokenMap(categories)
|
|
2274
2502
|
});
|
|
2275
2503
|
} catch (e) {
|
|
2276
|
-
|
|
2504
|
+
sendTokenMutationError(e, sendError);
|
|
2277
2505
|
}
|
|
2278
2506
|
}
|
|
2279
2507
|
/** DELETE /api/tokens */
|
|
@@ -2289,7 +2517,7 @@ async function handleTokensDelete(ctx, readBody, sendJson, sendError) {
|
|
|
2289
2517
|
sendError("Missing required field: path", 400);
|
|
2290
2518
|
return;
|
|
2291
2519
|
}
|
|
2292
|
-
const absoluteTokensPath =
|
|
2520
|
+
const absoluteTokensPath = resolveTokensPath(ctx);
|
|
2293
2521
|
const dependents = findDependentTokens(buildTokenMap(await parseTokens(absoluteTokensPath)), dotPath);
|
|
2294
2522
|
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
2295
2523
|
if (!deleteTokenAtPath(rawData, dotPath)) {
|
|
@@ -2305,7 +2533,7 @@ async function handleTokensDelete(ctx, readBody, sendJson, sendError) {
|
|
|
2305
2533
|
dependentsWarning: dependents.length > 0 ? dependents : void 0
|
|
2306
2534
|
});
|
|
2307
2535
|
} catch (e) {
|
|
2308
|
-
|
|
2536
|
+
sendTokenMutationError(e, sendError);
|
|
2309
2537
|
}
|
|
2310
2538
|
}
|
|
2311
2539
|
//#endregion
|
|
@@ -2336,7 +2564,11 @@ async function handleArtPalette(ctx, match, sendJson, sendError) {
|
|
|
2336
2564
|
typescript: ""
|
|
2337
2565
|
};
|
|
2338
2566
|
if (palette.controls.length === 0 && art.metadata.component) {
|
|
2339
|
-
const resolvedComponentPath =
|
|
2567
|
+
const resolvedComponentPath = resolveComponentSourcePath(art, artPath, allowedSourceRoots(ctx.config.root, ctx.scanRoots));
|
|
2568
|
+
if (!resolvedComponentPath) {
|
|
2569
|
+
sendJson(palette);
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2340
2572
|
try {
|
|
2341
2573
|
const componentSource = await fs.promises.readFile(resolvedComponentPath, "utf-8");
|
|
2342
2574
|
const analysis = binding.analyzeSfc ? binding.analyzeSfc(componentSource, { filename: resolvedComponentPath }) : analyzeSfcFallback(componentSource, { filename: resolvedComponentPath });
|
|
@@ -2414,7 +2646,7 @@ async function handleArtAnalysis(ctx, match, sendJson, sendError) {
|
|
|
2414
2646
|
return;
|
|
2415
2647
|
}
|
|
2416
2648
|
try {
|
|
2417
|
-
const resolvedComponentPath = art
|
|
2649
|
+
const resolvedComponentPath = resolveComponentSourcePath(art, artPath, allowedSourceRoots(ctx.config.root, ctx.scanRoots));
|
|
2418
2650
|
if (resolvedComponentPath) {
|
|
2419
2651
|
const source = await fs.promises.readFile(resolvedComponentPath, "utf-8");
|
|
2420
2652
|
const binding = loadNative();
|
|
@@ -2512,15 +2744,23 @@ function handlePreviewWithProps(ctx, body, res, sendJson, sendError) {
|
|
|
2512
2744
|
res.setHeader("Content-Type", "application/javascript");
|
|
2513
2745
|
res.end(moduleCode);
|
|
2514
2746
|
} catch (e) {
|
|
2747
|
+
if (e instanceof HttpError) {
|
|
2748
|
+
sendError(e.message, e.status);
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2515
2751
|
sendError(e instanceof Error ? e.message : String(e));
|
|
2516
2752
|
}
|
|
2517
2753
|
}
|
|
2518
2754
|
/** POST /api/generate */
|
|
2519
|
-
async function handleGenerate(body, sendJson, sendError) {
|
|
2755
|
+
async function handleGenerate(ctx, body, sendJson, sendError) {
|
|
2520
2756
|
try {
|
|
2521
2757
|
const { componentPath: reqComponentPath, options: autogenOptions } = JSON.parse(body);
|
|
2758
|
+
if (typeof reqComponentPath !== "string") {
|
|
2759
|
+
sendError("Missing required field: componentPath", 400);
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
2522
2762
|
const { generateArtFile: genArt } = await import("./autogen/index.mjs");
|
|
2523
|
-
const result = await genArt(reqComponentPath, autogenOptions);
|
|
2763
|
+
const result = await genArt(resolveInside(ctx.config.root, reqComponentPath, "componentPath"), autogenOptions);
|
|
2524
2764
|
sendJson({
|
|
2525
2765
|
generated: true,
|
|
2526
2766
|
componentName: result.componentName,
|
|
@@ -2528,6 +2768,10 @@ async function handleGenerate(body, sendJson, sendError) {
|
|
|
2528
2768
|
artFileContent: result.artFileContent
|
|
2529
2769
|
});
|
|
2530
2770
|
} catch (e) {
|
|
2771
|
+
if (e instanceof HttpError) {
|
|
2772
|
+
sendError(e.message, e.status);
|
|
2773
|
+
return;
|
|
2774
|
+
}
|
|
2531
2775
|
sendError(e instanceof Error ? e.message : String(e));
|
|
2532
2776
|
}
|
|
2533
2777
|
}
|
|
@@ -2590,16 +2834,6 @@ async function handleRunVrt(ctx, body, sendJson, sendError) {
|
|
|
2590
2834
|
}
|
|
2591
2835
|
//#endregion
|
|
2592
2836
|
//#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
2837
|
/**
|
|
2604
2838
|
* Create the API middleware handler for the Musea gallery.
|
|
2605
2839
|
*
|
|
@@ -2616,107 +2850,117 @@ function createApiMiddleware(ctx) {
|
|
|
2616
2850
|
const sendError = (message, status = 500) => {
|
|
2617
2851
|
sendJson({ error: message }, status);
|
|
2618
2852
|
};
|
|
2619
|
-
const readBody = () =>
|
|
2853
|
+
const readBody = () => collectRequestBody(req, ctx.apiBodyLimit ?? 1048576);
|
|
2620
2854
|
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
|
-
}
|
|
2855
|
+
try {
|
|
2856
|
+
const requestError = validateDevApiRequest(req, ctx.devSessionToken);
|
|
2857
|
+
if (requestError) {
|
|
2858
|
+
sendError(requestError.message, requestError.status);
|
|
2666
2859
|
return;
|
|
2667
2860
|
}
|
|
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);
|
|
2861
|
+
if (url === "/arts" && req.method === "GET") {
|
|
2862
|
+
sendJson(Array.from(ctx.artFiles.values()));
|
|
2680
2863
|
return;
|
|
2681
2864
|
}
|
|
2682
|
-
if (
|
|
2683
|
-
await
|
|
2865
|
+
if (url === "/tokens/usage" && req.method === "GET") {
|
|
2866
|
+
await handleTokensUsage(ctx, sendJson);
|
|
2684
2867
|
return;
|
|
2685
2868
|
}
|
|
2686
|
-
if (
|
|
2687
|
-
await
|
|
2869
|
+
if (url === "/tokens" && req.method === "GET") {
|
|
2870
|
+
await handleTokensGet(ctx, sendJson);
|
|
2688
2871
|
return;
|
|
2689
2872
|
}
|
|
2690
|
-
if (
|
|
2691
|
-
await
|
|
2873
|
+
if (url === "/tokens" && req.method === "POST") {
|
|
2874
|
+
await handleTokensCreate(ctx, readBody, sendJson, sendError);
|
|
2692
2875
|
return;
|
|
2693
2876
|
}
|
|
2694
|
-
if (
|
|
2695
|
-
|
|
2877
|
+
if (url === "/tokens" && req.method === "PUT") {
|
|
2878
|
+
await handleTokensUpdate(ctx, readBody, sendJson, sendError);
|
|
2696
2879
|
return;
|
|
2697
2880
|
}
|
|
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);
|
|
2881
|
+
if (url === "/tokens" && req.method === "DELETE") {
|
|
2882
|
+
await handleTokensDelete(ctx, readBody, sendJson, sendError);
|
|
2708
2883
|
return;
|
|
2709
2884
|
}
|
|
2710
|
-
if (url === "
|
|
2711
|
-
|
|
2885
|
+
if (url?.startsWith("/arts/") && req.method === "PUT") {
|
|
2886
|
+
const sourceMatch = url.slice(6).match(/^(.+)\/source$/);
|
|
2887
|
+
if (sourceMatch) {
|
|
2888
|
+
const artPath = decodeURIComponent(sourceMatch[1]);
|
|
2889
|
+
if (!ctx.artFiles.get(artPath)) {
|
|
2890
|
+
sendError("Art not found", 404);
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2893
|
+
const safeArtPath = resolveInside(ctx.config.root, artPath, "art path");
|
|
2894
|
+
const body = await readBody();
|
|
2895
|
+
const { source } = JSON.parse(body);
|
|
2896
|
+
if (typeof source !== "string") {
|
|
2897
|
+
sendError("Missing required field: source", 400);
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
await fs.promises.writeFile(safeArtPath, source, "utf-8");
|
|
2901
|
+
await ctx.processArtFile(safeArtPath);
|
|
2902
|
+
sendJson({ success: true });
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
next();
|
|
2712
2906
|
return;
|
|
2713
2907
|
}
|
|
2714
|
-
if (url === "
|
|
2715
|
-
|
|
2908
|
+
if (url?.startsWith("/arts/") && req.method === "GET") {
|
|
2909
|
+
const rest = url.slice(6);
|
|
2910
|
+
const sourceMatch = rest.match(/^(.+)\/source$/);
|
|
2911
|
+
const paletteMatch = rest.match(/^(.+)\/palette$/);
|
|
2912
|
+
const analysisMatch = rest.match(/^(.+)\/analysis$/);
|
|
2913
|
+
const docsMatch = rest.match(/^(.+)\/docs$/);
|
|
2914
|
+
const a11yMatch = rest.match(/^(.+)\/variants\/([^/]+)\/a11y$/);
|
|
2915
|
+
if (sourceMatch) {
|
|
2916
|
+
await handleArtSource(ctx, sourceMatch, sendJson, sendError);
|
|
2917
|
+
return;
|
|
2918
|
+
}
|
|
2919
|
+
if (paletteMatch) {
|
|
2920
|
+
await handleArtPalette(ctx, paletteMatch, sendJson, sendError);
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2923
|
+
if (analysisMatch) {
|
|
2924
|
+
await handleArtAnalysis(ctx, analysisMatch, sendJson, sendError);
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
if (docsMatch) {
|
|
2928
|
+
await handleArtDocs(ctx, docsMatch, sendJson, sendError);
|
|
2929
|
+
return;
|
|
2930
|
+
}
|
|
2931
|
+
if (a11yMatch) {
|
|
2932
|
+
handleArtA11y(ctx, a11yMatch, sendJson, sendError);
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2935
|
+
const artPath = decodeURIComponent(rest);
|
|
2936
|
+
const art = ctx.artFiles.get(artPath);
|
|
2937
|
+
if (art) sendJson(art);
|
|
2938
|
+
else sendError("Art not found", 404);
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
if (req.method === "POST") {
|
|
2942
|
+
const body = await readBody();
|
|
2943
|
+
if (url === "/preview-with-props") {
|
|
2944
|
+
handlePreviewWithProps(ctx, body, res, sendJson, sendError);
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
if (url === "/generate") {
|
|
2948
|
+
await handleGenerate(ctx, body, sendJson, sendError);
|
|
2949
|
+
return;
|
|
2950
|
+
}
|
|
2951
|
+
if (url === "/run-vrt") {
|
|
2952
|
+
await handleRunVrt(ctx, body, sendJson, sendError);
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
next();
|
|
2957
|
+
} catch (e) {
|
|
2958
|
+
if (e instanceof HttpError) {
|
|
2959
|
+
sendError(e.message, e.status);
|
|
2716
2960
|
return;
|
|
2717
2961
|
}
|
|
2962
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
2718
2963
|
}
|
|
2719
|
-
next();
|
|
2720
2964
|
};
|
|
2721
2965
|
}
|
|
2722
2966
|
//#endregion
|
|
@@ -2777,12 +3021,18 @@ function createLoad(state) {
|
|
|
2777
3021
|
if (id.startsWith("\0musea-art:")) {
|
|
2778
3022
|
const artPath = id.slice(11).replace(/\?musea-virtual$/, "");
|
|
2779
3023
|
const art = state.artFiles.get(artPath);
|
|
2780
|
-
if (art) return generateArtModule(art, artPath
|
|
3024
|
+
if (art) return generateArtModule(art, artPath, {
|
|
3025
|
+
root: state.getConfigRoot(),
|
|
3026
|
+
scanRoots: state.getScanRoots()
|
|
3027
|
+
});
|
|
2781
3028
|
}
|
|
2782
3029
|
if (id.startsWith("\0musea:")) {
|
|
2783
3030
|
const realPath = id.slice(7).replace(/\?musea-virtual$/, "");
|
|
2784
3031
|
const art = state.artFiles.get(realPath);
|
|
2785
|
-
if (art) return generateArtModule(art, realPath
|
|
3032
|
+
if (art) return generateArtModule(art, realPath, {
|
|
3033
|
+
root: state.getConfigRoot(),
|
|
3034
|
+
scanRoots: state.getScanRoots()
|
|
3035
|
+
});
|
|
2786
3036
|
}
|
|
2787
3037
|
return null;
|
|
2788
3038
|
};
|
|
@@ -2855,6 +3105,7 @@ function musea(options = {}) {
|
|
|
2855
3105
|
const themeConfig = buildThemeConfig(options.theme);
|
|
2856
3106
|
const previewCss = options.previewCss ?? [];
|
|
2857
3107
|
const previewSetup = options.previewSetup;
|
|
3108
|
+
const devSessionToken = createDevSessionToken();
|
|
2858
3109
|
let config;
|
|
2859
3110
|
let server = null;
|
|
2860
3111
|
const artFiles = /* @__PURE__ */ new Map();
|
|
@@ -2870,6 +3121,7 @@ function musea(options = {}) {
|
|
|
2870
3121
|
resolvedPreviewCss,
|
|
2871
3122
|
resolvedPreviewSetup,
|
|
2872
3123
|
getConfigRoot: () => config.root,
|
|
3124
|
+
getScanRoots: () => scanRoots,
|
|
2873
3125
|
getServer: () => server,
|
|
2874
3126
|
processArtFile
|
|
2875
3127
|
};
|
|
@@ -2902,18 +3154,22 @@ function musea(options = {}) {
|
|
|
2902
3154
|
devServer.watcher.add(scanRoots);
|
|
2903
3155
|
registerMiddleware(devServer, {
|
|
2904
3156
|
basePath,
|
|
3157
|
+
devSessionToken,
|
|
2905
3158
|
themeConfig,
|
|
2906
3159
|
artFiles,
|
|
3160
|
+
scanRoots,
|
|
2907
3161
|
resolvedPreviewCss,
|
|
2908
3162
|
resolvedPreviewSetup
|
|
2909
3163
|
});
|
|
2910
3164
|
devServer.middlewares.use(`${basePath}/api`, createApiMiddleware({
|
|
2911
3165
|
config,
|
|
2912
3166
|
artFiles,
|
|
3167
|
+
scanRoots,
|
|
2913
3168
|
tokensPath,
|
|
2914
3169
|
basePath,
|
|
2915
3170
|
resolvedPreviewCss,
|
|
2916
3171
|
resolvedPreviewSetup,
|
|
3172
|
+
devSessionToken,
|
|
2917
3173
|
processArtFile,
|
|
2918
3174
|
getDevServerPort: () => devServer.config.server.port || 5173
|
|
2919
3175
|
}));
|