@tixxin/nuxt-theme-engine 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,897 @@
1
+ import { promises } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { useNuxt, defineNuxtModule, createResolver, addImportsDir, addPlugin, addComponent, addTemplate, addTypeTemplate, extendPages, logger } from '@nuxt/kit';
4
+ import 'node:child_process';
5
+ import 'node:path';
6
+ import 'node:process';
7
+ import 'node:stream';
8
+ import 'node:readline';
9
+ import { resolve, relative, extname, dirname, join } from 'pathe';
10
+ import fg from 'fast-glob';
11
+ import { z } from 'zod';
12
+
13
+ //#region \0rolldown/runtime.js
14
+ var l = (e, t) => () => (t || e((t = { exports: {} }).exports, t), t.exports);
15
+ var u = /* @__PURE__ */ createRequire(import.meta.url);
16
+ //#endregion
17
+ //#region node_modules/isexe/windows.js
18
+ var _ = /* @__PURE__ */ l(((e, t) => {
19
+ t.exports = a;
20
+ a.sync = o;
21
+ var n = u("fs");
22
+ function r(e, t) {
23
+ var n = t.pathExt !== void 0 ? t.pathExt : process.env.PATHEXT;
24
+ if (!n) return true;
25
+ n = n.split(";");
26
+ if (n.indexOf("") !== -1) return true;
27
+ for (var r = 0; r < n.length; r++) {
28
+ var i = n[r].toLowerCase();
29
+ if (i && e.substr(-i.length).toLowerCase() === i) return true;
30
+ }
31
+ return false;
32
+ }
33
+ function i(e, t, n) {
34
+ if (!e.isSymbolicLink() && !e.isFile()) return false;
35
+ return r(t, n);
36
+ }
37
+ function a(e, t, r) {
38
+ n.stat(e, function(n, a) {
39
+ r(n, n ? false : i(a, e, t));
40
+ });
41
+ }
42
+ function o(e, t) {
43
+ return i(n.statSync(e), e, t);
44
+ }
45
+ }));
46
+ //#endregion
47
+ //#region node_modules/isexe/mode.js
48
+ var v = /* @__PURE__ */ l(((e, t) => {
49
+ t.exports = r;
50
+ r.sync = i;
51
+ var n = u("fs");
52
+ function r(e, t, r) {
53
+ n.stat(e, function(e, n) {
54
+ r(e, e ? false : a(n, t));
55
+ });
56
+ }
57
+ function i(e, t) {
58
+ return a(n.statSync(e), t);
59
+ }
60
+ function a(e, t) {
61
+ return e.isFile() && o(e, t);
62
+ }
63
+ function o(e, t) {
64
+ var n = e.mode;
65
+ var r = e.uid;
66
+ var i = e.gid;
67
+ var a = t.uid !== void 0 ? t.uid : process.getuid && process.getuid();
68
+ var o = t.gid !== void 0 ? t.gid : process.getgid && process.getgid();
69
+ var s = parseInt("100", 8);
70
+ var c = parseInt("010", 8);
71
+ var l = parseInt("001", 8);
72
+ var u = s | c;
73
+ return n & l || n & c && i === o || n & s && r === a || n & u && a === 0;
74
+ }
75
+ }));
76
+ //#endregion
77
+ //#region node_modules/isexe/index.js
78
+ var y = /* @__PURE__ */ l(((e, t) => {
79
+ u("fs");
80
+ var n;
81
+ if (process.platform === "win32" || global.TESTING_WINDOWS) n = _();
82
+ else n = v();
83
+ t.exports = r;
84
+ r.sync = i;
85
+ function r(e, t, i) {
86
+ if (typeof t === "function") {
87
+ i = t;
88
+ t = {};
89
+ }
90
+ if (!i) {
91
+ if (typeof Promise !== "function") throw new TypeError("callback not provided");
92
+ return new Promise(function(n, i) {
93
+ r(e, t || {}, function(e, t) {
94
+ if (e) i(e);
95
+ else n(t);
96
+ });
97
+ });
98
+ }
99
+ n(e, t || {}, function(e, n) {
100
+ if (e) {
101
+ if (e.code === "EACCES" || t && t.ignoreErrors) {
102
+ e = null;
103
+ n = false;
104
+ }
105
+ }
106
+ i(e, n);
107
+ });
108
+ }
109
+ function i(e, t) {
110
+ try {
111
+ return n.sync(e, t || {});
112
+ } catch (e) {
113
+ if (t && t.ignoreErrors || e.code === "EACCES") return false;
114
+ else throw e;
115
+ }
116
+ }
117
+ }));
118
+ //#endregion
119
+ //#region node_modules/which/which.js
120
+ var b = /* @__PURE__ */ l(((e, t) => {
121
+ const n = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
122
+ const r = u("path");
123
+ const i = n ? ";" : ":";
124
+ const a = y();
125
+ const o = (e) => Object.assign(/* @__PURE__ */ new Error(`not found: ${e}`), { code: "ENOENT" });
126
+ const s = (e, t) => {
127
+ const r = t.colon || i;
128
+ const a = e.match(/\//) || n && e.match(/\\/) ? [""] : [...n ? [process.cwd()] : [], ...(t.path || process.env.PATH || "").split(r)];
129
+ const o = n ? t.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
130
+ const s = n ? o.split(r) : [""];
131
+ if (n) {
132
+ if (e.indexOf(".") !== -1 && s[0] !== "") s.unshift("");
133
+ }
134
+ return {
135
+ pathEnv: a,
136
+ pathExt: s,
137
+ pathExtExe: o
138
+ };
139
+ };
140
+ const c = (e, t, n) => {
141
+ if (typeof t === "function") {
142
+ n = t;
143
+ t = {};
144
+ }
145
+ if (!t) t = {};
146
+ const { pathEnv: i, pathExt: c, pathExtExe: l } = s(e, t);
147
+ const u = [];
148
+ const d = (n) => new Promise((a, s) => {
149
+ if (n === i.length) return t.all && u.length ? a(u) : s(o(e));
150
+ const c = i[n];
151
+ const l = /^".*"$/.test(c) ? c.slice(1, -1) : c;
152
+ const d = r.join(l, e);
153
+ a(f(!l && /^\.[\\\/]/.test(e) ? e.slice(0, 2) + d : d, n, 0));
154
+ });
155
+ const f = (e, n, r) => new Promise((i, o) => {
156
+ if (r === c.length) return i(d(n + 1));
157
+ const s = c[r];
158
+ a(e + s, { pathExt: l }, (a, o) => {
159
+ if (!a && o) if (t.all) u.push(e + s);
160
+ else return i(e + s);
161
+ return i(f(e, n, r + 1));
162
+ });
163
+ });
164
+ return n ? d(0).then((e) => n(null, e), n) : d(0);
165
+ };
166
+ const l = (e, t) => {
167
+ t = t || {};
168
+ const { pathEnv: n, pathExt: i, pathExtExe: c } = s(e, t);
169
+ const l = [];
170
+ for (let o = 0; o < n.length; o++) {
171
+ const s = n[o];
172
+ const u = /^".*"$/.test(s) ? s.slice(1, -1) : s;
173
+ const d = r.join(u, e);
174
+ const f = !u && /^\.[\\\/]/.test(e) ? e.slice(0, 2) + d : d;
175
+ for (let e = 0; e < i.length; e++) {
176
+ const n = f + i[e];
177
+ try {
178
+ if (a.sync(n, { pathExt: c })) if (t.all) l.push(n);
179
+ else return n;
180
+ } catch (e) {}
181
+ }
182
+ }
183
+ if (t.all && l.length) return l;
184
+ if (t.nothrow) return null;
185
+ throw o(e);
186
+ };
187
+ t.exports = c;
188
+ c.sync = l;
189
+ }));
190
+ //#endregion
191
+ //#region node_modules/path-key/index.js
192
+ var x = /* @__PURE__ */ l(((e, t) => {
193
+ const n = (e = {}) => {
194
+ const t = e.env || process.env;
195
+ if ((e.platform || process.platform) !== "win32") return "PATH";
196
+ return Object.keys(t).reverse().find((e) => e.toUpperCase() === "PATH") || "Path";
197
+ };
198
+ t.exports = n;
199
+ t.exports.default = n;
200
+ }));
201
+ //#endregion
202
+ //#region node_modules/cross-spawn/lib/util/resolveCommand.js
203
+ var S = /* @__PURE__ */ l(((e, t) => {
204
+ const n = u("path");
205
+ const r = b();
206
+ const i = x();
207
+ function a(e, t) {
208
+ const a = e.options.env || process.env;
209
+ const o = process.cwd();
210
+ const s = e.options.cwd != null;
211
+ const c = s && process.chdir !== void 0 && !process.chdir.disabled;
212
+ if (c) try {
213
+ process.chdir(e.options.cwd);
214
+ } catch (e) {}
215
+ let l;
216
+ try {
217
+ l = r.sync(e.command, {
218
+ path: a[i({ env: a })],
219
+ pathExt: t ? n.delimiter : void 0
220
+ });
221
+ } catch (e) {} finally {
222
+ if (c) process.chdir(o);
223
+ }
224
+ if (l) l = n.resolve(s ? e.options.cwd : "", l);
225
+ return l;
226
+ }
227
+ function o(e) {
228
+ return a(e) || a(e, true);
229
+ }
230
+ t.exports = o;
231
+ }));
232
+ //#endregion
233
+ //#region node_modules/cross-spawn/lib/util/escape.js
234
+ var C = /* @__PURE__ */ l(((e, t) => {
235
+ const n = /([()\][%!^"`<>&|;, *?])/g;
236
+ function r(e) {
237
+ e = e.replace(n, "^$1");
238
+ return e;
239
+ }
240
+ function i(e, t) {
241
+ e = `${e}`;
242
+ e = e.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"");
243
+ e = e.replace(/(?=(\\+?)?)\1$/, "$1$1");
244
+ e = `"${e}"`;
245
+ e = e.replace(n, "^$1");
246
+ if (t) e = e.replace(n, "^$1");
247
+ return e;
248
+ }
249
+ t.exports.command = r;
250
+ t.exports.argument = i;
251
+ }));
252
+ //#endregion
253
+ //#region node_modules/shebang-regex/index.js
254
+ var w = /* @__PURE__ */ l(((e, t) => {
255
+ t.exports = /^#!(.*)/;
256
+ }));
257
+ //#endregion
258
+ //#region node_modules/shebang-command/index.js
259
+ var T = /* @__PURE__ */ l(((e, t) => {
260
+ const n = w();
261
+ t.exports = (e = "") => {
262
+ const t = e.match(n);
263
+ if (!t) return null;
264
+ const [r, i] = t[0].replace(/#! ?/, "").split(" ");
265
+ const a = r.split("/").pop();
266
+ if (a === "env") return i;
267
+ return i ? `${a} ${i}` : a;
268
+ };
269
+ }));
270
+ //#endregion
271
+ //#region node_modules/cross-spawn/lib/util/readShebang.js
272
+ var E = /* @__PURE__ */ l(((e, t) => {
273
+ const n = u("fs");
274
+ const r = T();
275
+ function i(e) {
276
+ const t = 150;
277
+ const i = Buffer.alloc(t);
278
+ let a;
279
+ try {
280
+ a = n.openSync(e, "r");
281
+ n.readSync(a, i, 0, t, 0);
282
+ n.closeSync(a);
283
+ } catch (e) {}
284
+ return r(i.toString());
285
+ }
286
+ t.exports = i;
287
+ }));
288
+ //#endregion
289
+ //#region node_modules/cross-spawn/lib/parse.js
290
+ var D = /* @__PURE__ */ l(((e, t) => {
291
+ const n = u("path");
292
+ const r = S();
293
+ const i = C();
294
+ const a = E();
295
+ const o = process.platform === "win32";
296
+ const s = /\.(?:com|exe)$/i;
297
+ const c = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
298
+ function l(e) {
299
+ e.file = r(e);
300
+ const t = e.file && a(e.file);
301
+ if (t) {
302
+ e.args.unshift(e.file);
303
+ e.command = t;
304
+ return r(e);
305
+ }
306
+ return e.file;
307
+ }
308
+ function d(e) {
309
+ if (!o) return e;
310
+ const t = l(e);
311
+ const r = !s.test(t);
312
+ if (e.options.forceShell || r) {
313
+ const r = c.test(t);
314
+ e.command = n.normalize(e.command);
315
+ e.command = i.command(e.command);
316
+ e.args = e.args.map((e) => i.argument(e, r));
317
+ e.args = [
318
+ "/d",
319
+ "/s",
320
+ "/c",
321
+ `"${[e.command].concat(e.args).join(" ")}"`
322
+ ];
323
+ e.command = process.env.comspec || "cmd.exe";
324
+ e.options.windowsVerbatimArguments = true;
325
+ }
326
+ return e;
327
+ }
328
+ function f(e, t, n) {
329
+ if (t && !Array.isArray(t)) {
330
+ n = t;
331
+ t = null;
332
+ }
333
+ t = t ? t.slice(0) : [];
334
+ n = Object.assign({}, n);
335
+ const r = {
336
+ command: e,
337
+ args: t,
338
+ options: n,
339
+ file: void 0,
340
+ original: {
341
+ command: e,
342
+ args: t
343
+ }
344
+ };
345
+ return n.shell ? r : d(r);
346
+ }
347
+ t.exports = f;
348
+ }));
349
+ //#endregion
350
+ //#region node_modules/cross-spawn/lib/enoent.js
351
+ var O = /* @__PURE__ */ l(((e, t) => {
352
+ const n = process.platform === "win32";
353
+ function r(e, t) {
354
+ return Object.assign(/* @__PURE__ */ new Error(`${t} ${e.command} ENOENT`), {
355
+ code: "ENOENT",
356
+ errno: "ENOENT",
357
+ syscall: `${t} ${e.command}`,
358
+ path: e.command,
359
+ spawnargs: e.args
360
+ });
361
+ }
362
+ function i(e, t) {
363
+ if (!n) return;
364
+ const r = e.emit;
365
+ e.emit = function(n, i) {
366
+ if (n === "exit") {
367
+ const n = a(i, t);
368
+ if (n) return r.call(e, "error", n);
369
+ }
370
+ return r.apply(e, arguments);
371
+ };
372
+ }
373
+ function a(e, t) {
374
+ if (n && e === 1 && !t.file) return r(t.original, "spawn");
375
+ return null;
376
+ }
377
+ function o(e, t) {
378
+ if (n && e === 1 && !t.file) return r(t.original, "spawnSync");
379
+ return null;
380
+ }
381
+ t.exports = {
382
+ hookChildProcess: i,
383
+ verifyENOENT: a,
384
+ verifyENOENTSync: o,
385
+ notFoundError: r
386
+ };
387
+ }));
388
+ //#endregion
389
+ //#region node_modules/cross-spawn/index.js
390
+ var k = /* @__PURE__ */ l(((e, t) => {
391
+ const n = u("child_process");
392
+ const r = D();
393
+ const i = O();
394
+ function a(e, t, a) {
395
+ const o = r(e, t, a);
396
+ const s = n.spawn(o.command, o.args, o.options);
397
+ i.hookChildProcess(s, o);
398
+ return s;
399
+ }
400
+ function o(e, t, a) {
401
+ const o = r(e, t, a);
402
+ const s = n.spawnSync(o.command, o.args, o.options);
403
+ s.error = s.error || i.verifyENOENTSync(s.status, o);
404
+ return s;
405
+ }
406
+ t.exports = a;
407
+ t.exports.spawn = a;
408
+ t.exports.sync = o;
409
+ t.exports._parse = r;
410
+ t.exports._enoent = i;
411
+ }));
412
+ //#endregion
413
+ //#region src/non-zero-exit-error.ts
414
+ k();
415
+
416
+ function addCustomTab(tab, nuxt = useNuxt()) {
417
+ nuxt.hook("devtools:customTabs", async (tabs) => {
418
+ if (typeof tab === "function")
419
+ tab = await tab();
420
+ tabs.push(tab);
421
+ });
422
+ }
423
+
424
+ function extractThemeScopedCss(css, themeName) {
425
+ const selectors = [
426
+ new RegExp(`\\[data-theme=["']${themeName}["']\\][^{]*\\{([\\s\\S]*?)\\}`, "g"),
427
+ new RegExp(`\\.dark\\s+\\[data-theme=["']${themeName}["']\\][^{]*\\{([\\s\\S]*?)\\}`, "g")
428
+ ];
429
+ return selectors.flatMap((pattern) => {
430
+ return Array.from(css.matchAll(pattern)).map((match) => match[1] ?? "");
431
+ });
432
+ }
433
+ function extractCssVars(css) {
434
+ return Array.from(css.matchAll(/(--theme-[a-zA-Z0-9-_]+)\s*:/g)).map((match) => match[1]).filter((value) => Boolean(value));
435
+ }
436
+ async function checkCssVars(rootDir, themes, requiredCssVars) {
437
+ if (requiredCssVars.length === 0) {
438
+ return [];
439
+ }
440
+ const cssFiles = await fg([
441
+ ".nuxt/dist/client/**/*.css",
442
+ ".output/public/_nuxt/**/*.css"
443
+ ], {
444
+ cwd: rootDir,
445
+ absolute: true,
446
+ onlyFiles: true
447
+ });
448
+ if (cssFiles.length === 0) {
449
+ return Promise.all(themes.map(async (theme) => {
450
+ const sourceCss = await Promise.all(theme.cssFiles.map((file) => promises.readFile(resolve(file), "utf8")));
451
+ const provided = Array.from(new Set(extractCssVars(sourceCss.join("\n")))).sort();
452
+ return {
453
+ theme: theme.name,
454
+ provided,
455
+ missing: requiredCssVars.filter((variable) => !provided.includes(variable))
456
+ };
457
+ }));
458
+ }
459
+ const cssChunks = await Promise.all(cssFiles.map((file) => promises.readFile(resolve(file), "utf8")));
460
+ const joinedCss = cssChunks.join("\n");
461
+ return themes.map((theme) => {
462
+ const scopedBlocks = extractThemeScopedCss(joinedCss, theme.name).join("\n");
463
+ const provided = Array.from(new Set(extractCssVars(scopedBlocks))).sort();
464
+ const missing = requiredCssVars.filter((variable) => !provided.includes(variable));
465
+ return {
466
+ theme: theme.name,
467
+ provided,
468
+ missing
469
+ };
470
+ });
471
+ }
472
+
473
+ function parseContractNames(source) {
474
+ const match = source.match(/themeContractNames\s*=\s*\[([\s\S]*?)\]\s*as const/);
475
+ if (!match) {
476
+ return [];
477
+ }
478
+ const contractListSource = match[1] ?? "";
479
+ return Array.from(contractListSource.matchAll(/['"`]([^'"`]+)['"`]/g)).map((result) => result[1]).filter((name) => Boolean(name));
480
+ }
481
+ async function generateThemeComponentDts(contractsSourcePath, contractsImportId) {
482
+ const source = await promises.readFile(contractsSourcePath, "utf8");
483
+ const names = parseContractNames(source);
484
+ const union = names.length > 0 ? names.map((name) => `'${name}'`).join(" | ") : "never";
485
+ return `import type { DefineComponent } from 'vue'
486
+ import type { ThemeComponentContracts } from ${JSON.stringify(contractsImportId)}
487
+
488
+ declare module '#build/theme-engine.contracts.mjs' {
489
+ export const themeEngineContracts: {
490
+ entry: string
491
+ importId: string
492
+ }
493
+
494
+ export type GeneratedThemeComponentName = ${union}
495
+ export type GeneratedThemeComponentContracts = ThemeComponentContracts
496
+ export type ThemeComponentDiscriminatedProps = {
497
+ [K in keyof ThemeComponentContracts]: { name: K } & ThemeComponentContracts[K]
498
+ }[keyof ThemeComponentContracts]
499
+ }
500
+
501
+ type ThemeComponentDiscriminatedProps = import('#build/theme-engine.contracts.mjs').ThemeComponentDiscriminatedProps
502
+ declare const ThemeComponent: DefineComponent<ThemeComponentDiscriminatedProps>
503
+
504
+ declare module 'vue' {
505
+ export interface GlobalComponents {
506
+ ThemeComponent: typeof ThemeComponent
507
+ }
508
+ }
509
+
510
+ export {}
511
+ `;
512
+ }
513
+
514
+ const COMPONENT_GLOB = "**/*.{vue,js,ts,tsx}";
515
+ function toPascalCase(segment) {
516
+ return segment.replace(/\.[^.]+$/, "").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
517
+ }
518
+ function toComponentName(relativePath) {
519
+ return relativePath.split(/[\\/]/).map((segment) => toPascalCase(segment.replace(extname(segment), ""))).join("");
520
+ }
521
+ function toThemePrefix(themeName) {
522
+ return themeName.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
523
+ }
524
+ function toImportPath(buildDir, filePath) {
525
+ const relativePath = relative(buildDir, filePath).replace(/\\/g, "/");
526
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
527
+ }
528
+ async function scanThemeComponentSources(theme) {
529
+ try {
530
+ await promises.access(theme.componentsDir);
531
+ } catch {
532
+ return [];
533
+ }
534
+ const files = await fg(COMPONENT_GLOB, {
535
+ cwd: theme.componentsDir,
536
+ absolute: true,
537
+ onlyFiles: true
538
+ });
539
+ return files.map((filePath) => {
540
+ const relativePath = relative(theme.componentsDir, filePath);
541
+ return {
542
+ logicalName: toComponentName(relativePath),
543
+ filePath,
544
+ relativePath,
545
+ ownerTheme: theme.name
546
+ };
547
+ });
548
+ }
549
+ async function generateThemeRegistry(themes, buildDir) {
550
+ new Map(themes.map((theme) => [theme.name, theme]));
551
+ const componentSourcesByTheme = /* @__PURE__ */ new Map();
552
+ for (const theme of themes) {
553
+ const sources = await scanThemeComponentSources(theme);
554
+ componentSourcesByTheme.set(
555
+ theme.name,
556
+ new Map(sources.map((source) => [source.logicalName, source]))
557
+ );
558
+ }
559
+ const registry = {
560
+ themes: {},
561
+ uniqueAliases: []
562
+ };
563
+ const seenAliases = /* @__PURE__ */ new Set();
564
+ for (const theme of themes) {
565
+ const entries = {};
566
+ for (const themeName of theme.inheritanceChain) {
567
+ const sourceMap = componentSourcesByTheme.get(themeName);
568
+ if (!sourceMap) {
569
+ continue;
570
+ }
571
+ for (const [logicalName, source] of sourceMap.entries()) {
572
+ if (entries[logicalName]) {
573
+ continue;
574
+ }
575
+ const entry = {
576
+ logicalName,
577
+ alias: `${toThemePrefix(theme.name)}${logicalName}`,
578
+ theme: theme.name,
579
+ sourceTheme: source.ownerTheme,
580
+ filePath: source.filePath,
581
+ importPath: toImportPath(buildDir, source.filePath)
582
+ };
583
+ entries[logicalName] = entry;
584
+ if (!seenAliases.has(entry.alias)) {
585
+ seenAliases.add(entry.alias);
586
+ registry.uniqueAliases.push(entry);
587
+ }
588
+ }
589
+ }
590
+ registry.themes[theme.name] = entries;
591
+ }
592
+ registry.uniqueAliases.sort((left, right) => left.alias.localeCompare(right.alias));
593
+ return registry;
594
+ }
595
+
596
+ const themeJsonSchema = z.object({
597
+ name: z.string().min(1).regex(/^[a-z0-9-]+$/),
598
+ extends: z.string().min(1).regex(/^[a-z0-9-]+$/).optional(),
599
+ label: z.string().min(1).optional(),
600
+ description: z.string().min(1).optional()
601
+ });
602
+ async function fileExists(path) {
603
+ try {
604
+ await promises.access(path);
605
+ return true;
606
+ } catch {
607
+ return false;
608
+ }
609
+ }
610
+ function detectCycles(themesByName) {
611
+ const visited = /* @__PURE__ */ new Set();
612
+ const visiting = /* @__PURE__ */ new Set();
613
+ const visit = (themeName) => {
614
+ if (visited.has(themeName)) {
615
+ return;
616
+ }
617
+ if (visiting.has(themeName)) {
618
+ throw new Error(`Detected circular theme inheritance involving "${themeName}".`);
619
+ }
620
+ visiting.add(themeName);
621
+ const theme = themesByName.get(themeName);
622
+ if (!theme) {
623
+ throw new Error(`Theme "${themeName}" is missing from the resolved theme map.`);
624
+ }
625
+ if (theme.extends) {
626
+ if (!themesByName.has(theme.extends)) {
627
+ throw new Error(`Theme "${themeName}" extends missing parent theme "${theme.extends}".`);
628
+ }
629
+ visit(theme.extends);
630
+ }
631
+ visiting.delete(themeName);
632
+ visited.add(themeName);
633
+ };
634
+ for (const themeName of themesByName.keys()) {
635
+ visit(themeName);
636
+ }
637
+ }
638
+ function resolveInheritanceChain(themeName, themesByName) {
639
+ const chain = [];
640
+ let cursor = themesByName.get(themeName);
641
+ while (cursor) {
642
+ chain.push(cursor.name);
643
+ cursor = cursor.extends ? themesByName.get(cursor.extends) : void 0;
644
+ }
645
+ return chain;
646
+ }
647
+ async function loadThemeDefinitions(themesDir) {
648
+ const themeJsonFiles = await fg("*/theme.json", {
649
+ cwd: themesDir,
650
+ absolute: true,
651
+ onlyFiles: true
652
+ });
653
+ const themes = await Promise.all(themeJsonFiles.map(async (themeJsonPath) => {
654
+ const raw = await promises.readFile(themeJsonPath, "utf8");
655
+ const json = themeJsonSchema.parse(JSON.parse(raw));
656
+ const themePath = dirname(themeJsonPath);
657
+ const componentsDir = join(themePath, "app/components");
658
+ const assetsDir = join(themePath, "app/assets");
659
+ const cssFiles = await fileExists(assetsDir) ? await fg("**/*.{css,scss,sass,less,pcss}", {
660
+ cwd: assetsDir,
661
+ absolute: true,
662
+ onlyFiles: true
663
+ }) : [];
664
+ return {
665
+ name: json.name,
666
+ label: json.label ?? json.name,
667
+ description: json.description,
668
+ extends: json.extends,
669
+ path: themePath,
670
+ themeJsonPath,
671
+ componentsDir,
672
+ assetsDir,
673
+ cssFiles,
674
+ inheritanceChain: []
675
+ };
676
+ }));
677
+ const themesByName = new Map(themes.map((theme) => [theme.name, theme]));
678
+ detectCycles(themesByName);
679
+ const resolvedThemes = await Promise.all(themes.map(async (theme) => {
680
+ if (!await fileExists(theme.componentsDir)) {
681
+ return {
682
+ ...theme,
683
+ inheritanceChain: resolveInheritanceChain(theme.name, themesByName)
684
+ };
685
+ }
686
+ return {
687
+ ...theme,
688
+ inheritanceChain: resolveInheritanceChain(theme.name, themesByName)
689
+ };
690
+ }));
691
+ return resolvedThemes.sort((left, right) => left.name.localeCompare(right.name));
692
+ }
693
+
694
+ const DEFAULTS = {
695
+ themesDir: "themes",
696
+ defaultTheme: "base",
697
+ cookieKey: "theme-pref",
698
+ lazyLoadThemes: false,
699
+ requiredCssVars: [],
700
+ contractsEntry: "@tixxin/theme-contracts",
701
+ contractsImportId: "@tixxin/theme-contracts"
702
+ };
703
+ const require$1 = createRequire(import.meta.url);
704
+ async function pathExists(path) {
705
+ try {
706
+ await promises.access(path);
707
+ return true;
708
+ } catch {
709
+ return false;
710
+ }
711
+ }
712
+ function isLocalSpecifier(specifier) {
713
+ return specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("~") || /^[A-Za-z]:[\\/]/.test(specifier);
714
+ }
715
+ async function resolveContractsEntry(entry, importId, rootDir, moduleRoot, aliases) {
716
+ const internalDefaultEntry = resolve(moduleRoot, "packages/theme-contracts/src/index.ts");
717
+ if ((entry === "@tixxin/theme-contracts" || entry === "packages/theme-contracts/src/index.ts") && await pathExists(internalDefaultEntry)) {
718
+ return internalDefaultEntry;
719
+ }
720
+ const aliasTarget = aliases[entry] ?? aliases[importId];
721
+ if (typeof aliasTarget === "string") {
722
+ return isLocalSpecifier(aliasTarget) ? resolve(rootDir, aliasTarget) : aliasTarget;
723
+ }
724
+ if (isLocalSpecifier(entry)) {
725
+ return resolve(rootDir, entry);
726
+ }
727
+ return require$1.resolve(entry, {
728
+ paths: [rootDir, moduleRoot]
729
+ });
730
+ }
731
+ function serializeRegistry(registry) {
732
+ const registryShape = Object.fromEntries(
733
+ Object.entries(registry.themes).map(([themeName, entries]) => {
734
+ return [themeName, Object.fromEntries(
735
+ Object.entries(entries).map(([logicalName, entry]) => {
736
+ return [logicalName, {
737
+ alias: entry.alias,
738
+ sourceTheme: entry.sourceTheme
739
+ }];
740
+ })
741
+ )];
742
+ })
743
+ );
744
+ const loaderEntries = registry.uniqueAliases.map((entry, index) => ({
745
+ constName: `loader_${index}`,
746
+ entry
747
+ }));
748
+ const loaderDefinitions = loaderEntries.map((item) => `const ${item.constName} = () => import(${JSON.stringify(item.entry.importPath)})`).join("\n");
749
+ const loaderLookup = new Map(loaderEntries.map((item) => [`${item.entry.theme}:${item.entry.logicalName}`, item.constName]));
750
+ const loaderObject = Object.entries(registry.themes).map(([themeName, entries]) => {
751
+ const entrySource = Object.keys(entries).map((logicalName) => {
752
+ const loaderName = loaderLookup.get(`${themeName}:${logicalName}`);
753
+ return `${JSON.stringify(logicalName)}: ${loaderName}`;
754
+ }).join(",\n ");
755
+ return `${JSON.stringify(themeName)}: {
756
+ ${entrySource}
757
+ }`;
758
+ }).join(",\n ");
759
+ return `${loaderDefinitions}
760
+
761
+ export const themeComponentRegistry = ${JSON.stringify(registryShape, null, 2)}
762
+
763
+ export const themeComponentLoaders = {
764
+ ${loaderObject}
765
+ }
766
+ `;
767
+ }
768
+ function serializeOptions(options, themes) {
769
+ const serializedThemes = themes.map((theme) => ({
770
+ name: theme.name,
771
+ label: theme.label,
772
+ extends: theme.extends ?? null,
773
+ inheritanceChain: theme.inheritanceChain
774
+ }));
775
+ return `export const themeEngineOptions = ${JSON.stringify(options, null, 2)}
776
+ export const themeEngineThemeNames = ${JSON.stringify(themes.map((theme) => theme.name), null, 2)}
777
+ export const themeEngineThemes = ${JSON.stringify(serializedThemes, null, 2)}
778
+ `;
779
+ }
780
+ function serializeCssReport(report) {
781
+ return `export const themeCssVariableReport = ${JSON.stringify(report, null, 2)}
782
+ `;
783
+ }
784
+ function serializeContracts(entry, importId) {
785
+ return `export const themeEngineContracts = ${JSON.stringify({
786
+ entry,
787
+ importId
788
+ }, null, 2)}
789
+ `;
790
+ }
791
+ const module$1 = defineNuxtModule({
792
+ meta: {
793
+ name: "@tixxin/nuxt-theme-engine",
794
+ configKey: "themeEngine",
795
+ compatibility: {
796
+ nuxt: ">=4.0.0"
797
+ }
798
+ },
799
+ defaults: DEFAULTS,
800
+ async setup(moduleOptions, nuxt) {
801
+ const resolver = createResolver(import.meta.url);
802
+ const moduleRoot = resolver.resolve("..");
803
+ const options = {
804
+ ...DEFAULTS,
805
+ ...moduleOptions
806
+ };
807
+ const themesDir = resolve(nuxt.options.rootDir, options.themesDir);
808
+ const contractsImportId = options.contractsImportId;
809
+ const contractsEntry = await resolveContractsEntry(
810
+ options.contractsEntry,
811
+ contractsImportId,
812
+ nuxt.options.rootDir,
813
+ moduleRoot,
814
+ nuxt.options.alias
815
+ );
816
+ const themes = await loadThemeDefinitions(themesDir);
817
+ const defaultTheme = themes.some((theme) => theme.name === options.defaultTheme) ? options.defaultTheme : themes[0]?.name ?? options.defaultTheme;
818
+ const resolvedOptions = {
819
+ ...options,
820
+ contractsEntry,
821
+ contractsImportId,
822
+ defaultTheme,
823
+ lazyLoadThemes: options.lazyLoadThemes && !nuxt.options.dev
824
+ };
825
+ const registry = await generateThemeRegistry(themes, nuxt.options.buildDir);
826
+ if (!nuxt.options.alias[contractsImportId]) {
827
+ nuxt.options.alias[contractsImportId] = contractsEntry;
828
+ }
829
+ addImportsDir(resolver.resolve("./runtime/composables"));
830
+ addPlugin(resolver.resolve("./runtime/plugins/theme-sync.client"));
831
+ addComponent({
832
+ name: "ThemeComponent",
833
+ filePath: resolver.resolve("./runtime/components/ThemeComponent.vue")
834
+ });
835
+ if (!resolvedOptions.lazyLoadThemes) {
836
+ for (const entry of registry.uniqueAliases) {
837
+ addComponent({
838
+ name: entry.alias,
839
+ filePath: entry.filePath
840
+ });
841
+ }
842
+ }
843
+ for (const theme of themes) {
844
+ for (const cssFile of theme.cssFiles) {
845
+ if (!nuxt.options.css.includes(cssFile)) {
846
+ nuxt.options.css.push(cssFile);
847
+ }
848
+ }
849
+ }
850
+ addTemplate({
851
+ filename: "theme-engine.options.mjs",
852
+ getContents: () => serializeOptions(resolvedOptions, themes)
853
+ });
854
+ addTemplate({
855
+ filename: "theme-engine.registry.mjs",
856
+ getContents: () => serializeRegistry(registry)
857
+ });
858
+ addTemplate({
859
+ filename: "theme-engine.contracts.mjs",
860
+ getContents: () => serializeContracts(contractsEntry, contractsImportId)
861
+ });
862
+ const cssReportTemplate = addTemplate({
863
+ filename: "theme-engine.css-report.mjs",
864
+ getContents: () => serializeCssReport([])
865
+ });
866
+ addTypeTemplate({
867
+ filename: "types/theme-components.d.ts",
868
+ getContents: () => generateThemeComponentDts(contractsEntry, contractsImportId)
869
+ });
870
+ extendPages((pages) => {
871
+ pages.push({
872
+ name: "theme-engine-devtools",
873
+ path: "/_theme-engine-devtools",
874
+ file: resolver.resolve("./runtime/devtools/page.vue")
875
+ });
876
+ });
877
+ addCustomTab({
878
+ name: "theme-engine",
879
+ title: "Theme Engine",
880
+ icon: "carbon:paint-brush",
881
+ category: "modules",
882
+ view: {
883
+ type: "iframe",
884
+ src: "/_theme-engine-devtools"
885
+ }
886
+ }, nuxt);
887
+ nuxt.hook("close", async () => {
888
+ const report = await checkCssVars(nuxt.options.rootDir, themes, resolvedOptions.requiredCssVars);
889
+ await promises.writeFile(cssReportTemplate.dst, serializeCssReport(report), "utf8");
890
+ for (const item of report.filter((entry) => entry.missing.length > 0)) {
891
+ logger.warn(`Theme "${item.theme}" is missing required CSS variables: ${item.missing.join(", ")}`);
892
+ }
893
+ });
894
+ }
895
+ });
896
+
897
+ export { module$1 as default };