@master/css-mcp 2.0.0-rc.76

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) Aoyue Design LLC.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # Master CSS MCP Server
2
+
3
+ Model Context Protocol server for Master CSS tooling. It lets AI clients inspect a Master CSS workspace, read the active project manifest, scan source files, inspect classes, render generated CSS, run lint diagnostics, request syntax suggestions, and apply reviewed fix previews.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -D @master/css-mcp
9
+ ```
10
+
11
+ You can also run it directly:
12
+
13
+ ```bash
14
+ npx -y @master/css-mcp@rc --root /absolute/path/to/project
15
+ ```
16
+
17
+ ## MCP client config
18
+
19
+ Most MCP clients accept a command and arguments for stdio servers:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "master-css": {
25
+ "command": "npx",
26
+ "args": ["-y", "@master/css-mcp@rc", "--root", "/absolute/path/to/project"]
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ Use an absolute `--root` path so the server resolves the intended workspace.
33
+
34
+ ## Tools
35
+
36
+ | Tool | Use |
37
+ | --- | --- |
38
+ | `mastercss_workspace_info` | Report workspace roots, resolved packages, and manifest status. |
39
+ | `mastercss_inspect_class` | Inspect one class and return generated rules and CSS text. |
40
+ | `mastercss_render_css` | Generate CSS from HTML or a class list. |
41
+ | `mastercss_scan_project` | Scan sources, check optional classes, and report scanner state, stylesheet entries, generated CSS metadata, and missing CSS diagnostics. |
42
+ | `mastercss_lint_project` | Run class-list diagnostics for workspace files without writing files. |
43
+ | `mastercss_lint_content` | Run class-list diagnostics on an in-memory source buffer without writing files. |
44
+ | `mastercss_suggest_syntax` | Return language-service completions and hover context. |
45
+ | `mastercss_preview_fixes` | Create a diff preview and confirmation token. |
46
+ | `mastercss_apply_preview` | Apply a preview after token, hash, and workspace checks. |
47
+
48
+ ## Machine-readable results
49
+
50
+ Project scan and lint reports use `version: 1`. Tooling can rely on stable top-level fields such as `version`, `root`, `manifest`, `inputs`, `files`, `diagnostics`, and `summary` when present. Nested diagnostic `data` objects may gain additional fields over time.
51
+
52
+ ## Safety
53
+
54
+ Read-only tools do not write files. File writes use a two-step preview/apply flow, validate workspace containment, and verify original file hashes before writing.
55
+
56
+ ## Related docs
57
+
58
+ - [MCP Server guide](https://rc.css.master.co/guide/mcp-server)
59
+ - [AI Coding guide](https://rc.css.master.co/guide/ai-coding)
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import { startStdioServer } from "../server.js";
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ //#region src/bin/index.ts
7
+ function readPackageVersion() {
8
+ const directory = dirname(fileURLToPath(import.meta.url));
9
+ try {
10
+ return JSON.parse(readFileSync(resolve(directory, "../../package.json"), "utf8")).version || "0.0.0";
11
+ } catch {
12
+ return "0.0.0";
13
+ }
14
+ }
15
+ function printHelp() {
16
+ process.stdout.write(`Usage: master-css-mcp [options]
17
+
18
+ Options:
19
+ --root <dir> Workspace root. Defaults to the current working directory.
20
+ --preview-ttl <ms> Preview confirmation token TTL in milliseconds.
21
+ --version Print version.
22
+ --help Print help.
23
+ `);
24
+ }
25
+ function parseArgs(argv) {
26
+ const options = {};
27
+ for (let index = 0; index < argv.length; index++) {
28
+ const arg = argv[index];
29
+ switch (arg) {
30
+ case "--root":
31
+ options.root = argv[++index];
32
+ if (!options.root) throw new Error("--root requires a directory.");
33
+ break;
34
+ case "--preview-ttl": {
35
+ const value = Number(argv[++index]);
36
+ if (!Number.isFinite(value) || value <= 0) throw new Error("--preview-ttl requires a positive number.");
37
+ options.previewTTL = value;
38
+ break;
39
+ }
40
+ case "--version":
41
+ process.stdout.write(`${readPackageVersion()}\n`);
42
+ process.exit(0);
43
+ break;
44
+ case "--help":
45
+ printHelp();
46
+ process.exit(0);
47
+ break;
48
+ default: throw new Error(`Unknown option: ${arg}`);
49
+ }
50
+ }
51
+ return options;
52
+ }
53
+ try {
54
+ await startStdioServer(parseArgs(process.argv.slice(2)));
55
+ } catch (error) {
56
+ console.error(error instanceof Error ? error.message : String(error));
57
+ process.exit(1);
58
+ }
59
+ //#endregion
60
+ export {};
@@ -0,0 +1,60 @@
1
+ //#region src/context.d.ts
2
+ interface MasterCSSMCPContextOptions {
3
+ root?: string;
4
+ roots?: string[];
5
+ previewTTL?: number;
6
+ }
7
+ interface PreviewChange {
8
+ filePath: string;
9
+ beforeText?: string;
10
+ afterText: string;
11
+ }
12
+ interface StoredPreviewChange {
13
+ filePath: string;
14
+ beforeHash: string | null;
15
+ afterHash: string;
16
+ beforeExists: boolean;
17
+ beforeBytes: number;
18
+ afterBytes: number;
19
+ afterText: string;
20
+ diff: string;
21
+ }
22
+ interface StoredPreview {
23
+ confirmToken: string;
24
+ createdAt: number;
25
+ expiresAt: number;
26
+ changes: StoredPreviewChange[];
27
+ }
28
+ interface PreviewResult {
29
+ confirmToken?: string;
30
+ expiresAt?: number;
31
+ changes: StoredPreviewChange[];
32
+ summary: {
33
+ files: number;
34
+ bytesBefore: number;
35
+ bytesAfter: number;
36
+ };
37
+ }
38
+ declare class MasterCSSMCPContext {
39
+ readonly root: string;
40
+ readonly roots: string[];
41
+ readonly previewTTL: number;
42
+ private readonly containmentRoots;
43
+ private previews;
44
+ constructor(options?: MasterCSSMCPContextOptions);
45
+ assertContained(filePath: string): string;
46
+ resolveVirtualPath(filePath: string): string;
47
+ resolveExistingFile(filePath: string): Promise<string>;
48
+ resolveWritableFile(filePath: string): Promise<string>;
49
+ validateGlobPatterns(patterns: string[]): void;
50
+ createPreview(changes: PreviewChange[], ttl?: number): Promise<PreviewResult>;
51
+ applyPreview(confirmToken: string): Promise<{
52
+ applied: boolean;
53
+ changes: {
54
+ filePath: string;
55
+ afterHash: string;
56
+ }[];
57
+ }>;
58
+ }
59
+ //#endregion
60
+ export { MasterCSSMCPContextOptions, PreviewChange, PreviewResult, StoredPreview, StoredPreviewChange, MasterCSSMCPContext as default };
@@ -0,0 +1,189 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { existsSync, realpathSync, statSync } from "node:fs";
3
+ import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
4
+ import { dirname, isAbsolute, relative, resolve } from "node:path";
5
+ //#region src/context.ts
6
+ const DEFAULT_PREVIEW_TTL = 300 * 1e3;
7
+ function sha256(text) {
8
+ return createHash("sha256").update(text).digest("hex");
9
+ }
10
+ function isContained(root, filePath) {
11
+ const path = relative(root, filePath);
12
+ return path === "" || Boolean(path && !path.startsWith("..") && !isAbsolute(path));
13
+ }
14
+ function pushUniquePath(paths, filePath) {
15
+ if (!paths.includes(filePath)) paths.push(filePath);
16
+ }
17
+ function collectPathAliases(filePath) {
18
+ const aliases = [];
19
+ const resolved = resolve(filePath);
20
+ pushUniquePath(aliases, resolved);
21
+ try {
22
+ pushUniquePath(aliases, realpathSync(resolved));
23
+ } catch {}
24
+ try {
25
+ pushUniquePath(aliases, realpathSync.native(resolved));
26
+ } catch {}
27
+ return aliases;
28
+ }
29
+ function assertFilePattern(pattern) {
30
+ if (isAbsolute(pattern)) throw new Error(`Absolute glob patterns are not allowed: ${pattern}`);
31
+ if (pattern.split(/[\\/]+/).includes("..")) throw new Error(`Glob patterns may not escape the workspace root: ${pattern}`);
32
+ }
33
+ function formatDiff(filePath, beforeText, afterText) {
34
+ if (beforeText === afterText) return "";
35
+ const beforeLines = beforeText.length ? beforeText.split("\n") : [];
36
+ const afterLines = afterText.length ? afterText.split("\n") : [];
37
+ return [
38
+ `--- ${filePath}`,
39
+ `+++ ${filePath}`,
40
+ "@@",
41
+ ...beforeLines.map((line) => `-${line}`),
42
+ ...afterLines.map((line) => `+${line}`)
43
+ ].join("\n");
44
+ }
45
+ var MasterCSSMCPContext = class {
46
+ root;
47
+ roots;
48
+ previewTTL;
49
+ containmentRoots;
50
+ previews = /* @__PURE__ */ new Map();
51
+ constructor(options = {}) {
52
+ const configuredRoots = options.roots?.length ? options.roots : [options.root || process.cwd()];
53
+ const roots = [];
54
+ const containmentRoots = [];
55
+ for (const root of configuredRoots) {
56
+ const [resolved] = collectPathAliases(root);
57
+ if (!existsSync(resolved)) throw new Error(`Workspace root does not exist: ${resolved}`);
58
+ const real = realpathSync(resolved);
59
+ if (!statSync(real).isDirectory()) throw new Error(`Workspace root is not a directory: ${real}`);
60
+ roots.push(real);
61
+ containmentRoots.push(...collectPathAliases(resolved));
62
+ }
63
+ this.roots = roots;
64
+ this.root = this.roots[0];
65
+ this.containmentRoots = [...new Set(containmentRoots)];
66
+ this.previewTTL = options.previewTTL ?? DEFAULT_PREVIEW_TTL;
67
+ }
68
+ assertContained(filePath) {
69
+ const resolvedPath = resolve(filePath);
70
+ const pathAliases = collectPathAliases(resolvedPath);
71
+ if (!this.containmentRoots.some((root) => pathAliases.some((pathAlias) => isContained(root, pathAlias)))) throw new Error(`Path is outside the allowed workspace roots: ${filePath}`);
72
+ return resolvedPath;
73
+ }
74
+ resolveVirtualPath(filePath) {
75
+ const resolved = resolve(this.root, filePath);
76
+ return this.assertContained(resolved);
77
+ }
78
+ async resolveExistingFile(filePath) {
79
+ const realPath = await realpath$1(resolve(this.root, filePath));
80
+ this.assertContained(realPath);
81
+ if (!(await stat(realPath)).isFile()) throw new Error(`Path is not a file: ${realPath}`);
82
+ return realPath;
83
+ }
84
+ async resolveWritableFile(filePath) {
85
+ const resolved = resolve(this.root, filePath);
86
+ if (existsSync(resolved)) {
87
+ const realPath = await realpath$1(resolved);
88
+ this.assertContained(realPath);
89
+ if (!(await stat(realPath)).isFile()) throw new Error(`Path is not a file: ${realPath}`);
90
+ return realPath;
91
+ }
92
+ this.assertContained(resolved);
93
+ const parent = await realpath$1(dirname(resolved));
94
+ this.assertContained(parent);
95
+ return resolved;
96
+ }
97
+ validateGlobPatterns(patterns) {
98
+ for (const pattern of patterns) assertFilePattern(pattern);
99
+ }
100
+ async createPreview(changes, ttl = this.previewTTL) {
101
+ const storedChanges = [];
102
+ for (const change of changes) {
103
+ const filePath = await this.resolveWritableFile(change.filePath);
104
+ const beforeText = change.beforeText ?? await readFileIfExists(filePath);
105
+ if (beforeText === change.afterText) continue;
106
+ const beforeExists = existsSync(filePath);
107
+ storedChanges.push({
108
+ filePath,
109
+ beforeHash: beforeExists ? sha256(beforeText) : null,
110
+ afterHash: sha256(change.afterText),
111
+ beforeExists,
112
+ beforeBytes: beforeText.length,
113
+ afterBytes: change.afterText.length,
114
+ afterText: change.afterText,
115
+ diff: formatDiff(filePath, beforeText, change.afterText)
116
+ });
117
+ }
118
+ const summary = {
119
+ files: storedChanges.length,
120
+ bytesBefore: storedChanges.reduce((total, change) => total + change.beforeBytes, 0),
121
+ bytesAfter: storedChanges.reduce((total, change) => total + change.afterBytes, 0)
122
+ };
123
+ if (!storedChanges.length) return {
124
+ changes: [],
125
+ summary
126
+ };
127
+ const confirmToken = randomBytes(24).toString("base64url");
128
+ const createdAt = Date.now();
129
+ const expiresAt = createdAt + ttl;
130
+ this.previews.set(confirmToken, {
131
+ confirmToken,
132
+ createdAt,
133
+ expiresAt,
134
+ changes: storedChanges
135
+ });
136
+ return {
137
+ confirmToken,
138
+ expiresAt,
139
+ changes: storedChanges,
140
+ summary
141
+ };
142
+ }
143
+ async applyPreview(confirmToken) {
144
+ const preview = this.previews.get(confirmToken);
145
+ if (!preview) throw new Error("Unknown or already applied preview token.");
146
+ if (preview.expiresAt < Date.now()) {
147
+ this.previews.delete(confirmToken);
148
+ throw new Error("Preview token has expired.");
149
+ }
150
+ for (const change of preview.changes) {
151
+ const filePath = await this.resolveWritableFile(change.filePath);
152
+ if (filePath !== change.filePath) throw new Error(`Preview path changed while applying: ${change.filePath}`);
153
+ const currentText = await readFileIfExists(filePath);
154
+ const currentExists = existsSync(filePath);
155
+ if (change.beforeHash === null) {
156
+ if (currentExists) throw new Error(`File was created after preview generation: ${filePath}`);
157
+ } else if (sha256(currentText) !== change.beforeHash) throw new Error(`File changed after preview generation: ${filePath}`);
158
+ }
159
+ for (const change of preview.changes) {
160
+ await mkdir(dirname(change.filePath), { recursive: true });
161
+ await writeFile(change.filePath, change.afterText);
162
+ }
163
+ this.previews.delete(confirmToken);
164
+ return {
165
+ applied: true,
166
+ changes: preview.changes.map(({ filePath, afterHash }) => ({
167
+ filePath,
168
+ afterHash
169
+ }))
170
+ };
171
+ }
172
+ };
173
+ async function realpath$1(filePath) {
174
+ try {
175
+ return await realpath(filePath);
176
+ } catch (error) {
177
+ throw new Error(`Unable to resolve path: ${filePath}${error instanceof Error ? ` (${error.message})` : ""}`);
178
+ }
179
+ }
180
+ async function readFileIfExists(filePath) {
181
+ try {
182
+ return await readFile(filePath, "utf8");
183
+ } catch (error) {
184
+ if (typeof error === "object" && error && "code" in error && error.code === "ENOENT") return "";
185
+ throw error;
186
+ }
187
+ }
188
+ //#endregion
189
+ export { MasterCSSMCPContext as default };
@@ -0,0 +1,3 @@
1
+ import { MasterCSSMCPServerInstance, createMasterCSSMCPServer, startStdioServer } from "./server.js";
2
+ import MasterCSSMCPContext, { MasterCSSMCPContextOptions } from "./context.js";
3
+ export { MasterCSSMCPContext, type MasterCSSMCPContextOptions, type MasterCSSMCPServerInstance, createMasterCSSMCPServer, startStdioServer };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import MasterCSSMCPContext from "./context.js";
2
+ import { createMasterCSSMCPServer, startStdioServer } from "./server.js";
3
+ export { MasterCSSMCPContext, createMasterCSSMCPServer, startStdioServer };
@@ -0,0 +1,25 @@
1
+ import MasterCSSMCPContext from "./context.js";
2
+
3
+ //#region src/language.d.ts
4
+ interface SuggestSyntaxOptions {
5
+ content: string;
6
+ filePath: string;
7
+ position: {
8
+ line: number;
9
+ character: number;
10
+ };
11
+ triggerCharacter?: string;
12
+ limit?: number;
13
+ }
14
+ declare function suggestSyntax(context: MasterCSSMCPContext, options: SuggestSyntaxOptions): Promise<{
15
+ manifest: {
16
+ error?: string | undefined;
17
+ status: "loaded" | "error";
18
+ entries: string[];
19
+ };
20
+ completions: import("vscode-languageserver-protocol").CompletionItem[];
21
+ total: number;
22
+ hover: import("vscode-languageserver-protocol").Hover | undefined;
23
+ }>;
24
+ //#endregion
25
+ export { SuggestSyntaxOptions, suggestSyntax };
@@ -0,0 +1,46 @@
1
+ import { loadWorkspaceManifest } from "./project.js";
2
+ import { extname } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import CSSLanguageService from "@master/css-language-service";
5
+ import { TextDocument } from "vscode-languageserver-textdocument";
6
+ //#region src/language.ts
7
+ const languageByExtension = {
8
+ ".astro": "astro",
9
+ ".css": "css",
10
+ ".htm": "html",
11
+ ".html": "html",
12
+ ".js": "javascript",
13
+ ".jsx": "javascriptreact",
14
+ ".less": "less",
15
+ ".md": "markdown",
16
+ ".mdx": "mdx",
17
+ ".scss": "scss",
18
+ ".svelte": "svelte",
19
+ ".ts": "typescript",
20
+ ".tsx": "typescriptreact",
21
+ ".vue": "vue"
22
+ };
23
+ async function suggestSyntax(context, options) {
24
+ const filePath = context.resolveVirtualPath(options.filePath);
25
+ const manifest = await loadWorkspaceManifest(context);
26
+ const service = new CSSLanguageService(manifest.status === "loaded" ? { manifest: manifest.manifest } : void 0);
27
+ const document = TextDocument.create(pathToFileURL(filePath).href, languageByExtension[extname(filePath).toLowerCase()] || "html", 0, options.content);
28
+ const completions = service.suggestSyntax(document, options.position, {
29
+ triggerKind: options.triggerCharacter ? 2 : 1,
30
+ ...options.triggerCharacter ? { triggerCharacter: options.triggerCharacter } : {}
31
+ }) ?? [];
32
+ const hover = service.inspectSyntax(document, options.position);
33
+ const limit = options.limit ?? 50;
34
+ return {
35
+ manifest: {
36
+ status: manifest.status,
37
+ entries: manifest.entries,
38
+ ...manifest.status === "error" ? { error: manifest.error } : {}
39
+ },
40
+ completions: completions.slice(0, limit),
41
+ total: completions.length,
42
+ hover
43
+ };
44
+ }
45
+ //#endregion
46
+ export { suggestSyntax };
package/dist/lint.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { MasterCSSLintFileResult, MasterCSSLintSourceDiagnostic } from "@master/css-lint";
2
+ import MasterCSSMCPContext from "./context.js";
3
+
4
+ //#region src/lint.d.ts
5
+ interface LintProjectOptions {
6
+ patterns?: string[];
7
+ rules?: string;
8
+ }
9
+ interface LintContentOptions {
10
+ content: string;
11
+ filePath: string;
12
+ rules?: string;
13
+ }
14
+ interface PreviewFixesOptions extends LintProjectOptions {
15
+ includeDirectiveFixes?: boolean;
16
+ ttlMs?: number;
17
+ }
18
+ declare function lintProject(context: MasterCSSMCPContext, options?: LintProjectOptions): Promise<{
19
+ version: number;
20
+ root: string;
21
+ manifest: {
22
+ status: "loaded" | "error";
23
+ entries: string[];
24
+ diagnostics: MasterCSSLintSourceDiagnostic[];
25
+ };
26
+ files: MasterCSSLintFileResult[];
27
+ summary: import("@master/css-lint").MasterCSSLintSummary;
28
+ }>;
29
+ declare function lintContent(context: MasterCSSMCPContext, options: LintContentOptions): Promise<{
30
+ version: number;
31
+ root: string;
32
+ manifest: {
33
+ status: "loaded" | "error";
34
+ entries: string[];
35
+ diagnostics: MasterCSSLintSourceDiagnostic[];
36
+ };
37
+ files: MasterCSSLintFileResult[];
38
+ summary: import("@master/css-lint").MasterCSSLintSummary;
39
+ }>;
40
+ declare function previewLintFixes(context: MasterCSSMCPContext, options?: PreviewFixesOptions): Promise<{
41
+ mode: string;
42
+ preview: import("./context.js").PreviewResult;
43
+ lint: {
44
+ version: number;
45
+ root: string;
46
+ manifest: {
47
+ status: "loaded" | "error";
48
+ entries: string[];
49
+ diagnostics: MasterCSSLintSourceDiagnostic[];
50
+ };
51
+ files: MasterCSSLintFileResult[];
52
+ summary: import("@master/css-lint").MasterCSSLintSummary;
53
+ };
54
+ }>;
55
+ //#endregion
56
+ export { LintContentOptions, LintProjectOptions, PreviewFixesOptions, lintContent, lintProject, previewLintFixes };
package/dist/lint.js ADDED
@@ -0,0 +1,132 @@
1
+ import { loadWorkspaceManifest } from "./project.js";
2
+ import { resolveSourceFiles } from "./scan.js";
3
+ import { readFile } from "node:fs/promises";
4
+ import { createCSSWithNativeDeclarations } from "@master/css-validator/native-declaration";
5
+ import { fixMasterCSSContent, lintMasterCSSContent, resolveMasterCSSLintRules, summarizeMasterCSSLintFiles } from "@master/css-lint";
6
+ //#region src/lint.ts
7
+ const DEFAULT_LINT_SOURCE_PATTERNS = ["**/*.{html,htm,js,jsx,cjs,ts,tsx,mts,cts,svelte,astro,vue,md,mdx,pug,php,css,scss,less}"];
8
+ const DEFAULT_IGNORE_PATTERNS = ["**/node_modules/**", "node_modules"];
9
+ const LINT_REPORT_VERSION = 1;
10
+ function createManifestDiagnostic(context, message) {
11
+ return {
12
+ ruleId: "manifest",
13
+ code: "manifest-loading-error",
14
+ severity: "error",
15
+ message,
16
+ range: {
17
+ start: 0,
18
+ end: 0
19
+ },
20
+ loc: {
21
+ start: {
22
+ line: 1,
23
+ column: 1
24
+ },
25
+ end: {
26
+ line: 1,
27
+ column: 1
28
+ }
29
+ },
30
+ source: "Master CSS",
31
+ sourceKind: "manifest",
32
+ data: { cwd: context.root }
33
+ };
34
+ }
35
+ async function loadLintState(context, options = {}) {
36
+ const rules = resolveMasterCSSLintRules(options.rules);
37
+ const manifest = await loadWorkspaceManifest(context);
38
+ const css = manifest.status === "loaded" ? createCSSWithNativeDeclarations(manifest.manifest) : void 0;
39
+ const files = await resolveSourceFiles(context, options.patterns ?? DEFAULT_LINT_SOURCE_PATTERNS, options.patterns ? [] : DEFAULT_IGNORE_PATTERNS);
40
+ return {
41
+ rules,
42
+ manifest,
43
+ css,
44
+ inputs: await Promise.all(files.map(async (filePath) => ({
45
+ filePath,
46
+ content: await readFile(filePath, "utf8")
47
+ })))
48
+ };
49
+ }
50
+ function lintInputs(inputs, css, rules) {
51
+ return inputs.map((input) => lintMasterCSSContent({
52
+ content: input.content,
53
+ filePath: input.filePath,
54
+ css,
55
+ rules
56
+ })).filter((result) => result.diagnostics.length);
57
+ }
58
+ function createManifestFileResult(context, diagnostic) {
59
+ return {
60
+ filePath: context.root,
61
+ languageId: "manifest",
62
+ sourceKind: "manifest",
63
+ diagnostics: [diagnostic]
64
+ };
65
+ }
66
+ async function lintProject(context, options = {}) {
67
+ const state = await loadLintState(context, options);
68
+ const files = state.manifest.status === "error" || !state.css ? [createManifestFileResult(context, createManifestDiagnostic(context, `Failed to load Master CSS manifest: ${state.manifest.error}`))] : lintInputs(state.inputs, state.css, state.rules);
69
+ return {
70
+ version: LINT_REPORT_VERSION,
71
+ root: context.root,
72
+ manifest: {
73
+ status: state.manifest.status,
74
+ entries: state.manifest.entries,
75
+ diagnostics: state.manifest.status === "error" ? files[0].diagnostics : []
76
+ },
77
+ files,
78
+ summary: summarizeMasterCSSLintFiles(files)
79
+ };
80
+ }
81
+ async function lintContent(context, options) {
82
+ const rules = resolveMasterCSSLintRules(options.rules);
83
+ const filePath = context.resolveVirtualPath(options.filePath);
84
+ const manifest = await loadWorkspaceManifest(context);
85
+ const files = manifest.status === "error" ? [createManifestFileResult(context, createManifestDiagnostic(context, `Failed to load Master CSS manifest: ${manifest.error}`))] : [lintMasterCSSContent({
86
+ content: options.content,
87
+ filePath,
88
+ css: createCSSWithNativeDeclarations(manifest.manifest),
89
+ rules
90
+ })];
91
+ return {
92
+ version: LINT_REPORT_VERSION,
93
+ root: context.root,
94
+ manifest: {
95
+ status: manifest.status,
96
+ entries: manifest.entries,
97
+ diagnostics: manifest.status === "error" ? files[0].diagnostics : []
98
+ },
99
+ files,
100
+ summary: summarizeMasterCSSLintFiles(files)
101
+ };
102
+ }
103
+ async function previewLintFixes(context, options = {}) {
104
+ const state = await loadLintState(context, options);
105
+ if (state.manifest.status === "error" || !state.css) return {
106
+ mode: "lint-fixes",
107
+ preview: await context.createPreview([], options.ttlMs),
108
+ lint: await lintProject(context, options)
109
+ };
110
+ const changes = [];
111
+ for (const input of state.inputs) {
112
+ const fixed = fixMasterCSSContent({
113
+ content: input.content,
114
+ filePath: input.filePath,
115
+ css: state.css,
116
+ rules: state.rules,
117
+ includeDirectiveFixes: Boolean(options.includeDirectiveFixes)
118
+ });
119
+ if (fixed !== input.content) changes.push({
120
+ filePath: input.filePath,
121
+ beforeText: input.content,
122
+ afterText: fixed
123
+ });
124
+ }
125
+ return {
126
+ mode: "lint-fixes",
127
+ preview: await context.createPreview(changes, options.ttlMs),
128
+ lint: await lintProject(context, options)
129
+ };
130
+ }
131
+ //#endregion
132
+ export { lintContent, lintProject, previewLintFixes };