@maz-ui/mcp 5.1.0-beta.0 → 5.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.mjs +3 -1
- package/dist/index.d.mts +11 -6
- package/dist/index.d.ts +11 -6
- package/dist/index.mjs +3 -1
- package/dist/shared/{mcp.DzqVbCaL.mjs → mcp.DseFuKdl.mjs} +696 -74
- package/docs/README.md +37 -37
- package/docs/agents/agents-md.md +4 -4
- package/docs/agents/claude-rule.md +4 -4
- package/docs/components/maz-accordion.md +2 -0
- package/docs/components/maz-btn-group.md +1 -0
- package/docs/components/maz-btn.md +3 -1
- package/docs/components/maz-card-spotlight.md +2 -2
- package/docs/components/maz-circular-progress-bar.md +2 -2
- package/docs/components/maz-drawer.md +3 -2
- package/docs/components/maz-fullscreen-loader.md +2 -2
- package/docs/components/maz-loading-bar.md +2 -2
- package/docs/components/maz-pagination.md +2 -2
- package/docs/components/maz-radio.md +2 -2
- package/docs/components/maz-sidebar-content.md +2 -2
- package/docs/components/maz-sidebar-footer.md +2 -2
- package/docs/components/maz-sidebar-group.md +2 -2
- package/docs/components/maz-sidebar-header.md +2 -2
- package/docs/components/maz-sidebar-menu-button.md +2 -2
- package/docs/components/maz-sidebar-menu-item.md +2 -2
- package/docs/components/maz-sidebar-menu-sub.md +2 -2
- package/docs/components/maz-sidebar-menu.md +2 -2
- package/docs/components/maz-sidebar-separator.md +2 -2
- package/docs/components/maz-sidebar-trigger.md +2 -2
- package/docs/components/maz-spinner.md +2 -2
- package/docs/components/maz-stepper.md +5 -0
- package/docs/components/maz-table-cell.md +2 -2
- package/docs/components/maz-table-row.md +2 -2
- package/docs/components/maz-table-title.md +2 -2
- package/docs/components/maz-table-virtualizer.md +2 -2
- package/docs/components/maz-tabs-bar.md +2 -2
- package/docs/components/maz-tabs-content-item.md +2 -2
- package/docs/components/maz-tabs-content.md +2 -2
- package/docs/components/maz-ui-provider.md +2 -2
- package/docs/composables/use-aos.md +2 -2
- package/docs/composables/use-breakpoints.md +2 -2
- package/docs/composables/use-toast.md +2 -2
- package/docs/composables/use-user-visibility.md +4 -4
- package/docs/composables/use-wait.md +2 -2
- package/docs/directives/fullscreen-img.md +4 -4
- package/docs/ecosystem/icons/icon-set.md +4 -4
- package/docs/ecosystem/icons/index.md +4 -4
- package/docs/ecosystem/mcp.md +1 -1
- package/docs/ecosystem/nuxt.md +5 -1
- package/docs/ecosystem/translations.md +2 -2
- package/docs/ecosystem/utils/country-code-to-unicode-flag.md +2 -2
- package/docs/guide/ai-agents.md +6 -4
- package/docs/guide/tailwind.md +25 -1
- package/docs/manifest.json +1 -1
- package/docs/reference/tailwind-tokens.md +1 -1
- package/docs/rules.md +5 -3
- package/package.json +4 -2
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { join, dirname, resolve, relative, isAbsolute } from 'node:path';
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
|
-
import {
|
|
4
|
+
import { findProjectPackage, readPackageVersion, AGENT_TARGETS, detectAgentTargets, setupAgents } from '@maz-ui/node';
|
|
5
5
|
import { getErrorMessage } from '@maz-ui/utils/helpers/getErrorMessage';
|
|
6
|
-
import {
|
|
6
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import MiniSearch from 'minisearch';
|
|
10
10
|
import { NodeTypes, parse } from '@vue/compiler-dom';
|
|
11
|
-
import { glob } from 'tinyglobby';
|
|
11
|
+
import { glob, globSync } from 'tinyglobby';
|
|
12
12
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
13
13
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
+
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import { parse as parse$1 } from '@babel/parser';
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
function readOwnVersion(from = dirname(fileURLToPath(import.meta.url))) {
|
|
18
|
+
let directory = from;
|
|
19
|
+
for (; ; ) {
|
|
20
|
+
const file = join(directory, "package.json");
|
|
21
|
+
if (existsSync(file)) {
|
|
22
|
+
const { name, version } = JSON.parse(readFileSync(file, "utf8"));
|
|
23
|
+
if (name === "@maz-ui/mcp" && version)
|
|
24
|
+
return version;
|
|
25
|
+
}
|
|
26
|
+
const parent = dirname(directory);
|
|
27
|
+
if (parent === directory)
|
|
28
|
+
return "0.0.0";
|
|
29
|
+
directory = parent;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
16
32
|
|
|
17
33
|
const SUPPORTED_SCHEMA_VERSION = 1;
|
|
18
34
|
const docCategories = ["guide", "component", "composable", "directive", "plugin", "ecosystem", "util", "node", "reference"];
|
|
@@ -93,9 +109,16 @@ const manifestSchema = z.object({
|
|
|
93
109
|
});
|
|
94
110
|
|
|
95
111
|
class Knowledge {
|
|
96
|
-
|
|
112
|
+
/**
|
|
113
|
+
* @param manifest manifest of the docs bundle
|
|
114
|
+
* @param source where the bundle was found
|
|
115
|
+
* @param documentedVersion version of the documented maz-ui, read from a package.json at runtime when possible: the
|
|
116
|
+
* docs are generated before the release bumps the version, so `manifest.version` can be the previous one
|
|
117
|
+
*/
|
|
118
|
+
constructor(manifest, source, documentedVersion = manifest.version) {
|
|
97
119
|
this.manifest = manifest;
|
|
98
120
|
this.source = source;
|
|
121
|
+
this.documentedVersion = documentedVersion;
|
|
99
122
|
this.docsById = new Map(manifest.docs.map((doc) => [doc.id, doc]));
|
|
100
123
|
this.componentsByName = new Map(manifest.components.map((component) => [component.name, component]));
|
|
101
124
|
this.iconNames = new Set(manifest.icons.names);
|
|
@@ -105,7 +128,7 @@ class Knowledge {
|
|
|
105
128
|
iconNames;
|
|
106
129
|
cache = /* @__PURE__ */ new Map();
|
|
107
130
|
get version() {
|
|
108
|
-
return this.
|
|
131
|
+
return this.documentedVersion;
|
|
109
132
|
}
|
|
110
133
|
get docs() {
|
|
111
134
|
return this.manifest.docs;
|
|
@@ -139,21 +162,8 @@ function readManifest(directory) {
|
|
|
139
162
|
return parsed.success ? parsed.data : void 0;
|
|
140
163
|
}
|
|
141
164
|
function findProjectMazUi(cwd) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
const root = join(cwd, folder);
|
|
145
|
-
if (existsSync(root)) {
|
|
146
|
-
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
147
|
-
if (entry.isDirectory())
|
|
148
|
-
bases.push(join(root, entry.name));
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
const directory = bases.map((base) => join(base, "node_modules", "maz-ui")).find((candidate) => existsSync(join(candidate, "package.json"))) ?? findInstalledPackage(cwd, "maz-ui");
|
|
153
|
-
if (!directory)
|
|
154
|
-
return void 0;
|
|
155
|
-
const { version } = JSON.parse(readFileSync(join(directory, "package.json"), "utf8"));
|
|
156
|
-
return { directory, version };
|
|
165
|
+
const directory = findProjectPackage(cwd, "maz-ui");
|
|
166
|
+
return directory ? { directory, version: readPackageVersion(directory) ?? "0.0.0" } : void 0;
|
|
157
167
|
}
|
|
158
168
|
function loadKnowledge(options = {}) {
|
|
159
169
|
const explicit = options.docsDir ?? process.env.MAZ_UI_DOCS_DIR;
|
|
@@ -161,20 +171,20 @@ function loadKnowledge(options = {}) {
|
|
|
161
171
|
const manifest2 = readManifest(explicit);
|
|
162
172
|
if (!manifest2)
|
|
163
173
|
throw new Error(`No valid maz-ui docs bundle (manifest.json) in ${explicit}`);
|
|
164
|
-
return new Knowledge(manifest2, { kind: "custom", directory: explicit });
|
|
174
|
+
return new Knowledge(manifest2, { kind: "custom", directory: explicit }, readPackageVersion(dirname(explicit)) ?? manifest2.version);
|
|
165
175
|
}
|
|
166
176
|
const project = findProjectMazUi(options.cwd ?? process.cwd());
|
|
167
177
|
if (project) {
|
|
168
178
|
const directory = join(project.directory, "docs");
|
|
169
179
|
const manifest2 = readManifest(directory);
|
|
170
180
|
if (manifest2)
|
|
171
|
-
return new Knowledge(manifest2, { kind: "project", directory, projectVersion: project.version });
|
|
181
|
+
return new Knowledge(manifest2, { kind: "project", directory, projectVersion: project.version }, project.version);
|
|
172
182
|
}
|
|
173
183
|
const bundledDir = options.bundledDir ?? defaultBundledDir();
|
|
174
184
|
const manifest = readManifest(bundledDir);
|
|
175
185
|
if (!manifest)
|
|
176
186
|
throw new Error(`The docs bundle of @maz-ui/mcp is missing or invalid (${bundledDir}). Reinstall the package.`);
|
|
177
|
-
return new Knowledge(manifest, { kind: "bundled", directory: bundledDir, projectVersion: project?.version });
|
|
187
|
+
return new Knowledge(manifest, { kind: "bundled", directory: bundledDir, projectVersion: project?.version }, options.bundledVersion ?? readOwnVersion());
|
|
178
188
|
}
|
|
179
189
|
|
|
180
190
|
const CATEGORY_PRIORITY = ["component", "composable", "directive", "plugin", "util", "guide", "ecosystem", "reference", "node"];
|
|
@@ -418,6 +428,7 @@ function analyze(text) {
|
|
|
418
428
|
|
|
419
429
|
const FIELD_BOOSTS = {
|
|
420
430
|
name: 8,
|
|
431
|
+
symbols: 3,
|
|
421
432
|
aliases: 5,
|
|
422
433
|
description: 2.5,
|
|
423
434
|
headings: 1.5,
|
|
@@ -436,8 +447,19 @@ const CATEGORY_HINTS = {
|
|
|
436
447
|
function: "util",
|
|
437
448
|
guide: "guide"
|
|
438
449
|
};
|
|
450
|
+
const META_DOCS = /* @__PURE__ */ new Map([
|
|
451
|
+
["guide/ai-agents", /\b(?:ai|agents?|llms?|claude|cursor|copilot|codex|mcp)\b/i],
|
|
452
|
+
["ecosystem/mcp", /\b(?:ai|agents?|llms?|claude|cursor|copilot|codex|mcp)\b/i],
|
|
453
|
+
["ecosystem/eslint-config", /\b(?:eslint|lint|linter|linting)\b/i],
|
|
454
|
+
["ecosystem/stylelint-config", /\b(?:stylelint|lint|linter|linting)\b/i],
|
|
455
|
+
["guide/migration-v5", /\b(?:migrat\w*|upgrad\w*|v4|v5|breaking)\b/i]
|
|
456
|
+
]);
|
|
457
|
+
const META_DOC_FACTOR = 0.3;
|
|
458
|
+
const LOW_VALUE_TERMS = new Set(["add", "show", "display", "delete", "remove", "update", "set", "open", "close", "user", "users", "page", "item", "data", "value", "list", "text", "click", "between", "before", "after"].map((term) => processTerm(term) ?? term));
|
|
459
|
+
const LOW_VALUE_FACTOR = 0.4;
|
|
460
|
+
const LOW_VALUE_ONLY_FACTOR = 0.25;
|
|
439
461
|
const EXACT_MATCH_BOOST = 4;
|
|
440
|
-
const EXACT_ALIAS_BOOST =
|
|
462
|
+
const EXACT_ALIAS_BOOST = 4;
|
|
441
463
|
const CATEGORY_HINT_BOOST = 1.4;
|
|
442
464
|
function normalizedSynonyms() {
|
|
443
465
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -473,7 +495,8 @@ class DocSearch {
|
|
|
473
495
|
].join(" ") : "";
|
|
474
496
|
return {
|
|
475
497
|
id: doc.id,
|
|
476
|
-
name: [doc.title,
|
|
498
|
+
name: [doc.title, doc.slug].join(" "),
|
|
499
|
+
symbols: doc.symbols.join(" "),
|
|
477
500
|
aliases: doc.aliases.join(" . "),
|
|
478
501
|
description: doc.description,
|
|
479
502
|
headings: doc.headings.join(" . "),
|
|
@@ -497,10 +520,11 @@ class DocSearch {
|
|
|
497
520
|
const expansion = [...new Set(terms.flatMap((term) => this.synonyms.get(term) ?? []))].filter((term) => !terms.includes(term));
|
|
498
521
|
const exact = this.exactMatch(query);
|
|
499
522
|
const normalizedQuery = query.trim().toLowerCase();
|
|
523
|
+
const hasValuableTerm = terms.some((term) => !LOW_VALUE_TERMS.has(term));
|
|
500
524
|
const results = this.index.search({
|
|
501
525
|
combineWith: "OR",
|
|
502
526
|
queries: [
|
|
503
|
-
{ queries: [terms.join(" ")], combineWith: "OR" },
|
|
527
|
+
{ queries: [terms.join(" ")], combineWith: "OR", boostTerm: (term) => LOW_VALUE_TERMS.has(term) ? LOW_VALUE_FACTOR : 1 },
|
|
504
528
|
...expansion.length > 0 ? [{ queries: [expansion.join(" ")], combineWith: "OR", boostTerm: () => 0.45 }] : []
|
|
505
529
|
]
|
|
506
530
|
}, {
|
|
@@ -521,6 +545,11 @@ class DocSearch {
|
|
|
521
545
|
score *= EXACT_ALIAS_BOOST;
|
|
522
546
|
if (hintedCategory && doc.category === hintedCategory)
|
|
523
547
|
score *= CATEGORY_HINT_BOOST;
|
|
548
|
+
if (hasValuableTerm && result.queryTerms.every((term) => LOW_VALUE_TERMS.has(term)))
|
|
549
|
+
score *= LOW_VALUE_ONLY_FACTOR;
|
|
550
|
+
const metaTopic = META_DOCS.get(doc.id);
|
|
551
|
+
if (metaTopic && !metaTopic.test(query))
|
|
552
|
+
score *= META_DOC_FACTOR;
|
|
524
553
|
return { doc, score, matched };
|
|
525
554
|
}).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
526
555
|
}
|
|
@@ -739,7 +768,39 @@ function themeSuggestion(utility, hue, step) {
|
|
|
739
768
|
const scale = step === "500" ? "" : `-${step}`;
|
|
740
769
|
return colors.map((color) => `\`${utility}-${color}${scale}\``).join(" or ");
|
|
741
770
|
}
|
|
742
|
-
|
|
771
|
+
const THEME_BACKGROUND = /^(?:[\w-]+:)*!?bg-(primary|secondary|accent|info|success|warning|destructive|contrast)(?:-\d{2,3})?(?:\/\d+)?!?$/;
|
|
772
|
+
const FIXED_BACKGROUND = /^(?:[\w-]+:)*!?(?:bg-(?:overlay|black|white|transparent|linear|radial|conic|gradient|none)|bg-\[|bg-\(|from-|via-|to-)/;
|
|
773
|
+
function backgroundOf(classList) {
|
|
774
|
+
for (const token of classList.split(/\s+/)) {
|
|
775
|
+
const theme = THEME_BACKGROUND.exec(token);
|
|
776
|
+
if (theme)
|
|
777
|
+
return { kind: "theme", color: theme[1] };
|
|
778
|
+
if (FIXED_BACKGROUND.test(token))
|
|
779
|
+
return { kind: "fixed" };
|
|
780
|
+
}
|
|
781
|
+
return void 0;
|
|
782
|
+
}
|
|
783
|
+
function reportBlackWhite(token, utility, color, background, position, context) {
|
|
784
|
+
if (background?.kind === "fixed" && utility !== "bg")
|
|
785
|
+
return;
|
|
786
|
+
if (background?.color && color === "white" && utility !== "bg") {
|
|
787
|
+
context.report({
|
|
788
|
+
rule: "theme-colors",
|
|
789
|
+
severity: "warning",
|
|
790
|
+
message: `\`${token}\` on \`bg-${background.color}\`: the theme gives the readable color of each background.`,
|
|
791
|
+
suggestion: `Use \`${utility}-${background.color}-foreground\`.`
|
|
792
|
+
}, position);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
context.report({
|
|
796
|
+
rule: "theme-colors",
|
|
797
|
+
severity: "warning",
|
|
798
|
+
message: `\`${token}\` is a fixed color that does not follow the theme nor dark mode.`,
|
|
799
|
+
suggestion: color === "white" ? `Use \`${utility}-surface\` (backgrounds) or the \`-foreground\` of the color behind (\`text-primary-foreground\`). Keep it over a fixed dark background (\`bg-overlay\`, image, video).` : `Use \`${utility}-foreground\` (text) or \`${utility}-contrast\`.`
|
|
800
|
+
}, position);
|
|
801
|
+
}
|
|
802
|
+
function checkClassList(value, offset, context, inherited) {
|
|
803
|
+
const background = backgroundOf(value) ?? inherited;
|
|
743
804
|
for (const match of value.matchAll(/\S+/g)) {
|
|
744
805
|
const token = match[0];
|
|
745
806
|
const position = offset + (match.index ?? 0);
|
|
@@ -765,13 +826,7 @@ function checkClassList(value, offset, context) {
|
|
|
765
826
|
}
|
|
766
827
|
const blackWhite = BLACK_WHITE_CLASS.exec(token);
|
|
767
828
|
if (blackWhite) {
|
|
768
|
-
|
|
769
|
-
context.report({
|
|
770
|
-
rule: "theme-colors",
|
|
771
|
-
severity: "warning",
|
|
772
|
-
message: `\`${token}\` is a fixed color that does not follow the theme nor dark mode.`,
|
|
773
|
-
suggestion: color === "white" ? `Use \`${utility}-surface\` (backgrounds) or the \`-foreground\` of the color behind (\`text-primary-foreground\`).` : `Use \`${utility}-foreground\` (text) or \`${utility}-contrast\`.`
|
|
774
|
-
}, position);
|
|
829
|
+
reportBlackWhite(token, blackWhite[1], blackWhite[2], background, position, context);
|
|
775
830
|
continue;
|
|
776
831
|
}
|
|
777
832
|
if (ARBITRARY_COLOR_CLASS.test(token)) {
|
|
@@ -991,30 +1046,71 @@ function reviewScript(code, offset, context) {
|
|
|
991
1046
|
}
|
|
992
1047
|
checkClassStrings(code.replaceAll(/^\s*import\s[^\n]*$/gm, (line) => " ".repeat(line.length)), offset, context);
|
|
993
1048
|
}
|
|
1049
|
+
function mazUiName(imported, source) {
|
|
1050
|
+
if (!/^(?:maz-ui|@maz-ui\/icons)(?:\/|$)/.test(source))
|
|
1051
|
+
return void 0;
|
|
1052
|
+
return imported ?? source.split("/").at(-1);
|
|
1053
|
+
}
|
|
1054
|
+
function localComponents(scripts) {
|
|
1055
|
+
const components = /* @__PURE__ */ new Map();
|
|
1056
|
+
for (const [, clause, source] of scripts.matchAll(/import\s+(?!type\s)([^'";]*?)\s+from\s+["']([^"']+)["']/g)) {
|
|
1057
|
+
const named = /\{([^}]*)\}/.exec(clause)?.[1] ?? "";
|
|
1058
|
+
const defaultName = /^\s*([A-Z]\w*)/.exec(clause.replace(/\{[^}]*\}/, ""))?.[1];
|
|
1059
|
+
if (defaultName)
|
|
1060
|
+
components.set(defaultName, mazUiName(void 0, source));
|
|
1061
|
+
for (const specifier of named.split(",")) {
|
|
1062
|
+
const [imported, local = imported] = specifier.replace(/^\s*type\s+/, "").split(/\s+as\s+/).map((part) => part.trim());
|
|
1063
|
+
if (/^[A-Z]/.test(local ?? ""))
|
|
1064
|
+
components.set(local, mazUiName(imported, source));
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return components;
|
|
1068
|
+
}
|
|
994
1069
|
|
|
995
1070
|
const COLOR_LITERAL = /#[\da-f]{3,8}\b|\b(?:rgba?|hsla?|oklch|oklab|lab|lch)\(/i;
|
|
996
1071
|
const COLOR_PROPERTIES = /^(?:color|background(?:-color)?|border(?:-[a-z]+)*-color|border(?:-(?:top|right|bottom|left|inline|block)(?:-(?:start|end))?)?|outline(?:-color)?|fill|stroke|box-shadow|text-shadow|caret-color|accent-color|text-decoration-color|column-rule-color)$/;
|
|
997
|
-
function blankComments(css) {
|
|
1072
|
+
function blankComments$1(css) {
|
|
998
1073
|
return css.replaceAll(/\/\*[\s\S]*?\*\//g, (match) => match.replaceAll(/[^\n]/g, " "));
|
|
999
1074
|
}
|
|
1075
|
+
const BLACK_OR_WHITE = /^#(?:f{3}|f{6}|f{8}|0{3}|0{6}|0{8})$/i;
|
|
1076
|
+
function keyframesRanges(css) {
|
|
1077
|
+
const ranges = [];
|
|
1078
|
+
for (const match of css.matchAll(/@keyframes[^{]*\{/g)) {
|
|
1079
|
+
const start = match.index ?? 0;
|
|
1080
|
+
let depth = 0;
|
|
1081
|
+
for (let index = start + match[0].length - 1; index < css.length; index++) {
|
|
1082
|
+
if (css[index] === "{") {
|
|
1083
|
+
depth++;
|
|
1084
|
+
} else if (css[index] === "}" && --depth === 0) {
|
|
1085
|
+
ranges.push([start, index]);
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return ranges;
|
|
1091
|
+
}
|
|
1000
1092
|
function checkStyleDeclarations(css, offset, context) {
|
|
1093
|
+
const keyframes = keyframesRanges(css);
|
|
1001
1094
|
for (const match of css.matchAll(/(^|[;{\s])([a-z-]+)\s*:\s*([^;{}]+)/g)) {
|
|
1002
1095
|
const [, prefix, property, value] = match;
|
|
1003
1096
|
if (property.startsWith("--") || !COLOR_PROPERTIES.test(property) || /var\(--/.test(value))
|
|
1004
1097
|
continue;
|
|
1005
1098
|
const color = COLOR_LITERAL.exec(value);
|
|
1006
|
-
if (color)
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1099
|
+
if (!color)
|
|
1100
|
+
continue;
|
|
1101
|
+
const index = match.index ?? 0;
|
|
1102
|
+
const inKeyframes = keyframes.some(([start, end]) => index > start && index < end);
|
|
1103
|
+
const blackOrWhite = BLACK_OR_WHITE.test(color[0]);
|
|
1104
|
+
context.report({
|
|
1105
|
+
rule: "hardcoded-color",
|
|
1106
|
+
severity: inKeyframes || blackOrWhite ? "info" : "warning",
|
|
1107
|
+
message: `\`${property}\` uses a hard-coded color (\`${color[0].replace("(", "")}\`): it ignores the theme and dark mode.`,
|
|
1108
|
+
suggestion: `Use a theme variable: \`var(--maz-primary)\`, \`var(--maz-surface)\`, \`var(--maz-foreground)\`, \`var(--maz-muted)\`, \`var(--maz-divider)\`... (\`color-mix(in oklab, var(--maz-primary) 20%, transparent)\` for tints).${blackOrWhite ? " Keep it if the element is over media (image, video) or a fixed dark background." : ""}`
|
|
1109
|
+
}, offset + index + prefix.length);
|
|
1014
1110
|
}
|
|
1015
1111
|
}
|
|
1016
1112
|
function reviewStyle(source, offset, context, { sfc = false } = {}) {
|
|
1017
|
-
const css = blankComments(source);
|
|
1113
|
+
const css = blankComments$1(source);
|
|
1018
1114
|
const applies = [...css.matchAll(/@apply\s+([^;]+);/g)];
|
|
1019
1115
|
for (const apply of applies) {
|
|
1020
1116
|
checkClassList(apply[1], offset + (apply.index ?? 0) + apply[0].indexOf(apply[1]), context);
|
|
@@ -1259,17 +1355,54 @@ function checkPropValue(prop, value, offset, component, context) {
|
|
|
1259
1355
|
}, offset);
|
|
1260
1356
|
}
|
|
1261
1357
|
}
|
|
1262
|
-
|
|
1358
|
+
const PROP_SYNONYMS = {
|
|
1359
|
+
items: ["options"],
|
|
1360
|
+
choices: ["options"],
|
|
1361
|
+
list: ["options"],
|
|
1362
|
+
data: ["options", "rows"],
|
|
1363
|
+
columns: ["headers"],
|
|
1364
|
+
value: ["modelValue"],
|
|
1365
|
+
checked: ["modelValue"],
|
|
1366
|
+
selected: ["modelValue"],
|
|
1367
|
+
open: ["modelValue"],
|
|
1368
|
+
isOpen: ["modelValue"],
|
|
1369
|
+
show: ["modelValue"],
|
|
1370
|
+
visible: ["modelValue"],
|
|
1371
|
+
active: ["modelValue"],
|
|
1372
|
+
text: ["label", "title", "content"],
|
|
1373
|
+
helperText: ["hint"],
|
|
1374
|
+
helper: ["hint"],
|
|
1375
|
+
message: ["hint"],
|
|
1376
|
+
errorMessage: ["hint", "error"],
|
|
1377
|
+
variant: ["color", "fill", "outlined"],
|
|
1378
|
+
image: ["src"],
|
|
1379
|
+
leftIcon: ["startIcon"],
|
|
1380
|
+
rightIcon: ["endIcon"],
|
|
1381
|
+
prependIcon: ["startIcon"],
|
|
1382
|
+
appendIcon: ["endIcon"]
|
|
1383
|
+
};
|
|
1384
|
+
function nameWords(name) {
|
|
1385
|
+
return toKebab(name).split("-").filter(Boolean);
|
|
1386
|
+
}
|
|
1387
|
+
function propSuggestions(name, component, { containing = true } = {}) {
|
|
1263
1388
|
const camel = toCamel(name);
|
|
1264
|
-
const
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1389
|
+
const props = component.props.map((prop) => prop.name);
|
|
1390
|
+
const synonyms = (PROP_SYNONYMS[camel] ?? []).filter((prop) => props.includes(prop));
|
|
1391
|
+
const words = nameWords(camel.replace(/^(?:is|has)(?=[A-Z])/, ""));
|
|
1392
|
+
const withWords = containing ? props.filter((prop) => words.every((word) => nameWords(prop).includes(word))).sort((a, b) => a.length - b.length) : [];
|
|
1393
|
+
const near = closest(camel, props, { max: camel.length <= 3 ? 1 : 2, limit: 2 });
|
|
1394
|
+
return [.../* @__PURE__ */ new Set([...synonyms, ...withWords, ...near])].slice(0, 3);
|
|
1395
|
+
}
|
|
1396
|
+
function reportUnknownProp(name, offset, component, context) {
|
|
1397
|
+
const passthrough = isPassthroughAttribute(name);
|
|
1398
|
+
const near = propSuggestions(name, component, { containing: !passthrough });
|
|
1399
|
+
if (passthrough && near.length === 0)
|
|
1267
1400
|
return;
|
|
1268
1401
|
context.report({
|
|
1269
1402
|
rule: "unknown-prop",
|
|
1270
1403
|
severity: "warning",
|
|
1271
|
-
message: `${component.name} has no prop \`${name}\`${
|
|
1272
|
-
suggestion: near.length > 0 ? `Did you mean ${near.map((entry) => `\`${toKebab(entry)}\``).join(" or ")}? Check \`get_component_api({ names: ["${component.name}"] })\`.` : `Check the real API with \`get_component_api({ names: ["${component.name}"] })\`.`
|
|
1404
|
+
message: `${component.name} has no prop \`${name}\`${passthrough ? "" : ": it will only be forwarded as an HTML attribute"}.`,
|
|
1405
|
+
suggestion: near.length > 0 ? `Did you mean ${near.map((entry) => entry === "modelValue" ? "`v-model`" : `\`${toKebab(entry)}\``).join(" or ")}? Check \`get_component_api({ names: ["${component.name}"] })\`.` : `Check the real API with \`get_component_api({ names: ["${component.name}"] })\`.`
|
|
1273
1406
|
}, offset);
|
|
1274
1407
|
}
|
|
1275
1408
|
function checkModel(directive, component, context) {
|
|
@@ -1320,10 +1453,16 @@ function usedSlots(element) {
|
|
|
1320
1453
|
const templates = element.children.filter((child) => isElement(child) && child.tag === "template");
|
|
1321
1454
|
return [element, ...templates].flatMap((node) => node.props.filter(isStaticSlot)).map((prop) => ({ name: directiveArg(prop) ?? "default", offset: prop.loc.start.offset }));
|
|
1322
1455
|
}
|
|
1456
|
+
function slotMatches(slot, name) {
|
|
1457
|
+
if (!slot.includes("{"))
|
|
1458
|
+
return slot === name;
|
|
1459
|
+
const [prefix, suffix] = slot.split(/\{[^}]*\}/);
|
|
1460
|
+
return name.length > prefix.length + suffix.length && name.startsWith(prefix) && name.endsWith(suffix);
|
|
1461
|
+
}
|
|
1323
1462
|
function checkSlots(element, component, context) {
|
|
1324
1463
|
const slots = component.slots.map((slot) => slot.name);
|
|
1325
1464
|
for (const { name, offset } of usedSlots(element)) {
|
|
1326
|
-
if (slots.
|
|
1465
|
+
if (slots.some((slot) => slotMatches(slot, name)) || name === "default" && slots.length === 0)
|
|
1327
1466
|
continue;
|
|
1328
1467
|
const near = closest(name, slots, { max: 3, limit: 2 });
|
|
1329
1468
|
context.report({
|
|
@@ -1371,8 +1510,26 @@ function checkMazComponent(element, component, source, context) {
|
|
|
1371
1510
|
}
|
|
1372
1511
|
checkSlots(element, component, context);
|
|
1373
1512
|
}
|
|
1513
|
+
const SVG_SHAPES = /* @__PURE__ */ new Set(["path", "circle", "rect", "polyline", "polygon", "line", "ellipse"]);
|
|
1514
|
+
const ICON_MAX_VIEWBOX = 32;
|
|
1515
|
+
function svgDescendants(element) {
|
|
1516
|
+
return element.children.filter(isElement).flatMap((child) => [child, ...svgDescendants(child)]);
|
|
1517
|
+
}
|
|
1518
|
+
function isAnimated(element) {
|
|
1519
|
+
const classes = staticAttribute(element, "class")?.value?.content ?? "";
|
|
1520
|
+
return element.tag.startsWith("animate") || /\banimate-|\[animation/.test(classes) || element.props.some((prop) => prop.name.startsWith("stroke-dash"));
|
|
1521
|
+
}
|
|
1522
|
+
function isIconLikeSvg(svg) {
|
|
1523
|
+
const descendants = svgDescendants(svg);
|
|
1524
|
+
if (!descendants.some((child) => SVG_SHAPES.has(child.tag)))
|
|
1525
|
+
return false;
|
|
1526
|
+
const viewBox = staticAttribute(svg, "viewBox")?.value?.content.trim().split(/[\s,]+/).map(Number);
|
|
1527
|
+
if (viewBox?.length === 4 && (viewBox[2] > ICON_MAX_VIEWBOX || viewBox[3] > ICON_MAX_VIEWBOX))
|
|
1528
|
+
return false;
|
|
1529
|
+
return ![svg, ...descendants].some((node) => isAnimated(node) || node !== svg && node.props.some((prop) => prop.type === NodeTypes.DIRECTIVE));
|
|
1530
|
+
}
|
|
1374
1531
|
function checkNativeElement(element, context) {
|
|
1375
|
-
if (element.tag === "svg" && element
|
|
1532
|
+
if (element.tag === "svg" && isIconLikeSvg(element)) {
|
|
1376
1533
|
context.report({
|
|
1377
1534
|
rule: "inline-svg",
|
|
1378
1535
|
severity: "warning",
|
|
@@ -1391,10 +1548,10 @@ function checkNativeElement(element, context) {
|
|
|
1391
1548
|
}, element.loc.start.offset);
|
|
1392
1549
|
}
|
|
1393
1550
|
}
|
|
1394
|
-
function checkClassBindings(element, source, context) {
|
|
1551
|
+
function checkClassBindings(element, source, context, inherited) {
|
|
1395
1552
|
for (const prop of element.props) {
|
|
1396
1553
|
if (prop.type === NodeTypes.ATTRIBUTE && prop.name === "class" && prop.value) {
|
|
1397
|
-
checkClassList(prop.value.content, valueOffset(prop, source), context);
|
|
1554
|
+
checkClassList(prop.value.content, valueOffset(prop, source), context, inherited);
|
|
1398
1555
|
} else if (prop.type === NodeTypes.ATTRIBUTE && prop.name === "style" && prop.value) {
|
|
1399
1556
|
checkStyleDeclarations(prop.value.content, valueOffset(prop, source), context);
|
|
1400
1557
|
} else if (prop.type === NodeTypes.DIRECTIVE && prop.name === "bind" && directiveArg(prop) === "class") {
|
|
@@ -1420,17 +1577,18 @@ function checkMazTag(element, mazName, source, context) {
|
|
|
1420
1577
|
suggestion: near.length > 0 ? `Did you mean ${near.map((entry) => `\`${entry}\``).join(" or ")}?` : "Use `search_docs` or `find_icons` to find the right name."
|
|
1421
1578
|
}, element.loc.start.offset);
|
|
1422
1579
|
}
|
|
1423
|
-
function walk(node, source, context) {
|
|
1580
|
+
function walk(node, source, context, background) {
|
|
1424
1581
|
for (const child of node.children) {
|
|
1425
1582
|
if (!isElement(child))
|
|
1426
1583
|
continue;
|
|
1427
|
-
const
|
|
1584
|
+
const pascal = /[A-Z-]/.test(child.tag) ? toPascal(child.tag) : void 0;
|
|
1585
|
+
const mazName = pascal && context.localComponents?.has(pascal) ? mazTagName(context.localComponents.get(pascal) ?? "") : mazTagName(child.tag);
|
|
1428
1586
|
if (mazName)
|
|
1429
1587
|
checkMazTag(child, mazName, source, context);
|
|
1430
1588
|
else
|
|
1431
1589
|
checkNativeElement(child, context);
|
|
1432
|
-
checkClassBindings(child, source, context);
|
|
1433
|
-
walk(child, source, context);
|
|
1590
|
+
checkClassBindings(child, source, context, background);
|
|
1591
|
+
walk(child, source, context, backgroundOf(staticAttribute(child, "class")?.value?.content ?? "") ?? background);
|
|
1434
1592
|
}
|
|
1435
1593
|
}
|
|
1436
1594
|
function reviewTemplate(root, source, context) {
|
|
@@ -1537,7 +1695,9 @@ function reviewCode(code, options) {
|
|
|
1537
1695
|
reviewTemplate(parse(code), code, context);
|
|
1538
1696
|
} else {
|
|
1539
1697
|
const root = parse(code, { parseMode: "sfc" });
|
|
1540
|
-
|
|
1698
|
+
const blocks = blocksOf(root);
|
|
1699
|
+
context.localComponents = localComponents(blocks.filter((block) => block.tag === "script").map((block) => blockContent(block).content).join("\n"));
|
|
1700
|
+
for (const block of blocks) {
|
|
1541
1701
|
if (block.tag === "template") {
|
|
1542
1702
|
reviewTemplate(block, code, context);
|
|
1543
1703
|
} else if (block.tag === "script") {
|
|
@@ -1742,7 +1902,7 @@ Workflow:
|
|
|
1742
1902
|
1. search_docs("<the need in plain words>") BEFORE creating a component, composable, helper, icon or style ("confirm before delete", "phone input", "debounce search", "dark mode toggle").
|
|
1743
1903
|
2. get_component_api(["MazBtn", ...]) BEFORE writing a template with Maz* components: use only documented props, values, events and slots.
|
|
1744
1904
|
3. find_icons("<meaning>") for icons: never inline SVG nor another icon library.
|
|
1745
|
-
4. get_theme_tokens() BEFORE styling: theme colors only (bg-surface, text-foreground, text-muted, border-divider, bg-primary text-primary-foreground, bg-container...), radius, shadow-elevation, breakpoints mob-*/tab-*/lap-*.
|
|
1905
|
+
4. get_theme_tokens() BEFORE styling: it reads the project theme (preset, @theme tokens, variants, utilities) and lists the maz-ui classes. Theme colors only (bg-surface, text-foreground, text-muted, border-divider, bg-primary text-primary-foreground, bg-container...), radius, shadow-elevation, breakpoints mob-*/tab-*/lap-*.
|
|
1746
1906
|
5. review_code({ paths: [...] }) on the files you changed, then fix every error and warning.
|
|
1747
1907
|
Other tools: get_doc (pages and guides: guide/tailwind, ecosystem/themes, ecosystem/translations, ecosystem/nuxt, guide/vue, guide/migration-v5...), list_docs (catalog), get_setup_guide (install in Vue or Nuxt).
|
|
1748
1908
|
|
|
@@ -2042,6 +2202,464 @@ const getSetupGuideTool = defineTool({
|
|
|
2042
2202
|
run: getSetupGuide
|
|
2043
2203
|
});
|
|
2044
2204
|
|
|
2205
|
+
function sourceOf(node, source) {
|
|
2206
|
+
return source.slice(node.start ?? 0, node.end ?? 0).replaceAll(/\s+/g, " ");
|
|
2207
|
+
}
|
|
2208
|
+
function keyOf(node) {
|
|
2209
|
+
if (node.type !== "ObjectProperty" || node.computed)
|
|
2210
|
+
return void 0;
|
|
2211
|
+
if (node.key.type === "Identifier")
|
|
2212
|
+
return node.key.name;
|
|
2213
|
+
return node.key.type === "StringLiteral" || node.key.type === "NumericLiteral" ? String(node.key.value) : void 0;
|
|
2214
|
+
}
|
|
2215
|
+
function unwrap(node) {
|
|
2216
|
+
return node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSNonNullExpression" || node.type === "ParenthesizedExpression" ? unwrap(node.expression) : node;
|
|
2217
|
+
}
|
|
2218
|
+
function literalValue(node, source) {
|
|
2219
|
+
const expression = unwrap(node);
|
|
2220
|
+
switch (expression.type) {
|
|
2221
|
+
case "StringLiteral":
|
|
2222
|
+
case "NumericLiteral":
|
|
2223
|
+
case "BooleanLiteral":
|
|
2224
|
+
return expression.value;
|
|
2225
|
+
case "NullLiteral":
|
|
2226
|
+
return null;
|
|
2227
|
+
case "TemplateLiteral":
|
|
2228
|
+
return expression.expressions.length === 0 ? expression.quasis.map((quasi) => quasi.value.cooked ?? quasi.value.raw).join("") : `<expr: ${sourceOf(expression, source)}>`;
|
|
2229
|
+
case "ArrayExpression":
|
|
2230
|
+
return expression.elements.map((element) => element && element.type !== "SpreadElement" ? literalValue(element, source) : null);
|
|
2231
|
+
case "ObjectExpression":
|
|
2232
|
+
return Object.fromEntries(expression.properties.flatMap((property) => {
|
|
2233
|
+
const key = keyOf(property);
|
|
2234
|
+
return key && property.type === "ObjectProperty" ? [[key, literalValue(property.value, source)]] : [];
|
|
2235
|
+
}));
|
|
2236
|
+
default:
|
|
2237
|
+
return `<expr: ${sourceOf(expression, source)}>`;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
function isDefinePreset(node) {
|
|
2241
|
+
return node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "definePreset";
|
|
2242
|
+
}
|
|
2243
|
+
function findPresetCalls(node, found, name) {
|
|
2244
|
+
if (isDefinePreset(node)) {
|
|
2245
|
+
found.push({ call: node, name });
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
const variableName = node.type === "VariableDeclarator" && node.id.type === "Identifier" ? node.id.name : name;
|
|
2249
|
+
for (const [key, value] of Object.entries(node)) {
|
|
2250
|
+
if (key === "loc" || key === "leadingComments" || key === "trailingComments" || key === "innerComments")
|
|
2251
|
+
continue;
|
|
2252
|
+
for (const child of Array.isArray(value) ? value : [value]) {
|
|
2253
|
+
if (child && typeof child === "object" && typeof child.type === "string")
|
|
2254
|
+
findPresetCalls(child, found, variableName);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
function readPresets(file, source) {
|
|
2259
|
+
let program;
|
|
2260
|
+
try {
|
|
2261
|
+
program = parse$1(source, { sourceType: "module", plugins: ["typescript"], errorRecovery: true }).program;
|
|
2262
|
+
} catch {
|
|
2263
|
+
return [];
|
|
2264
|
+
}
|
|
2265
|
+
const calls = [];
|
|
2266
|
+
findPresetCalls(program, calls);
|
|
2267
|
+
return calls.flatMap(({ call, name }) => {
|
|
2268
|
+
const [argument] = call.arguments;
|
|
2269
|
+
if (!argument || argument.type !== "ObjectExpression")
|
|
2270
|
+
return [];
|
|
2271
|
+
const options = literalValue(argument, source);
|
|
2272
|
+
const base = options.base;
|
|
2273
|
+
const overrides = options.overrides;
|
|
2274
|
+
return [{
|
|
2275
|
+
file,
|
|
2276
|
+
exportName: name,
|
|
2277
|
+
base: typeof base === "string" ? base.replace(/^<expr: (.*)>$/, "$1") : void 0,
|
|
2278
|
+
overrides: overrides && typeof overrides === "object" && !Array.isArray(overrides) ? overrides : {}
|
|
2279
|
+
}];
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
const BLOCK_AT_RULES = /^@(?:utility|keyframes|custom-variant)\s/;
|
|
2284
|
+
function blankComments(css, comments) {
|
|
2285
|
+
return css.replaceAll(/\/\*([\s\S]*?)\*\//g, (match, text, index) => {
|
|
2286
|
+
comments.push({ end: index + match.length, text: text.replaceAll(/\s*\n\s*\*?\s*/g, " ").trim() });
|
|
2287
|
+
return match.replaceAll(/[^\n]/g, " ");
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
function commentBefore(css, comments, start) {
|
|
2291
|
+
const comment = comments.findLast((entry) => entry.end <= start);
|
|
2292
|
+
return comment && css.slice(comment.end, start).trim() === "" ? comment.text : void 0;
|
|
2293
|
+
}
|
|
2294
|
+
function parseCss(source) {
|
|
2295
|
+
const comments = [];
|
|
2296
|
+
const css = blankComments(source, comments);
|
|
2297
|
+
const result = { declarations: [], statements: [], blocks: [] };
|
|
2298
|
+
const stack = [];
|
|
2299
|
+
let start = 0;
|
|
2300
|
+
let parens = 0;
|
|
2301
|
+
let quote;
|
|
2302
|
+
const flush = (end) => {
|
|
2303
|
+
const raw = css.slice(start, end);
|
|
2304
|
+
const statement = raw.trim();
|
|
2305
|
+
const position = start + raw.indexOf(statement);
|
|
2306
|
+
if (statement.startsWith("@")) {
|
|
2307
|
+
result.statements.push(statement.replaceAll(/\s+/g, " "));
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
2310
|
+
const declaration = /^(--[\w*-]+|[a-z-]+)\s*:([\s\S]*)$/.exec(statement);
|
|
2311
|
+
if (declaration && stack.length > 0) {
|
|
2312
|
+
result.declarations.push({
|
|
2313
|
+
property: declaration[1],
|
|
2314
|
+
value: declaration[2].trim().replaceAll(/\s+/g, " "),
|
|
2315
|
+
context: stack.map((entry) => entry.prelude),
|
|
2316
|
+
comment: commentBefore(css, comments, position)
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
};
|
|
2320
|
+
for (let index = 0; index < css.length; index++) {
|
|
2321
|
+
const char = css[index];
|
|
2322
|
+
if (quote) {
|
|
2323
|
+
if (char === quote && css[index - 1] !== "\\")
|
|
2324
|
+
quote = void 0;
|
|
2325
|
+
continue;
|
|
2326
|
+
}
|
|
2327
|
+
if (char === '"' || char === "'") {
|
|
2328
|
+
quote = char;
|
|
2329
|
+
} else if (char === "(") {
|
|
2330
|
+
parens++;
|
|
2331
|
+
} else if (char === ")") {
|
|
2332
|
+
parens = Math.max(0, parens - 1);
|
|
2333
|
+
} else if (parens > 0) {
|
|
2334
|
+
continue;
|
|
2335
|
+
} else if (char === ";") {
|
|
2336
|
+
flush(index);
|
|
2337
|
+
start = index + 1;
|
|
2338
|
+
} else if (char === "{") {
|
|
2339
|
+
stack.push({ prelude: css.slice(start, index).trim().replaceAll(/\s+/g, " "), open: index });
|
|
2340
|
+
start = index + 1;
|
|
2341
|
+
} else if (char === "}") {
|
|
2342
|
+
flush(index);
|
|
2343
|
+
start = index + 1;
|
|
2344
|
+
const block = stack.pop();
|
|
2345
|
+
if (block && BLOCK_AT_RULES.test(block.prelude))
|
|
2346
|
+
result.blocks.push({ prelude: block.prelude, body: css.slice(block.open + 1, index).trim().replaceAll(/\s+/g, " ") });
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
return result;
|
|
2350
|
+
}
|
|
2351
|
+
const NAMESPACES = [
|
|
2352
|
+
{ prefix: "--color-", topic: "colors", usage: (name) => `bg-${name} text-${name} border-${name}...` },
|
|
2353
|
+
{ prefix: "--font-weight-", topic: "typography", usage: (name) => `font-${name}` },
|
|
2354
|
+
{ prefix: "--font-", topic: "typography", usage: (name) => `font-${name}` },
|
|
2355
|
+
{ prefix: "--text-shadow-", topic: "shadows", usage: (name) => `text-shadow-${name}` },
|
|
2356
|
+
{ prefix: "--text-", topic: "typography", usage: (name) => `text-${name}` },
|
|
2357
|
+
{ prefix: "--tracking-", topic: "typography", usage: (name) => `tracking-${name}` },
|
|
2358
|
+
{ prefix: "--leading-", topic: "typography", usage: (name) => `leading-${name}` },
|
|
2359
|
+
{ prefix: "--animate-", topic: "typography", usage: (name) => `animate-${name}` },
|
|
2360
|
+
{ prefix: "--ease-", topic: "typography", usage: (name) => `ease-${name}` },
|
|
2361
|
+
{ prefix: "--duration-", topic: "typography", usage: (name) => `duration-(--duration-${name})` },
|
|
2362
|
+
{ prefix: "--radius-", topic: "radius", usage: (name) => `rounded-${name}` },
|
|
2363
|
+
{ prefix: "--inset-shadow-", topic: "shadows", usage: (name) => `inset-shadow-${name}` },
|
|
2364
|
+
{ prefix: "--drop-shadow-", topic: "shadows", usage: (name) => `drop-shadow-${name}` },
|
|
2365
|
+
{ prefix: "--shadow-", topic: "shadows", usage: (name) => `shadow-${name}` },
|
|
2366
|
+
{ prefix: "--spacing-", topic: "spacing", usage: (name) => `p-${name} gap-${name} w-${name}...` },
|
|
2367
|
+
{ prefix: "--breakpoint-", topic: "breakpoints", usage: (name) => `${name}:` },
|
|
2368
|
+
{ prefix: "--container-", topic: "layout", usage: (name) => `max-w-${name} @${name}:` },
|
|
2369
|
+
{ prefix: "--aspect-", topic: "layout", usage: (name) => `aspect-${name}` },
|
|
2370
|
+
{ prefix: "--z-", topic: "z-index", usage: (name) => `z-${name}` }
|
|
2371
|
+
];
|
|
2372
|
+
function tokenOf(variable, value, { inline = false, comment } = {}) {
|
|
2373
|
+
if (variable === "--spacing")
|
|
2374
|
+
return { variable, value, topic: "spacing", usage: "p-4 gap-2 w-8... (base step)", inline, comment };
|
|
2375
|
+
const namespace = NAMESPACES.find((entry) => variable.startsWith(entry.prefix));
|
|
2376
|
+
return namespace ? { variable, value, topic: namespace.topic, usage: namespace.usage(variable.slice(namespace.prefix.length)), inline, comment } : { variable, value, topic: "css-variables", usage: `var(${variable})`, inline, comment };
|
|
2377
|
+
}
|
|
2378
|
+
function importPath(statement) {
|
|
2379
|
+
return /^@import\s+(?:url\()?\s*["']([^"']+)["']/.exec(statement)?.[1];
|
|
2380
|
+
}
|
|
2381
|
+
function themeContext(context) {
|
|
2382
|
+
return context.find((prelude) => /^@theme\b/.test(prelude));
|
|
2383
|
+
}
|
|
2384
|
+
function readStylesheet(file, source) {
|
|
2385
|
+
const { declarations, statements, blocks } = parseCss(source);
|
|
2386
|
+
const stylesheet = { file, imports: [], tokens: [], resets: [], variants: [], utilities: [], keyframes: [], mazVariables: [] };
|
|
2387
|
+
for (const statement of statements) {
|
|
2388
|
+
const path = importPath(statement);
|
|
2389
|
+
const variant = /^@custom-variant ([\w-]+) (.+)$/.exec(statement);
|
|
2390
|
+
if (path)
|
|
2391
|
+
stylesheet.imports.push(path);
|
|
2392
|
+
else if (variant)
|
|
2393
|
+
stylesheet.variants.push({ name: variant[1], definition: variant[2] });
|
|
2394
|
+
}
|
|
2395
|
+
for (const { prelude, body } of blocks) {
|
|
2396
|
+
const [, kind, name] = /^@([\w-]+) ([\w*-]+)/.exec(prelude) ?? [];
|
|
2397
|
+
if (kind === "utility")
|
|
2398
|
+
stylesheet.utilities.push({ name, css: body });
|
|
2399
|
+
else if (kind === "keyframes")
|
|
2400
|
+
stylesheet.keyframes.push(name);
|
|
2401
|
+
else if (kind === "custom-variant")
|
|
2402
|
+
stylesheet.variants.push({ name, definition: `{ ${body} }` });
|
|
2403
|
+
}
|
|
2404
|
+
for (const { property, value, context, comment } of declarations) {
|
|
2405
|
+
const theme = themeContext(context);
|
|
2406
|
+
if (theme && property.startsWith("--")) {
|
|
2407
|
+
if (value === "initial")
|
|
2408
|
+
stylesheet.resets.push(property);
|
|
2409
|
+
else if (!/^--[\w-]+--/.test(property))
|
|
2410
|
+
stylesheet.tokens.push(tokenOf(property, value, { inline: /\binline\b/.test(theme), comment }));
|
|
2411
|
+
} else if (!theme && property.startsWith("--maz-")) {
|
|
2412
|
+
stylesheet.mazVariables.push({ variable: property, value, selector: context.filter((prelude) => !prelude.startsWith("@layer")).join(" ") });
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
return stylesheet;
|
|
2416
|
+
}
|
|
2417
|
+
function isTailwindStylesheet(source) {
|
|
2418
|
+
return /@import\s+(?:url\()?\s*["'](?:tailwindcss|maz-ui\/tailwindcss)/.test(source) || /@(?:theme|custom-variant|utility)\b/.test(source);
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
const IGNORED = [
|
|
2422
|
+
"**/node_modules/**",
|
|
2423
|
+
"**/dist/**",
|
|
2424
|
+
"**/build/**",
|
|
2425
|
+
"**/coverage/**",
|
|
2426
|
+
"**/public/**",
|
|
2427
|
+
"**/.*/**",
|
|
2428
|
+
"**/*.min.css",
|
|
2429
|
+
"**/*.d.ts",
|
|
2430
|
+
"**/*.{spec,test}.*"
|
|
2431
|
+
];
|
|
2432
|
+
const MAX_FILE_SIZE = 3e5;
|
|
2433
|
+
const PATTERNS = ["*.css", "*.ts", "*.mts", "*.cts", "*.js", "*.mjs"];
|
|
2434
|
+
const IGNORED_PATH = /(?:^|\/)(?:node_modules|dist|build|coverage|public|fixtures|__tests__|\.[^/]+)\/|\.min\.css$|\.d\.ts$|\.(?:spec|test)\.\w+$/;
|
|
2435
|
+
function projectFiles(rootDir) {
|
|
2436
|
+
try {
|
|
2437
|
+
return execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "--", ...PATTERNS], { cwd: rootDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], maxBuffer: 64 * 1024 * 1024 }).split("\n").filter((file) => file && !IGNORED_PATH.test(file));
|
|
2438
|
+
} catch {
|
|
2439
|
+
return globSync(PATTERNS.map((pattern) => `**/${pattern}`), { cwd: rootDir, ignore: IGNORED, deep: 8 }).filter((file) => !IGNORED_PATH.test(file));
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
function readSmallFile(path) {
|
|
2443
|
+
try {
|
|
2444
|
+
return statSync(path).size <= MAX_FILE_SIZE ? readFileSync(path, "utf8") : void 0;
|
|
2445
|
+
} catch {
|
|
2446
|
+
return void 0;
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
function readProjectTheme(rootDir) {
|
|
2450
|
+
if (!existsSync(join(rootDir, "package.json")))
|
|
2451
|
+
return { stylesheets: [], presets: [] };
|
|
2452
|
+
const files = projectFiles(rootDir).sort();
|
|
2453
|
+
const theme = { stylesheets: [], presets: [] };
|
|
2454
|
+
for (const file of files) {
|
|
2455
|
+
const source = readSmallFile(join(rootDir, file));
|
|
2456
|
+
if (!source)
|
|
2457
|
+
continue;
|
|
2458
|
+
if (file.endsWith(".css")) {
|
|
2459
|
+
if (isTailwindStylesheet(source))
|
|
2460
|
+
theme.stylesheets.push(readStylesheet(file, source));
|
|
2461
|
+
} else if (source.includes("definePreset(")) {
|
|
2462
|
+
theme.presets.push(...readPresets(file, source));
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
return theme;
|
|
2466
|
+
}
|
|
2467
|
+
const FOUNDATION_USAGE = {
|
|
2468
|
+
"base-font-size": { topic: "typography", usage: "root font size: every `rem` and the `text-*` sizes" },
|
|
2469
|
+
"border-width": { topic: "typography", usage: "`border` (default width)" },
|
|
2470
|
+
"font-family": { topic: "typography", usage: "`font-sans` (default font)" },
|
|
2471
|
+
"font-mono-stack": { topic: "typography", usage: "`font-mono`" },
|
|
2472
|
+
"font-display-stack": { topic: "typography", usage: "`font-display`" },
|
|
2473
|
+
"motion-fast": { topic: "typography", usage: "`duration-(--duration-fast)`" },
|
|
2474
|
+
"motion-normal": { topic: "typography", usage: "`duration-(--duration-normal)` and the default transition duration" },
|
|
2475
|
+
"motion-slow": { topic: "typography", usage: "`duration-(--duration-slow)`" },
|
|
2476
|
+
"easing-out": { topic: "typography", usage: "`ease-out` and the default transition easing" },
|
|
2477
|
+
"easing-in": { topic: "typography", usage: "`ease-in`" },
|
|
2478
|
+
"easing-in-out": { topic: "typography", usage: "`ease-in-out`" },
|
|
2479
|
+
"disabled-opacity": { topic: "utilities", usage: "`disabled-opacity`, `disabled-state`" },
|
|
2480
|
+
"disabled-cursor": { topic: "utilities", usage: "`disabled-cursor`, `disabled-state`" },
|
|
2481
|
+
"space": { topic: "spacing", usage: "every spacing utility (`p-4` = 4 x space)" }
|
|
2482
|
+
};
|
|
2483
|
+
const COMPONENT_USAGE = {
|
|
2484
|
+
"btn.font-weight": "font weight of MazBtn",
|
|
2485
|
+
"container.bg": "`bg-container`: MazCard, MazContainer, MazDialog, MazPopover, MazDrawer...",
|
|
2486
|
+
"input.bg": "`bg-input`: MazInput, MazSelect, MazTextarea, MazCheckbox...",
|
|
2487
|
+
"input.top-label-font-weight": "font weight of the `top-label` of the inputs",
|
|
2488
|
+
"dialog.max-width": "max width of MazDialog",
|
|
2489
|
+
"dialog.min-width": "min width of MazDialog"
|
|
2490
|
+
};
|
|
2491
|
+
function code(value) {
|
|
2492
|
+
return `\`${value.replaceAll("`", "'").replaceAll("|", "\\|")}\``;
|
|
2493
|
+
}
|
|
2494
|
+
function shorten(value, max = 70) {
|
|
2495
|
+
return value.length > max ? `${value.slice(0, max - 3)}...` : value;
|
|
2496
|
+
}
|
|
2497
|
+
function display(value) {
|
|
2498
|
+
if (value === void 0)
|
|
2499
|
+
return "-";
|
|
2500
|
+
return code(shorten(typeof value === "object" && value !== null ? JSON.stringify(value) : String(value)));
|
|
2501
|
+
}
|
|
2502
|
+
function asRecord(value) {
|
|
2503
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2504
|
+
}
|
|
2505
|
+
const COLOR_CLASSES = {
|
|
2506
|
+
shadow: "`shadow-shadow/20`",
|
|
2507
|
+
overlay: "`bg-overlay`",
|
|
2508
|
+
divider: "`border-divider`"
|
|
2509
|
+
};
|
|
2510
|
+
function colorClasses(name) {
|
|
2511
|
+
if (COLOR_CLASSES[name])
|
|
2512
|
+
return COLOR_CLASSES[name];
|
|
2513
|
+
return name.endsWith("-foreground") ? code(`text-${name}`) : `${code(`bg-${name}`)} ${code(`text-${name}`)}`;
|
|
2514
|
+
}
|
|
2515
|
+
function presetColors(preset) {
|
|
2516
|
+
const colors = asRecord(preset.overrides.colors);
|
|
2517
|
+
const light = asRecord(colors.light);
|
|
2518
|
+
const dark = asRecord(colors.dark);
|
|
2519
|
+
const names = [.../* @__PURE__ */ new Set([...Object.keys(light), ...Object.keys(dark)])];
|
|
2520
|
+
if (names.length === 0)
|
|
2521
|
+
return [];
|
|
2522
|
+
return [
|
|
2523
|
+
"| Color | Classes | Light | Dark |",
|
|
2524
|
+
"| --- | --- | --- | --- |",
|
|
2525
|
+
...names.map((name) => `| \`${name}\` | ${colorClasses(name)} | ${display(light[name])} | ${display(dark[name])} |`)
|
|
2526
|
+
];
|
|
2527
|
+
}
|
|
2528
|
+
function presetFoundation(preset, wanted) {
|
|
2529
|
+
const rows = Object.entries(asRecord(preset.overrides.foundation)).filter(([key]) => wanted(FOUNDATION_USAGE[key]?.topic ?? "css-variables")).map(([key, value]) => `| \`${key}\` | \`--maz-${key}\` | ${FOUNDATION_USAGE[key]?.usage ?? "-"} | ${display(value)} |`);
|
|
2530
|
+
return rows.length > 0 ? ["| Foundation | CSS variable | Used by | Value |", "| --- | --- | --- | --- |", ...rows] : [];
|
|
2531
|
+
}
|
|
2532
|
+
function presetScales(preset, wanted) {
|
|
2533
|
+
const scales = asRecord(preset.overrides.scales);
|
|
2534
|
+
const rows = [
|
|
2535
|
+
...wanted("radius") ? Object.entries(asRecord(scales.rounded)).map(([key, value]) => `| ${code(`rounded-${key}`)} | ${display(value)} |`) : [],
|
|
2536
|
+
...wanted("shadows") ? Object.entries(asRecord(scales.shadow)).map(([key, value]) => `| ${code(`shadow-${key}`)} | ${display(value)} |`) : []
|
|
2537
|
+
];
|
|
2538
|
+
return rows.length > 0 ? ["| Class | Value |", "| --- | --- |", ...rows] : [];
|
|
2539
|
+
}
|
|
2540
|
+
function flatten(value, path) {
|
|
2541
|
+
const record = asRecord(value);
|
|
2542
|
+
const keys = Object.keys(record);
|
|
2543
|
+
if (keys.length === 0 || keys.every((key) => key === "light" || key === "dark"))
|
|
2544
|
+
return [[path, value]];
|
|
2545
|
+
return keys.flatMap((key) => flatten(record[key], path ? `${path}.${key}` : key));
|
|
2546
|
+
}
|
|
2547
|
+
function presetComponents(preset) {
|
|
2548
|
+
const rows = flatten(asRecord(preset.overrides.components), "").filter(([path]) => path).map(([path, value]) => `| \`components.${path}\` | ${COMPONENT_USAGE[path] ?? "-"} | ${display(value)} |`);
|
|
2549
|
+
return rows.length > 0 ? ["| Component token | Used by | Value |", "| --- | --- | --- |", ...rows] : [];
|
|
2550
|
+
}
|
|
2551
|
+
function renderPreset(preset, wanted, filtered) {
|
|
2552
|
+
const name = typeof preset.overrides.name === "string" ? ` \`${preset.overrides.name}\`` : "";
|
|
2553
|
+
const origin = [preset.exportName && `\`${preset.exportName}\``, preset.base && `base \`${preset.base}\``].filter(Boolean).join(", ");
|
|
2554
|
+
const tables = [
|
|
2555
|
+
wanted("colors") ? presetColors(preset) : [],
|
|
2556
|
+
presetFoundation(preset, wanted),
|
|
2557
|
+
presetScales(preset, wanted),
|
|
2558
|
+
wanted("colors") ? presetComponents(preset) : []
|
|
2559
|
+
].filter((table) => table.length > 0);
|
|
2560
|
+
if (filtered && tables.length === 0)
|
|
2561
|
+
return void 0;
|
|
2562
|
+
return [
|
|
2563
|
+
`### Preset${name} - \`${preset.file}\`${origin ? ` (${origin})` : ""}`,
|
|
2564
|
+
"The preset sets the **values** of the maz-ui tokens at runtime, the class names stay the same: `bg-primary` uses the preset `primary`, `font-display` the preset `font-display-stack`. Brand colors, fonts, radius, shadows and motion belong here, not in CSS.",
|
|
2565
|
+
...tables.map((table) => table.join("\n"))
|
|
2566
|
+
].join("\n\n");
|
|
2567
|
+
}
|
|
2568
|
+
function mazUiThemeVariables(tailwind) {
|
|
2569
|
+
const variables = /* @__PURE__ */ new Map();
|
|
2570
|
+
const named = (entries, toVariable) => {
|
|
2571
|
+
for (const entry of entries) variables.set(toVariable(entry.name), entry.value);
|
|
2572
|
+
};
|
|
2573
|
+
for (const color of tailwind.colors) variables.set(`--color-${color.name}`, `var(${color.variable})`);
|
|
2574
|
+
named(tailwind.fonts, (name) => `--${name}`);
|
|
2575
|
+
named(tailwind.radius, (name) => name.replace(/^rounded-/, "--radius-"));
|
|
2576
|
+
named(tailwind.shadows, (name) => `--${name}`);
|
|
2577
|
+
named(tailwind.breakpoints, (name) => `--breakpoint-${name.replace(/:$/, "")}`);
|
|
2578
|
+
named(tailwind.easings, (name) => `--${name}`);
|
|
2579
|
+
named(tailwind.zIndex, (name) => `--${name}`);
|
|
2580
|
+
named(tailwind.durations, (name) => /--duration-[\w-]+/.exec(name)?.[0] ?? name);
|
|
2581
|
+
return variables;
|
|
2582
|
+
}
|
|
2583
|
+
function tokenNote(token, mazUi) {
|
|
2584
|
+
const original = mazUi.get(token.variable);
|
|
2585
|
+
const notes = [
|
|
2586
|
+
token.comment,
|
|
2587
|
+
original && original !== token.value ? `Redefines the maz-ui token (maz-ui: ${code(shorten(original, 50))}): this class no longer follows the preset.` : void 0
|
|
2588
|
+
];
|
|
2589
|
+
return notes.filter(Boolean).join(" ").replaceAll("|", "\\|") || "-";
|
|
2590
|
+
}
|
|
2591
|
+
function renderStylesheet(stylesheet, mazUi, wanted, animated) {
|
|
2592
|
+
const sections = [];
|
|
2593
|
+
const problems = stylesheetProblems(stylesheet);
|
|
2594
|
+
if (problems.length > 0)
|
|
2595
|
+
sections.push(problems.map((problem) => `> ${problem}`).join("\n>\n"));
|
|
2596
|
+
const tokens = stylesheet.tokens.filter((token) => wanted(token.topic));
|
|
2597
|
+
if (tokens.length > 0) {
|
|
2598
|
+
sections.push([
|
|
2599
|
+
`#### Project tokens (\`@theme\`)`,
|
|
2600
|
+
"",
|
|
2601
|
+
"| Classes | Variable | Value | Note |",
|
|
2602
|
+
"| --- | --- | --- | --- |",
|
|
2603
|
+
...tokens.map((token) => `| ${code(token.usage)} | \`${token.variable}\` | ${code(shorten(token.value, 60))} | ${tokenNote(token, mazUi)} |`)
|
|
2604
|
+
].join("\n"));
|
|
2605
|
+
}
|
|
2606
|
+
if (stylesheet.resets.length > 0 && wanted("colors"))
|
|
2607
|
+
sections.push(`Reset namespaces: ${stylesheet.resets.map((reset) => code(reset)).join(", ")}: the default Tailwind tokens of these namespaces do not exist.`);
|
|
2608
|
+
if (stylesheet.variants.length > 0 && (wanted("variants") || wanted("breakpoints")))
|
|
2609
|
+
sections.push(["#### Custom variants", "", "| Variant | Definition |", "| --- | --- |", ...stylesheet.variants.map((variant) => `| \`${variant.name}:\` | ${code(shorten(variant.definition))} |`)].join("\n"));
|
|
2610
|
+
if (stylesheet.utilities.length > 0 && wanted("utilities"))
|
|
2611
|
+
sections.push(["#### Custom utilities", "", "| Class | CSS |", "| --- | --- |", ...stylesheet.utilities.map((utility) => `| \`${utility.name}\` | ${code(shorten(utility.css))} |`)].join("\n"));
|
|
2612
|
+
const keyframes = stylesheet.keyframes.filter((name) => !animated.has(name));
|
|
2613
|
+
if (keyframes.length > 0 && wanted("typography"))
|
|
2614
|
+
sections.push(`Keyframes without \`animate-*\` token: ${keyframes.map((name) => code(name)).join(", ")}. Use them in CSS or add a \`--animate-<name>\` token to the \`@theme\` to get a class.`);
|
|
2615
|
+
if (stylesheet.mazVariables.length > 0 && (wanted("layout") || wanted("css-variables"))) {
|
|
2616
|
+
sections.push([
|
|
2617
|
+
"#### maz-ui variables set in CSS",
|
|
2618
|
+
"",
|
|
2619
|
+
"| Variable | Value | Selector |",
|
|
2620
|
+
"| --- | --- | --- |",
|
|
2621
|
+
...stylesheet.mazVariables.map((variable) => `| \`${variable.variable}\` | ${code(shorten(variable.value, 50))} | ${code(variable.selector)} |`)
|
|
2622
|
+
].join("\n"));
|
|
2623
|
+
}
|
|
2624
|
+
return sections;
|
|
2625
|
+
}
|
|
2626
|
+
function renderStylesheetSection(stylesheet, mazUi, topics, animated) {
|
|
2627
|
+
const sections = renderStylesheet(stylesheet, mazUi, (topic) => !topics?.length || topics.includes(topic), animated);
|
|
2628
|
+
if (topics?.length && sections.length === 0)
|
|
2629
|
+
return void 0;
|
|
2630
|
+
const imports = stylesheet.imports.length > 0 ? [`Imports: ${stylesheet.imports.map((path) => code(path)).join(", ")}`] : [];
|
|
2631
|
+
return [`### Tailwind \`${stylesheet.file}\``, ...imports, ...sections].join("\n\n");
|
|
2632
|
+
}
|
|
2633
|
+
function stylesheetProblems(stylesheet) {
|
|
2634
|
+
if (!stylesheet.imports.includes("tailwindcss"))
|
|
2635
|
+
return [];
|
|
2636
|
+
const mazUiImports = stylesheet.imports.filter((path) => path.startsWith("maz-ui/tailwindcss"));
|
|
2637
|
+
const problems = [];
|
|
2638
|
+
if (mazUiImports.length === 0)
|
|
2639
|
+
problems.push("The maz-ui theme is not imported: `bg-primary`, `rounded-md`, `mob-m:`... do not exist. Add `@import 'maz-ui/tailwindcss/theme.css';` after `@import 'tailwindcss';`.");
|
|
2640
|
+
else if (!mazUiImports.includes("maz-ui/tailwindcss/utilities.css"))
|
|
2641
|
+
problems.push("`maz-ui/tailwindcss/utilities.css` is not imported: `flex-center`, `padded-container`, `disabled-state`... do not exist.");
|
|
2642
|
+
return problems;
|
|
2643
|
+
}
|
|
2644
|
+
const HOW_IT_WORKS = `How your theme combines with maz-ui:
|
|
2645
|
+
|
|
2646
|
+
1. **The preset** (\`definePreset\` of \`@maz-ui/themes\`) sets the runtime values of the maz-ui tokens (\`--maz-*\`): brand colors, fonts, radius, shadows, motion. The maz-ui classes keep their names and follow it, in light and dark mode.
|
|
2647
|
+
2. **The Tailwind entry** imports the maz-ui theme, then extends it with \`@theme\` tokens (each one generates classes: \`--animate-rise\` -> \`animate-rise\`, \`--font-display\` -> \`font-display\`), \`@custom-variant\` and \`@utility\`.
|
|
2648
|
+
3. **Pick classes in this order**: the project tokens below for project-specific needs, then the maz-ui classes, never the default Tailwind palette, hard-coded values or arbitrary values. When a value is missing, add a token to the project \`@theme\` (or a brand value to the preset) and use its class.`;
|
|
2649
|
+
function renderProjectTheme(theme, tailwind, topics) {
|
|
2650
|
+
if (theme.stylesheets.length === 0 && theme.presets.length === 0)
|
|
2651
|
+
return void 0;
|
|
2652
|
+
const wanted = (topic) => !topics?.length || topics.includes(topic);
|
|
2653
|
+
const mazUi = mazUiThemeVariables(tailwind);
|
|
2654
|
+
const animated = new Set(theme.stylesheets.flatMap((stylesheet) => stylesheet.tokens.filter((token) => token.variable.startsWith("--animate-")).map((token) => token.value.split(/\s/)[0])));
|
|
2655
|
+
return [
|
|
2656
|
+
"## Your project theme",
|
|
2657
|
+
HOW_IT_WORKS,
|
|
2658
|
+
...theme.presets.map((preset) => renderPreset(preset, wanted, !!topics?.length)),
|
|
2659
|
+
...theme.stylesheets.map((stylesheet) => renderStylesheetSection(stylesheet, mazUi, topics, animated))
|
|
2660
|
+
].filter(Boolean).join("\n\n");
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2045
2663
|
const THEME_TOPICS = {
|
|
2046
2664
|
"setup": "Setup",
|
|
2047
2665
|
"colors": "Colors",
|
|
@@ -2057,28 +2675,32 @@ const THEME_TOPICS = {
|
|
|
2057
2675
|
"transitions": "Vue transitions",
|
|
2058
2676
|
"css-variables": "CSS variables in custom CSS"
|
|
2059
2677
|
};
|
|
2060
|
-
function getThemeTokens(context, { topics }) {
|
|
2678
|
+
function getThemeTokens(context, { topics, project = true }) {
|
|
2061
2679
|
const reference = context.knowledge.docsById.get("reference/tailwind-tokens");
|
|
2062
2680
|
if (!reference)
|
|
2063
2681
|
throw new ToolInputError('This maz-ui version does not ship the Tailwind reference. Read `get_doc({ id: "guide/tailwind" })`.');
|
|
2064
2682
|
const markdown = stripFrontmatter(context.knowledge.readDoc(reference.id));
|
|
2065
|
-
const
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2683
|
+
const projectTheme = project ? renderProjectTheme(readProjectTheme(context.cwd), context.knowledge.manifest.tailwind, topics) : void 0;
|
|
2684
|
+
const footer = '\n\nHow to extend the theme (project colors, fonts, animations, variants, utilities, container width) and how it combines with the preset: `get_doc({ id: "guide/tailwind" })`. Brand colors, fonts and radius belong to a preset: `get_doc({ id: "ecosystem/themes", sections: ["Creating Custom Themes"] })`.';
|
|
2685
|
+
const mazUiTheme = topics?.length ? `# maz-ui ${context.knowledge.version} Tailwind theme
|
|
2686
|
+
|
|
2687
|
+
${topics.map((topic) => {
|
|
2069
2688
|
const section = findSection(markdown, THEME_TOPICS[topic]);
|
|
2070
2689
|
return section ? sectionContent(markdown, section) : "";
|
|
2071
|
-
});
|
|
2072
|
-
return
|
|
2690
|
+
}).filter(Boolean).join("\n\n")}` : markdown.trim();
|
|
2691
|
+
return projectTheme ? `${projectTheme}
|
|
2692
|
+
|
|
2693
|
+
---
|
|
2073
2694
|
|
|
2074
|
-
${
|
|
2695
|
+
${mazUiTheme}${footer}` : `${mazUiTheme}${footer}`;
|
|
2075
2696
|
}
|
|
2076
2697
|
const getThemeTokensTool = defineTool({
|
|
2077
2698
|
name: "get_theme_tokens",
|
|
2078
2699
|
title: "maz-ui Tailwind theme tokens",
|
|
2079
|
-
description: "
|
|
2700
|
+
description: "The Tailwind theme of the project: its own tokens, preset, variants and utilities (read from its Tailwind entry and `definePreset`), then every Tailwind CSS class and CSS variable of the maz-ui theme (installed version): theme colors (bg-primary, text-muted, border-divider, bg-surface... with -foreground and 50-950 scales), radius, shadows (shadow-elevation), spacing, breakpoints (mob-*, tab-*, lap-*), fonts, durations, z-index, custom utilities (flex-center, padded-container), dark variant and CSS variables. Use it before styling: never the default Tailwind palette, hard-coded colors or the internal `maz:` prefix.",
|
|
2080
2701
|
inputSchema: {
|
|
2081
|
-
topics: z.array(z.enum(Object.keys(THEME_TOPICS))).optional().describe("Only these topics (default: everything)")
|
|
2702
|
+
topics: z.array(z.enum(Object.keys(THEME_TOPICS))).optional().describe("Only these topics (default: everything)"),
|
|
2703
|
+
project: z.boolean().optional().describe("Include the theme of the project: preset, `@theme` tokens, variants, utilities (default: true)")
|
|
2082
2704
|
},
|
|
2083
2705
|
run: getThemeTokens
|
|
2084
2706
|
});
|
|
@@ -2145,7 +2767,7 @@ async function runTool(context, tool, args) {
|
|
|
2145
2767
|
function createMazUiMcpServer(options = {}) {
|
|
2146
2768
|
const context = createContext(options);
|
|
2147
2769
|
const server = new McpServer(
|
|
2148
|
-
{ name: "maz-ui", title: "maz-ui", version },
|
|
2770
|
+
{ name: "maz-ui", title: "maz-ui", version: readOwnVersion() },
|
|
2149
2771
|
{ instructions: buildInstructions(context) }
|
|
2150
2772
|
);
|
|
2151
2773
|
for (const tool of TOOLS) {
|
|
@@ -2172,7 +2794,7 @@ async function startStdioServer(options = {}) {
|
|
|
2172
2794
|
return server;
|
|
2173
2795
|
}
|
|
2174
2796
|
|
|
2175
|
-
const HELP = `maz-ui-mcp
|
|
2797
|
+
const HELP = `maz-ui-mcp: maz-ui knowledge for AI coding agents
|
|
2176
2798
|
|
|
2177
2799
|
Usage:
|
|
2178
2800
|
maz-ui-mcp [serve] [--docs <dir>] Start the MCP server (stdio)
|
|
@@ -2309,7 +2931,7 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
2309
2931
|
return 0;
|
|
2310
2932
|
}
|
|
2311
2933
|
if (values.version) {
|
|
2312
|
-
console.log(
|
|
2934
|
+
console.log(readOwnVersion());
|
|
2313
2935
|
return 0;
|
|
2314
2936
|
}
|
|
2315
2937
|
switch (command) {
|