tailwind-lint 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,6 +48,7 @@ tailwind-lint --verbose
48
48
  ### How Auto-Discovery Works
49
49
 
50
50
  **Tailwind CSS v4:**
51
+
51
52
  - Finds CSS config files in common locations: `app.css`, `index.css`, `tailwind.css`, `global.css`, etc.
52
53
  - Searches in project root and subdirectories: `./`, `./src/`, `./src/styles/`, `./app/`, etc.
53
54
  - Uses file patterns from `@source` directives if present
@@ -55,6 +56,7 @@ tailwind-lint --verbose
55
56
  - **Note:** When CSS config is in a subdirectory (e.g., `src/styles/global.css`), files are discovered from the project root
56
57
 
57
58
  **Tailwind CSS v3:**
59
+
58
60
  - Finds JavaScript config files: `tailwind.config.js`, `tailwind.config.cjs`, `tailwind.config.mjs`, `tailwind.config.ts`
59
61
  - Uses file patterns from the `content` array in your config
60
62
 
@@ -63,6 +65,7 @@ tailwind-lint --verbose
63
65
  - `-c, --config <path>` - Path to Tailwind config file (default: auto-discover)
64
66
  - `-a, --auto` - Auto-discover files from config content patterns (legacy, enabled by default)
65
67
  - `--fix` - Automatically fix problems that can be fixed
68
+ - `--format <text|json>` - Output format (`text` default, `json` for machine-readable output)
66
69
  - `-v, --verbose` - Enable verbose logging for debugging
67
70
  - `-h, --help` - Show help message
68
71
  - `--version` - Show version number
@@ -81,8 +84,33 @@ tailwind-lint "**/*.vue" --fix
81
84
 
82
85
  # Lint with a specific CSS config (v4)
83
86
  tailwind-lint --config ./styles/app.css
87
+
88
+ # Machine-readable output for LLMs/agents
89
+ tailwind-lint --auto --format json
84
90
  ```
85
91
 
92
+ ## LLM / Agent Integration
93
+
94
+ Use JSON output to avoid brittle text parsing:
95
+
96
+ ```bash
97
+ tailwind-lint --auto --format json
98
+ ```
99
+
100
+ The JSON payload includes:
101
+
102
+ - `ok` - `true` when no errors are found
103
+ - `summary` - counts for `errors`, `warnings`, `fixed`, `filesWithIssues`, `totalFilesProcessed`
104
+ - `config` - resolved runtime values (`cwd`, `configPath`, `autoDiscover`, `fix`, `patterns`)
105
+ - `files[]` - per-file diagnostics with 1-based `line`/`column` ranges
106
+
107
+ Typical agent flow:
108
+
109
+ 1. Run `tailwind-lint --auto --format json`.
110
+ 2. If `summary.errors > 0`, fail the check and surface diagnostics.
111
+ 3. If only warnings exist, optionally continue and open a cleanup task.
112
+ 4. Re-run with `--fix` when autofix is allowed.
113
+
86
114
  ## Configuration
87
115
 
88
116
  ### Tailwind CSS v4
@@ -93,7 +121,7 @@ Create a CSS config file (`app.css`, `index.css`, or `tailwind.css`):
93
121
  @import "tailwindcss";
94
122
 
95
123
  @theme {
96
- --color-primary: #3b82f6;
124
+ --color-primary: #3b82f6;
97
125
  }
98
126
 
99
127
  @source "./src/**/*.{js,jsx,ts,tsx,html}";
@@ -108,12 +136,12 @@ Create a JavaScript config file (`tailwind.config.js`):
108
136
  ```javascript
109
137
  /** @type {import('tailwindcss').Config} */
110
138
  module.exports = {
111
- content: ['./src/**/*.{js,jsx,ts,tsx}'],
112
- theme: {
113
- extend: {},
114
- },
115
- plugins: [],
116
- }
139
+ content: ["./src/**/*.{js,jsx,ts,tsx}"],
140
+ theme: {
141
+ extend: {},
142
+ },
143
+ plugins: [],
144
+ };
117
145
  ```
118
146
 
119
147
  ## Autofix
@@ -128,6 +156,7 @@ Files are written atomically with multiple iterations to ensure all fixes are ap
128
156
  ## Features
129
157
 
130
158
  **Core (v3 & v4):**
159
+
131
160
  - CSS Conflicts - Detects when multiple classes apply the same CSS properties (e.g., `block flex`, `text-left text-center`) - **Note:** Works reliably in v4, limited support in v3 - no autofix
132
161
  - Invalid @apply Usage - Validates if a class can be used with `@apply`
133
162
  - Invalid @screen References - Detects references to non-existent breakpoints
@@ -138,6 +167,7 @@ Files are written atomically with multiple iterations to ensure all fixes are ap
138
167
  - Autofix - Automatically fix issues with `--fix` flag
139
168
 
140
169
  **v4-Specific:**
170
+
141
171
  - Canonical Class Suggestions - Suggests shorthand equivalents for arbitrary values (e.g., `top-[60px]` → `top-15`)
142
172
  - Invalid @source Directives - Validates `@source` directive paths
143
173
  - Full theme loading - Automatically loads Tailwind's default theme
@@ -168,4 +198,25 @@ pnpm format
168
198
 
169
199
  # Check code without fixing
170
200
  pnpm lint
201
+
202
+ # Preview next version locally (no publish)
203
+ pnpm release:dry
171
204
  ```
205
+
206
+ ## Releases
207
+
208
+ Releases are automated with Semantic Release on pushes to `main`.
209
+
210
+ - Version bump is derived from Conventional Commits.
211
+ - npm release and GitHub release are generated automatically.
212
+ - npm publish uses npm Trusted Publishing (OIDC), no `NPM_TOKEN` required.
213
+
214
+ Commit examples:
215
+
216
+ - `feat: add json output mode` -> minor release
217
+ - `fix: resolve v4 config discovery in monorepos` -> patch release
218
+ - `feat: drop Node 20 support` + commit body `BREAKING CHANGE: Node 20 is no longer supported` -> major release
219
+ - `perf: speed up config discovery` -> patch release
220
+ - `docs: update readme` -> no release
221
+
222
+ `pnpm release:dry` runs against the local repo metadata (`--repository-url .`) so it does not require GitHub remote access.
package/dist/cli.cjs CHANGED
@@ -1,27 +1,85 @@
1
1
  #!/usr/bin/env node
2
- const require_linter = require('./linter-DVb9soAi.cjs');
2
+ const require_state = require('./state-QQi4_-5a.cjs');
3
+ const require_linter = require('./linter.cjs');
3
4
  let node_path = require("node:path");
4
- node_path = require_linter.__toESM(node_path);
5
+ node_path = require_state.__toESM(node_path);
5
6
  let chalk = require("chalk");
6
- chalk = require_linter.__toESM(chalk);
7
+ chalk = require_state.__toESM(chalk);
7
8
  let node_fs = require("node:fs");
8
- node_fs = require_linter.__toESM(node_fs);
9
+ node_fs = require_state.__toESM(node_fs);
9
10
  let commander = require("commander");
10
11
 
11
- //#region src/cli.ts
12
- const MAX_FILENAME_DISPLAY_LENGTH = 50;
13
- function countDiagnosticsBySeverity(diagnostics) {
12
+ //#region src/output.ts
13
+ function countBySeverity(diagnostics) {
14
14
  let errors = 0;
15
15
  let warnings = 0;
16
16
  for (const diagnostic of diagnostics) {
17
- if (diagnostic.severity === require_linter.SEVERITY.ERROR) errors++;
18
- if (diagnostic.severity === require_linter.SEVERITY.WARNING) warnings++;
17
+ if (diagnostic.severity === require_state.SEVERITY.ERROR) errors++;
18
+ if (diagnostic.severity === require_state.SEVERITY.WARNING) warnings++;
19
19
  }
20
20
  return {
21
21
  errors,
22
22
  warnings
23
23
  };
24
24
  }
25
+ function toJsonSeverity(severity) {
26
+ if (severity === require_state.SEVERITY.ERROR) return "error";
27
+ if (severity === require_state.SEVERITY.WARNING) return "warning";
28
+ return "info";
29
+ }
30
+ function toJsonDiagnostic(diagnostic) {
31
+ return {
32
+ line: diagnostic.range.start.line + 1,
33
+ column: diagnostic.range.start.character + 1,
34
+ endLine: diagnostic.range.end.line + 1,
35
+ endColumn: diagnostic.range.end.character + 1,
36
+ severity: toJsonSeverity(diagnostic.severity),
37
+ code: diagnostic.code ?? null,
38
+ message: diagnostic.message,
39
+ source: diagnostic.source
40
+ };
41
+ }
42
+ function createJsonOutput({ files, totalFilesProcessed, cwd, configPath, autoDiscover, fix, patterns }) {
43
+ let errors = 0;
44
+ let warnings = 0;
45
+ let fixed = 0;
46
+ let filesWithIssues = 0;
47
+ const mappedFiles = files.map((file) => {
48
+ const severityCount = countBySeverity(file.diagnostics);
49
+ errors += severityCount.errors;
50
+ warnings += severityCount.warnings;
51
+ fixed += file.fixedCount || 0;
52
+ if (file.diagnostics.length > 0) filesWithIssues++;
53
+ return {
54
+ path: file.path,
55
+ fixed: file.fixed || false,
56
+ fixedCount: file.fixedCount || 0,
57
+ diagnostics: file.diagnostics.map(toJsonDiagnostic)
58
+ };
59
+ });
60
+ return {
61
+ ok: errors === 0,
62
+ summary: {
63
+ errors,
64
+ warnings,
65
+ fixed,
66
+ filesWithIssues,
67
+ totalFilesProcessed
68
+ },
69
+ config: {
70
+ cwd,
71
+ configPath: configPath || null,
72
+ autoDiscover,
73
+ fix,
74
+ patterns
75
+ },
76
+ files: mappedFiles
77
+ };
78
+ }
79
+
80
+ //#endregion
81
+ //#region src/cli.ts
82
+ const MAX_FILENAME_DISPLAY_LENGTH = 50;
25
83
  function resolveOptions(files, options) {
26
84
  const hasConfigFlag = !!options.config;
27
85
  const hasAutoFlag = !!options.auto;
@@ -33,7 +91,7 @@ function resolveOptions(files, options) {
33
91
  const absoluteConfigPath = node_path.isAbsolute(options.config) ? options.config : node_path.resolve(process.cwd(), options.config);
34
92
  cwd = node_path.dirname(absoluteConfigPath);
35
93
  configPath = node_path.basename(absoluteConfigPath);
36
- patterns = [require_linter.DEFAULT_FILE_PATTERN];
94
+ patterns = [require_state.DEFAULT_FILE_PATTERN];
37
95
  }
38
96
  const autoDiscover = hasAutoFlag;
39
97
  return {
@@ -65,15 +123,15 @@ async function displayResults(files, fixMode) {
65
123
  console.log(chalk.default.green(` ✔ Fixed ${issueText}`));
66
124
  totalFixed += file.fixedCount || 0;
67
125
  }
68
- const { errors, warnings } = countDiagnosticsBySeverity(file.diagnostics);
126
+ const { errors, warnings } = countBySeverity(file.diagnostics);
69
127
  totalErrors += errors;
70
128
  totalWarnings += warnings;
71
129
  if (file.diagnostics.length > 0) filesWithIssues++;
72
130
  for (const diagnostic of file.diagnostics) {
73
131
  const line = diagnostic.range.start.line + 1;
74
132
  const char = diagnostic.range.start.character + 1;
75
- const severity = diagnostic.severity === require_linter.SEVERITY.ERROR ? "error" : "warning";
76
- const severityColor = diagnostic.severity === require_linter.SEVERITY.ERROR ? chalk.default.red(severity) : chalk.default.yellow(severity);
133
+ const severity = diagnostic.severity === require_state.SEVERITY.ERROR ? "error" : "warning";
134
+ const severityColor = diagnostic.severity === require_state.SEVERITY.ERROR ? chalk.default.red(severity) : chalk.default.yellow(severity);
77
135
  const code = diagnostic.code ? chalk.default.dim(` (${diagnostic.code})`) : "";
78
136
  console.log(` ${chalk.default.dim(`${line}:${char}`)} ${severityColor} ${diagnostic.message}${code}`);
79
137
  }
@@ -100,9 +158,9 @@ const program = new commander.Command();
100
158
  const getVersion = () => {
101
159
  const packageJsonPath = node_path.join(__dirname, "../package.json");
102
160
  try {
103
- return JSON.parse(node_fs.readFileSync(packageJsonPath, "utf-8")).version || require_linter.DEFAULT_VERSION;
161
+ return JSON.parse(node_fs.readFileSync(packageJsonPath, "utf-8")).version || require_state.DEFAULT_VERSION;
104
162
  } catch {
105
- return require_linter.DEFAULT_VERSION;
163
+ return require_state.DEFAULT_VERSION;
106
164
  }
107
165
  };
108
166
  program.configureHelp({ formatHelp: (cmd, helper) => {
@@ -131,7 +189,7 @@ program.configureHelp({ formatHelp: (cmd, helper) => {
131
189
  }
132
190
  return output;
133
191
  } });
134
- program.name("tailwind-lint").description("A CLI tool for linting Tailwind CSS class usage").version(getVersion()).argument("[files...]", "File patterns to lint (e.g., \"src/**/*.{js,jsx,ts,tsx}\")").option("-c, --config <path>", "Path to Tailwind config file (default: auto-discover)").option("-a, --auto", "Auto-discover files from Tailwind config content patterns").option("--fix", "Automatically fix problems that can be fixed").option("-v, --verbose", "Enable verbose logging for debugging").addHelpText("after", `
192
+ program.name("tailwind-lint").description("A CLI tool for linting Tailwind CSS class usage").version(getVersion()).argument("[files...]", "File patterns to lint (e.g., \"src/**/*.{js,jsx,ts,tsx}\")").option("-c, --config <path>", "Path to Tailwind config file (default: auto-discover)").option("-a, --auto", "Auto-discover files from Tailwind config content patterns").option("--fix", "Automatically fix problems that can be fixed").option("-v, --verbose", "Enable verbose logging for debugging").option("--format <format>", "Output format: text or json", "text").addHelpText("after", `
135
193
  ${chalk.default.bold.cyan("Examples:")}
136
194
  ${chalk.default.dim("$")} tailwind-lint
137
195
  ${chalk.default.dim("$")} tailwind-lint ${chalk.default.green("\"src/**/*.{js,jsx,ts,tsx}\"")}
@@ -150,8 +208,9 @@ ${chalk.default.bold.cyan("Notes:")}
150
208
  const hasAutoFlag = !!options.auto;
151
209
  if (!(files.length > 0) && !hasAutoFlag && !hasConfigFlag) options.auto = true;
152
210
  const resolved = resolveOptions(files, options);
211
+ const isJsonOutput = (options.format === "json" ? "json" : "text") === "json";
153
212
  try {
154
- if (resolved.verbose) {
213
+ if (resolved.verbose && !isJsonOutput) {
155
214
  console.log(chalk.default.cyan.bold("→ Configuration"));
156
215
  console.log(chalk.default.dim(` Working directory: ${resolved.cwd}`));
157
216
  console.log(chalk.default.dim(` Config path: ${resolved.configPath || "auto-discover"}`));
@@ -162,29 +221,51 @@ ${chalk.default.bold.cyan("Notes:")}
162
221
  const results = await require_linter.lint({
163
222
  ...resolved,
164
223
  onProgress: (current, total, file) => {
224
+ if (isJsonOutput) return;
165
225
  if (process.stdout.isTTY && !resolved.verbose) {
166
226
  const displayFile = truncateFilename(file);
167
- process.stdout.write(`\r${chalk.default.cyan("→")} Linting files... ${chalk.default.dim(`(${current}/${total})`)} ${chalk.default.dim(displayFile)}${" ".repeat(require_linter.TERMINAL_PADDING)}`);
227
+ process.stdout.write(`\r${chalk.default.cyan("→")} Linting files... ${chalk.default.dim(`(${current}/${total})`)} ${chalk.default.dim(displayFile)}${" ".repeat(require_state.TERMINAL_PADDING)}`);
168
228
  } else if (resolved.verbose) console.log(chalk.default.dim(` [${current}/${total}] Linting ${file}`));
169
229
  }
170
230
  });
171
- if (process.stdout.isTTY && !resolved.verbose) process.stdout.write(`\r${" ".repeat(require_linter.TERMINAL_WIDTH)}\r`);
231
+ if (process.stdout.isTTY && !resolved.verbose && !isJsonOutput) process.stdout.write(`\r${" ".repeat(require_state.TERMINAL_WIDTH)}\r`);
172
232
  if (results.totalFilesProcessed === 0) {
173
- console.log();
174
- console.log(chalk.default.yellow("⚠ No files found to lint"));
233
+ if (isJsonOutput) console.log(JSON.stringify(createJsonOutput({
234
+ ...resolved,
235
+ files: [],
236
+ totalFilesProcessed: 0
237
+ })));
238
+ else {
239
+ console.log();
240
+ console.log(chalk.default.yellow("⚠ No files found to lint"));
241
+ }
175
242
  process.exit(0);
176
243
  }
177
244
  if (results.files.length === 0) {
178
- console.log(chalk.default.green.bold("✔ No issues found"));
245
+ if (isJsonOutput) console.log(JSON.stringify(createJsonOutput({
246
+ ...resolved,
247
+ files: [],
248
+ totalFilesProcessed: results.totalFilesProcessed
249
+ })));
250
+ else console.log(chalk.default.green.bold("✔ No issues found"));
179
251
  process.exit(0);
180
252
  }
181
- await displayResults(results.files, resolved.fix);
182
- const hasErrors = results.files.some((file) => file.diagnostics.some((d) => d.severity === require_linter.SEVERITY.ERROR));
253
+ if (isJsonOutput) console.log(JSON.stringify(createJsonOutput({
254
+ ...resolved,
255
+ files: results.files,
256
+ totalFilesProcessed: results.totalFilesProcessed
257
+ })));
258
+ else await displayResults(results.files, resolved.fix);
259
+ const hasErrors = results.files.some((file) => file.diagnostics.some((d) => d.severity === require_state.SEVERITY.ERROR));
183
260
  process.exit(hasErrors ? 1 : 0);
184
261
  } catch (error) {
185
262
  const errorMessage = error instanceof Error ? error.message : String(error);
186
- console.error(chalk.default.red("✖ Error:"), errorMessage);
187
- if (resolved.verbose && error instanceof Error) {
263
+ if (isJsonOutput) console.log(JSON.stringify({
264
+ ok: false,
265
+ error: errorMessage
266
+ }));
267
+ else console.error(chalk.default.red("✖ Error:"), errorMessage);
268
+ if (resolved.verbose && error instanceof Error && !isJsonOutput) {
188
269
  console.error(chalk.default.dim("\nStack trace:"));
189
270
  console.error(chalk.default.dim(error.stack || error.toString()));
190
271
  }
package/dist/linter.cjs CHANGED
@@ -1,3 +1,169 @@
1
- const require_linter = require('./linter-DVb9soAi.cjs');
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_state = require('./state-QQi4_-5a.cjs');
3
+ let node_path = require("node:path");
4
+ node_path = require_state.__toESM(node_path);
5
+ let _tailwindcss_language_service = require("@tailwindcss/language-service");
6
+ let chalk = require("chalk");
7
+ chalk = require_state.__toESM(chalk);
8
+ let fast_glob = require("fast-glob");
9
+ fast_glob = require_state.__toESM(fast_glob);
10
+ let vscode_languageserver_textdocument = require("vscode-languageserver-textdocument");
2
11
 
3
- exports.lint = require_linter.lint;
12
+ //#region src/linter.ts
13
+ async function validateDocument(state, filePath, content) {
14
+ try {
15
+ if (!state) throw new Error("State is not initialized");
16
+ if (state.v4 && !state.designSystem) throw new Error("Design system not initialized for Tailwind v4. This might indicate a configuration issue.");
17
+ if (!state.v4 && !state.modules?.tailwindcss) throw new Error("Tailwind modules not initialized for Tailwind v3. This might indicate a configuration issue.");
18
+ const languageId = require_state.getLanguageId(filePath);
19
+ const uri = `file://${filePath}`;
20
+ return await (0, _tailwindcss_language_service.doValidate)(state, vscode_languageserver_textdocument.TextDocument.create(uri, languageId, 1, content));
21
+ } catch (error) {
22
+ const message = error instanceof Error ? error.message : String(error);
23
+ if (message.includes("Cannot read") || message.includes("undefined")) {
24
+ if (process.env.DEBUG) console.error(`Debug: Language service error for ${filePath}:`, error);
25
+ console.warn(`Warning: Language service crashed while validating ${filePath}. Skipping this file.`);
26
+ return [];
27
+ }
28
+ throw new Error(`Failed to validate document ${filePath}: ${message}`);
29
+ }
30
+ }
31
+ async function discoverFiles(cwd, patterns, configPath, autoDiscover) {
32
+ if (autoDiscover) return discoverFilesFromConfig(cwd, configPath);
33
+ return expandPatterns(cwd, patterns);
34
+ }
35
+ async function expandPatterns(cwd, patterns, extraIgnore = []) {
36
+ const files = await (0, fast_glob.default)(patterns, {
37
+ cwd,
38
+ absolute: false,
39
+ ignore: [...require_state.DEFAULT_IGNORE_PATTERNS, ...extraIgnore]
40
+ });
41
+ return [...new Set(files)].sort((a, b) => a.localeCompare(b));
42
+ }
43
+ async function discoverFilesFromConfig(cwd, configPath) {
44
+ const configFilePath = await require_state.findTailwindConfigPath(cwd, configPath);
45
+ if (!configFilePath) throw new Error("Could not find Tailwind config for auto-discovery.\nUse --config to specify the path, or provide file patterns directly.");
46
+ if (!require_state.isCssConfigFile(configFilePath)) {
47
+ const config = await require_state.loadTailwindConfig(configFilePath);
48
+ if (!config || !config.content) throw new Error("Tailwind config is missing the 'content' property.\nAdd a content array to specify which files to scan:\n content: ['./src/**/*.{js,jsx,ts,tsx}']");
49
+ const patterns = extractContentPatterns(config);
50
+ if (patterns.length === 0) throw new Error("No content patterns found in Tailwind config.\nEnsure your config has a content array with file patterns.");
51
+ return expandPatterns(cwd, patterns);
52
+ }
53
+ const configDir = node_path.dirname(configFilePath);
54
+ const cssContent = require_state.readFileSync(configFilePath);
55
+ const { include, exclude } = extractSourcePatterns(cssContent);
56
+ const importSource = extractImportSourceDirectives(cssContent);
57
+ const resolveFromConfig = (pattern) => {
58
+ const absolutePattern = node_path.resolve(configDir, pattern);
59
+ return node_path.relative(cwd, absolutePattern);
60
+ };
61
+ const resolvedExclude = exclude.map(resolveFromConfig);
62
+ const gitignorePatterns = require_state.readGitignorePatterns(cwd);
63
+ const extraIgnore = [...resolvedExclude, ...gitignorePatterns];
64
+ if (include.length > 0) return expandPatterns(cwd, include.map(resolveFromConfig), extraIgnore);
65
+ if (importSource.roots.length > 0) return expandPatterns(cwd, importSource.roots.map((root) => resolveFromConfig(node_path.join(root, "**/*.{js,jsx,ts,tsx,html,vue,svelte,astro,mdx}"))), extraIgnore);
66
+ if (importSource.disableAutoSource) return [];
67
+ return expandPatterns(cwd, [require_state.DEFAULT_FILE_PATTERN], extraIgnore);
68
+ }
69
+ function extractContentPatterns(config) {
70
+ if (!config.content) return [];
71
+ return (Array.isArray(config.content) ? config.content : config.content.files || []).filter((p) => typeof p === "string");
72
+ }
73
+ function extractSourcePatterns(cssContent) {
74
+ const include = [];
75
+ const exclude = [];
76
+ for (const match of cssContent.matchAll(/@source\s+(not\s+)?(?:inline\(|["']([^"']+)["'])/g)) {
77
+ const isNot = !!match[1];
78
+ const filePath = match[2];
79
+ if (!filePath) continue;
80
+ if (isNot) exclude.push(filePath);
81
+ else include.push(filePath);
82
+ }
83
+ return {
84
+ include,
85
+ exclude
86
+ };
87
+ }
88
+ function extractImportSourceDirectives(cssContent) {
89
+ const roots = [];
90
+ let disableAutoSource = false;
91
+ for (const match of cssContent.matchAll(/@import\s+["']tailwindcss(?:[^;]*?)\ssource\(\s*(none|["'][^"']+["'])\s*\)/g)) {
92
+ const raw = match[1];
93
+ if (!raw) continue;
94
+ if (raw === "none") {
95
+ disableAutoSource = true;
96
+ continue;
97
+ }
98
+ const sourceRoot = raw.slice(1, -1).trim();
99
+ if (sourceRoot.length > 0) roots.push(sourceRoot);
100
+ }
101
+ return {
102
+ roots: [...new Set(roots)],
103
+ disableAutoSource
104
+ };
105
+ }
106
+ async function processFiles(state, cwd, files, fix, onProgress) {
107
+ const results = [];
108
+ for (let i = 0; i < files.length; i += require_state.CONCURRENT_FILES) {
109
+ const batch = files.slice(i, i + require_state.CONCURRENT_FILES);
110
+ const batchResults = await Promise.all(batch.map(async (file, batchIndex) => {
111
+ if (onProgress) onProgress(i + batchIndex + 1, files.length, file);
112
+ return processFile(state, cwd, file, fix);
113
+ }));
114
+ results.push(...batchResults.filter((r) => r !== null));
115
+ }
116
+ return results;
117
+ }
118
+ async function processFile(state, cwd, filePath, fix) {
119
+ const absolutePath = node_path.isAbsolute(filePath) ? filePath : node_path.resolve(cwd, filePath);
120
+ if (!require_state.fileExists(absolutePath)) return null;
121
+ const content = require_state.readFileSync(absolutePath);
122
+ let diagnostics = await validateDocument(state, absolutePath, content);
123
+ let fixedCount = 0;
124
+ if (fix && diagnostics.length > 0) {
125
+ const fixResult = await require_state.applyCodeActions(state, absolutePath, content, diagnostics);
126
+ if (fixResult.changed) {
127
+ require_state.writeFileSync(absolutePath, fixResult.content);
128
+ fixedCount = fixResult.fixedCount;
129
+ diagnostics = await validateDocument(state, absolutePath, fixResult.content);
130
+ }
131
+ }
132
+ return {
133
+ path: node_path.relative(cwd, absolutePath),
134
+ diagnostics,
135
+ fixed: fixedCount > 0,
136
+ fixedCount
137
+ };
138
+ }
139
+ async function initializeState(cwd, configPath, verbose = false) {
140
+ try {
141
+ const state = await require_state.createState(cwd, configPath, verbose);
142
+ if (verbose) console.log();
143
+ return state;
144
+ } catch (error) {
145
+ const message = error instanceof Error ? error.message : String(error);
146
+ throw new Error(`Failed to initialize Tailwind state: ${message}`);
147
+ }
148
+ }
149
+ async function lint({ cwd, patterns, configPath, autoDiscover, fix = false, verbose = false, onProgress }) {
150
+ const state = await initializeState(cwd, configPath, verbose);
151
+ const files = await discoverFiles(cwd, patterns, configPath, autoDiscover);
152
+ if (verbose) {
153
+ console.log(chalk.default.cyan.bold(`→ Discovered ${files.length} file${files.length !== 1 ? "s" : ""} to lint`));
154
+ console.log();
155
+ }
156
+ if (files.length === 0) return {
157
+ files: [],
158
+ totalFilesProcessed: 0
159
+ };
160
+ return {
161
+ files: (await processFiles(state, cwd, files, fix, onProgress)).filter((result) => result.diagnostics.length > 0 || result.fixed),
162
+ totalFilesProcessed: files.length
163
+ };
164
+ }
165
+
166
+ //#endregion
167
+ exports.extractImportSourceDirectives = extractImportSourceDirectives;
168
+ exports.extractSourcePatterns = extractSourcePatterns;
169
+ exports.lint = lint;
package/dist/linter.d.cts CHANGED
@@ -22,6 +22,14 @@ interface LintResult {
22
22
  }
23
23
  //#endregion
24
24
  //#region src/linter.d.ts
25
+ declare function extractSourcePatterns(cssContent: string): {
26
+ include: string[];
27
+ exclude: string[];
28
+ };
29
+ declare function extractImportSourceDirectives(cssContent: string): {
30
+ roots: string[];
31
+ disableAutoSource: boolean;
32
+ };
25
33
  declare function lint({
26
34
  cwd,
27
35
  patterns,
@@ -32,4 +40,4 @@ declare function lint({
32
40
  onProgress
33
41
  }: LintOptions): Promise<LintResult>;
34
42
  //#endregion
35
- export { type LintFileResult, type LintOptions, type LintResult, lint };
43
+ export { type LintFileResult, type LintOptions, type LintResult, extractImportSourceDirectives, extractSourcePatterns, lint };
@@ -1,4 +1,4 @@
1
- //#region rolldown:runtime
1
+ //#region \0rolldown/runtime.js
2
2
  var __create = Object.create;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -68,11 +68,13 @@ const V4_CSS_NAMES = [
68
68
  "global.css",
69
69
  "styles.css",
70
70
  "style.css",
71
- "main.css"
71
+ "main.css",
72
+ "input.css"
72
73
  ];
73
74
  const V4_CSS_FOLDERS = [
74
75
  "./",
75
76
  "./src/",
77
+ "./src/app/",
76
78
  "./src/css/",
77
79
  "./src/style/",
78
80
  "./src/styles/",
@@ -80,9 +82,12 @@ const V4_CSS_FOLDERS = [
80
82
  "./app/css/",
81
83
  "./app/style/",
82
84
  "./app/styles/",
85
+ "./app/assets/css/",
83
86
  "./css/",
84
87
  "./style/",
85
- "./styles/"
88
+ "./styles/",
89
+ "./assets/css/",
90
+ "./resources/css/"
86
91
  ];
87
92
  const LANGUAGE_MAP = {
88
93
  ".astro": "astro",
@@ -118,6 +123,7 @@ const TERMINAL_WIDTH = 80;
118
123
  const TERMINAL_PADDING = 10;
119
124
  const MAX_FIX_ITERATIONS = 100;
120
125
  const QUICKFIX_ACTION_KIND = "quickfix";
126
+ const TAILWIND_V4_IMPORT_REGEX = /@import\s+["']tailwindcss(?:["'\s/]|$)/;
121
127
  function getLanguageId(filePath) {
122
128
  return LANGUAGE_MAP[filePath.substring(filePath.lastIndexOf(".")).toLowerCase()] || "html";
123
129
  }
@@ -386,6 +392,19 @@ function writeFileSync(filePath, content) {
386
392
  if (typeof content !== "string") throw new TypeError("Content must be a string");
387
393
  node_fs.writeFileSync(filePath, content, "utf-8");
388
394
  }
395
+ function readGitignorePatterns(cwd) {
396
+ const gitignorePath = node_path.join(cwd, ".gitignore");
397
+ if (!fileExists(gitignorePath)) return [];
398
+ try {
399
+ return node_fs.readFileSync(gitignorePath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!")).map((pattern) => {
400
+ const cleaned = pattern.replace(/\/+$/, "");
401
+ if (cleaned.includes("/") || cleaned.includes("*")) return cleaned.endsWith("/**") ? cleaned : `${cleaned}/**`;
402
+ return `**/${cleaned}/**`;
403
+ });
404
+ } catch {
405
+ return [];
406
+ }
407
+ }
389
408
 
390
409
  //#endregion
391
410
  //#region src/adapters/v4-adapter.ts
@@ -466,6 +485,7 @@ async function loadV4DesignSystem(state, cwd, configPath, verbose = false) {
466
485
  //#endregion
467
486
  //#region src/utils/config.ts
468
487
  const require$2 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href || __filename);
488
+ const CONFIG_DISCOVERY_MAX_DEPTH = 8;
469
489
  const isCssConfigFile = (filePath) => filePath.endsWith(".css");
470
490
  async function loadTailwindConfig(configPath) {
471
491
  if (isCssConfigFile(configPath)) return {};
@@ -492,16 +512,67 @@ async function findTailwindConfigPath(cwd, configPath) {
492
512
  const fullPath = node_path.join(cwd, p);
493
513
  if (fileExists(fullPath)) return fullPath;
494
514
  }
515
+ const v3Recursive = await (0, fast_glob.default)(V3_CONFIG_PATHS.map((p) => `**/${p}`), {
516
+ cwd,
517
+ absolute: true,
518
+ ignore: DEFAULT_IGNORE_PATTERNS,
519
+ deep: CONFIG_DISCOVERY_MAX_DEPTH
520
+ });
521
+ if (v3Recursive.length > 0) return sortByPathDepth(v3Recursive)[0];
495
522
  const v4Paths = V4_CSS_FOLDERS.flatMap((folder) => V4_CSS_NAMES.map((name) => node_path.join(folder, name)));
496
523
  for (const p of v4Paths) {
497
524
  const fullPath = node_path.join(cwd, p);
498
525
  if (fileExists(fullPath)) try {
499
526
  const content = readFileSync(fullPath);
500
- if (content.includes("@import \"tailwindcss\"") || content.includes("@import 'tailwindcss'")) return fullPath;
527
+ if (TAILWIND_V4_IMPORT_REGEX.test(content)) return fullPath;
501
528
  } catch {}
502
529
  }
530
+ const cssCandidates = await (0, fast_glob.default)("**/*.css", {
531
+ cwd,
532
+ absolute: true,
533
+ ignore: DEFAULT_IGNORE_PATTERNS,
534
+ deep: CONFIG_DISCOVERY_MAX_DEPTH
535
+ });
536
+ const v4Matches = [];
537
+ for (const candidate of cssCandidates) try {
538
+ const content = readFileSync(candidate);
539
+ if (TAILWIND_V4_IMPORT_REGEX.test(content)) v4Matches.push(candidate);
540
+ } catch {}
541
+ if (v4Matches.length > 0) return sortCssCandidates(cwd, v4Matches)[0];
503
542
  return null;
504
543
  }
544
+ function sortByPathDepth(paths) {
545
+ return [...paths].sort((a, b) => {
546
+ const depthA = splitDepth(a);
547
+ const depthB = splitDepth(b);
548
+ if (depthA !== depthB) return depthA - depthB;
549
+ return a.localeCompare(b);
550
+ });
551
+ }
552
+ function sortCssCandidates(cwd, paths) {
553
+ return [...paths].sort((a, b) => {
554
+ const scoreA = cssCandidateScore(cwd, a);
555
+ const scoreB = cssCandidateScore(cwd, b);
556
+ if (scoreA !== scoreB) return scoreA - scoreB;
557
+ return a.localeCompare(b);
558
+ });
559
+ }
560
+ function cssCandidateScore(cwd, candidate) {
561
+ const normalized = node_path.relative(cwd, candidate).split(node_path.sep).join("/");
562
+ const base = node_path.basename(candidate);
563
+ const depth = splitDepth(normalized);
564
+ const nameScore = V4_CSS_NAMES.includes(base) ? 0 : 20;
565
+ const folderScore = isPreferredCssFolder(normalized) ? 0 : 10;
566
+ return depth * 100 + nameScore + folderScore;
567
+ }
568
+ function isPreferredCssFolder(relativePath) {
569
+ const folder = node_path.dirname(relativePath).replace(/\\/g, "/");
570
+ const withSlash = folder === "." ? "./" : `./${folder}/`;
571
+ return V4_CSS_FOLDERS.includes(withSlash);
572
+ }
573
+ function splitDepth(value) {
574
+ return value.split(/[\\/]/).filter(Boolean).length;
575
+ }
505
576
 
506
577
  //#endregion
507
578
  //#region src/state.ts
@@ -571,130 +642,24 @@ async function createState(cwd, configPath, verbose = false) {
571
642
  }
572
643
 
573
644
  //#endregion
574
- //#region src/linter.ts
575
- async function validateDocument(state, filePath, content) {
576
- try {
577
- if (!state) throw new Error("State is not initialized");
578
- if (state.v4 && !state.designSystem) throw new Error("Design system not initialized for Tailwind v4. This might indicate a configuration issue.");
579
- if (!state.v4 && !state.modules?.tailwindcss) throw new Error("Tailwind modules not initialized for Tailwind v3. This might indicate a configuration issue.");
580
- const languageId = getLanguageId(filePath);
581
- const uri = `file://${filePath}`;
582
- return await (0, _tailwindcss_language_service.doValidate)(state, vscode_languageserver_textdocument.TextDocument.create(uri, languageId, 1, content));
583
- } catch (error) {
584
- const message = error instanceof Error ? error.message : String(error);
585
- if (message.includes("Cannot read") || message.includes("undefined")) {
586
- if (process.env.DEBUG) console.error(`Debug: Language service error for ${filePath}:`, error);
587
- console.warn(`Warning: Language service crashed while validating ${filePath}. Skipping this file.`);
588
- return [];
589
- }
590
- throw new Error(`Failed to validate document ${filePath}: ${message}`);
591
- }
592
- }
593
- async function discoverFiles(cwd, patterns, configPath, autoDiscover) {
594
- if (autoDiscover) return discoverFilesFromConfig(cwd, configPath);
595
- return expandPatterns(cwd, patterns);
596
- }
597
- async function expandPatterns(cwd, patterns) {
598
- return (0, fast_glob.default)(patterns, {
599
- cwd,
600
- absolute: false,
601
- ignore: DEFAULT_IGNORE_PATTERNS
602
- });
603
- }
604
- async function discoverFilesFromConfig(cwd, configPath) {
605
- const configFilePath = await findTailwindConfigPath(cwd, configPath);
606
- if (!configFilePath) throw new Error("Could not find Tailwind config for auto-discovery.\nUse --config to specify the path, or provide file patterns directly.");
607
- if (!isCssConfigFile(configFilePath)) {
608
- const config = await loadTailwindConfig(configFilePath);
609
- if (!config || !config.content) throw new Error("Tailwind config is missing the 'content' property.\nAdd a content array to specify which files to scan:\n content: ['./src/**/*.{js,jsx,ts,tsx}']");
610
- const patterns = extractContentPatterns(config);
611
- if (patterns.length === 0) throw new Error("No content patterns found in Tailwind config.\nEnsure your config has a content array with file patterns.");
612
- return expandPatterns(cwd, patterns);
613
- }
614
- const configDir = node_path.dirname(configFilePath);
615
- const sourcePatterns = extractSourcePatterns(readFileSync(configFilePath));
616
- if (sourcePatterns.length > 0) return expandPatterns(cwd, sourcePatterns.map((pattern) => {
617
- const absolutePattern = node_path.resolve(configDir, pattern);
618
- return node_path.relative(cwd, absolutePattern);
619
- }));
620
- return expandPatterns(cwd, [DEFAULT_FILE_PATTERN]);
621
- }
622
- function extractContentPatterns(config) {
623
- if (!config.content) return [];
624
- return (Array.isArray(config.content) ? config.content : config.content.files || []).filter((p) => typeof p === "string");
625
- }
626
- function extractSourcePatterns(cssContent) {
627
- const patterns = [];
628
- for (const match of cssContent.matchAll(/@source\s+["']([^"']+)["']/g)) patterns.push(match[1]);
629
- return patterns;
630
- }
631
- async function processFiles(state, cwd, files, fix, onProgress) {
632
- const results = [];
633
- for (let i = 0; i < files.length; i += CONCURRENT_FILES) {
634
- const batch = files.slice(i, i + CONCURRENT_FILES);
635
- const batchResults = await Promise.all(batch.map(async (file, batchIndex) => {
636
- if (onProgress) onProgress(i + batchIndex + 1, files.length, file);
637
- return processFile(state, cwd, file, fix);
638
- }));
639
- results.push(...batchResults.filter((r) => r !== null));
640
- }
641
- return results;
642
- }
643
- async function processFile(state, cwd, filePath, fix) {
644
- const absolutePath = node_path.isAbsolute(filePath) ? filePath : node_path.resolve(cwd, filePath);
645
- if (!fileExists(absolutePath)) return null;
646
- const content = readFileSync(absolutePath);
647
- let diagnostics = await validateDocument(state, absolutePath, content);
648
- let fixedCount = 0;
649
- if (fix && diagnostics.length > 0) {
650
- const fixResult = await applyCodeActions(state, absolutePath, content, diagnostics);
651
- if (fixResult.changed) {
652
- writeFileSync(absolutePath, fixResult.content);
653
- fixedCount = fixResult.fixedCount;
654
- diagnostics = await validateDocument(state, absolutePath, fixResult.content);
655
- }
656
- }
657
- return {
658
- path: node_path.relative(cwd, absolutePath),
659
- diagnostics,
660
- fixed: fixedCount > 0,
661
- fixedCount
662
- };
663
- }
664
- async function initializeState(cwd, configPath, verbose = false) {
665
- try {
666
- const state = await createState(cwd, configPath, verbose);
667
- if (verbose) console.log();
668
- return state;
669
- } catch (error) {
670
- const message = error instanceof Error ? error.message : String(error);
671
- throw new Error(`Failed to initialize Tailwind state: ${message}`);
672
- }
673
- }
674
- async function lint({ cwd, patterns, configPath, autoDiscover, fix = false, verbose = false, onProgress }) {
675
- const state = await initializeState(cwd, configPath, verbose);
676
- const files = await discoverFiles(cwd, patterns, configPath, autoDiscover);
677
- if (verbose) {
678
- console.log(chalk.default.cyan.bold(`→ Discovered ${files.length} file${files.length !== 1 ? "s" : ""} to lint`));
679
- console.log();
680
- }
681
- if (files.length === 0) return {
682
- files: [],
683
- totalFilesProcessed: 0
684
- };
685
- return {
686
- files: (await processFiles(state, cwd, files, fix, onProgress)).filter((result) => result.diagnostics.length > 0 || result.fixed),
687
- totalFilesProcessed: files.length
688
- };
689
- }
690
-
691
- //#endregion
645
+ Object.defineProperty(exports, 'CONCURRENT_FILES', {
646
+ enumerable: true,
647
+ get: function () {
648
+ return CONCURRENT_FILES;
649
+ }
650
+ });
692
651
  Object.defineProperty(exports, 'DEFAULT_FILE_PATTERN', {
693
652
  enumerable: true,
694
653
  get: function () {
695
654
  return DEFAULT_FILE_PATTERN;
696
655
  }
697
656
  });
657
+ Object.defineProperty(exports, 'DEFAULT_IGNORE_PATTERNS', {
658
+ enumerable: true,
659
+ get: function () {
660
+ return DEFAULT_IGNORE_PATTERNS;
661
+ }
662
+ });
698
663
  Object.defineProperty(exports, 'DEFAULT_VERSION', {
699
664
  enumerable: true,
700
665
  get: function () {
@@ -725,9 +690,63 @@ Object.defineProperty(exports, '__toESM', {
725
690
  return __toESM;
726
691
  }
727
692
  });
728
- Object.defineProperty(exports, 'lint', {
693
+ Object.defineProperty(exports, 'applyCodeActions', {
694
+ enumerable: true,
695
+ get: function () {
696
+ return applyCodeActions;
697
+ }
698
+ });
699
+ Object.defineProperty(exports, 'createState', {
700
+ enumerable: true,
701
+ get: function () {
702
+ return createState;
703
+ }
704
+ });
705
+ Object.defineProperty(exports, 'fileExists', {
706
+ enumerable: true,
707
+ get: function () {
708
+ return fileExists;
709
+ }
710
+ });
711
+ Object.defineProperty(exports, 'findTailwindConfigPath', {
712
+ enumerable: true,
713
+ get: function () {
714
+ return findTailwindConfigPath;
715
+ }
716
+ });
717
+ Object.defineProperty(exports, 'getLanguageId', {
718
+ enumerable: true,
719
+ get: function () {
720
+ return getLanguageId;
721
+ }
722
+ });
723
+ Object.defineProperty(exports, 'isCssConfigFile', {
724
+ enumerable: true,
725
+ get: function () {
726
+ return isCssConfigFile;
727
+ }
728
+ });
729
+ Object.defineProperty(exports, 'loadTailwindConfig', {
730
+ enumerable: true,
731
+ get: function () {
732
+ return loadTailwindConfig;
733
+ }
734
+ });
735
+ Object.defineProperty(exports, 'readFileSync', {
736
+ enumerable: true,
737
+ get: function () {
738
+ return readFileSync;
739
+ }
740
+ });
741
+ Object.defineProperty(exports, 'readGitignorePatterns', {
742
+ enumerable: true,
743
+ get: function () {
744
+ return readGitignorePatterns;
745
+ }
746
+ });
747
+ Object.defineProperty(exports, 'writeFileSync', {
729
748
  enumerable: true,
730
749
  get: function () {
731
- return lint;
750
+ return writeFileSync;
732
751
  }
733
752
  });
package/package.json CHANGED
@@ -1,74 +1,81 @@
1
1
  {
2
- "name": "tailwind-lint",
3
- "version": "0.6.1",
4
- "description": "A command-line tool that uses the Tailwind CSS IntelliSense plugin to show linting suggestions for your Tailwind CSS classes",
5
- "keywords": [
6
- "tailwindcss",
7
- "tailwind",
8
- "linter",
9
- "lint",
10
- "cli",
11
- "diagnostics",
12
- "intellisense",
13
- "css",
14
- "utility-first",
15
- "code-quality"
16
- ],
17
- "homepage": "https://github.com/ph1p/tailwind-lint#readme",
18
- "bugs": {
19
- "url": "https://github.com/ph1p/tailwind-lint/issues"
20
- },
21
- "repository": {
22
- "type": "git",
23
- "url": "https://github.com/ph1p/tailwind-lint.git"
24
- },
25
- "funding": {
26
- "type": "github",
27
- "url": "https://github.com/sponsors/ph1p"
28
- },
29
- "license": "MIT",
30
- "author": "Philip Stapelfeldt <me@ph1p.dev>",
31
- "type": "module",
32
- "bin": {
33
- "tailwind-lint": "./dist/cli.cjs"
34
- },
35
- "types": "./dist/linter.d.ts",
36
- "files": [
37
- "dist",
38
- "README.md",
39
- "LICENSE"
40
- ],
41
- "dependencies": {
42
- "@tailwindcss/language-service": "^0.14.29",
43
- "chalk": "^5.6.2",
44
- "commander": "^14.0.2",
45
- "fast-glob": "^3.3.3",
46
- "postcss": "^8.5.6",
47
- "vscode-languageserver-textdocument": "^1.0.12"
48
- },
49
- "devDependencies": {
50
- "@biomejs/biome": "^2.3.13",
51
- "@types/node": "^25.1.0",
52
- "tsdown": "^0.20.1",
53
- "typescript": "^5.9.3",
54
- "vitest": "^4.0.18"
55
- },
56
- "engines": {
57
- "node": ">=22.0.0",
58
- "pnpm": ">=10.0.0"
59
- },
60
- "publishConfig": {
61
- "access": "public",
62
- "registry": "https://registry.npmjs.org/"
63
- },
64
- "scripts": {
65
- "build": "tsdown",
66
- "dev": "tsdown --watch",
67
- "format": "biome check --write",
68
- "lint": "biome check",
69
- "start": "node dist/cli.cjs",
70
- "test": "vitest run",
71
- "test:coverage": "vitest run --coverage",
72
- "test:watch": "vitest"
73
- }
74
- }
2
+ "name": "tailwind-lint",
3
+ "version": "0.8.0",
4
+ "description": "A command-line tool that uses the Tailwind CSS IntelliSense plugin to show linting suggestions for your Tailwind CSS classes",
5
+ "keywords": [
6
+ "cli",
7
+ "code-quality",
8
+ "css",
9
+ "diagnostics",
10
+ "intellisense",
11
+ "lint",
12
+ "linter",
13
+ "tailwind",
14
+ "tailwindcss",
15
+ "utility-first"
16
+ ],
17
+ "homepage": "https://github.com/ph1p/tailwind-lint#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/ph1p/tailwind-lint/issues"
20
+ },
21
+ "license": "MIT",
22
+ "author": "Philip Stapelfeldt <me@ph1p.dev>",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/ph1p/tailwind-lint.git"
26
+ },
27
+ "funding": {
28
+ "type": "github",
29
+ "url": "https://github.com/sponsors/ph1p"
30
+ },
31
+ "bin": {
32
+ "tailwind-lint": "./dist/cli.cjs"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "README.md",
37
+ "LICENSE"
38
+ ],
39
+ "type": "module",
40
+ "types": "./dist/linter.d.ts",
41
+ "publishConfig": {
42
+ "access": "public",
43
+ "registry": "https://registry.npmjs.org/"
44
+ },
45
+ "scripts": {
46
+ "build": "tsdown",
47
+ "dev": "tsdown --watch",
48
+ "format": "oxfmt --write .",
49
+ "format:check": "oxfmt --check .",
50
+ "lint": "oxlint .",
51
+ "lint:fix": "oxlint --fix .",
52
+ "prepublishOnly": "pnpm build && pnpm test",
53
+ "release": "pnpm dlx semantic-release",
54
+ "release:dry": "SEMREL_LOCAL=1 pnpm dlx semantic-release --dry-run --no-ci --repository-url .",
55
+ "start": "node dist/cli.cjs",
56
+ "test": "vitest run",
57
+ "test:coverage": "vitest run --coverage",
58
+ "test:watch": "vitest"
59
+ },
60
+ "dependencies": {
61
+ "@tailwindcss/language-service": "^0.14.29",
62
+ "chalk": "^5.6.2",
63
+ "commander": "^14.0.3",
64
+ "fast-glob": "^3.3.3",
65
+ "postcss": "^8.5.6",
66
+ "vscode-languageserver-textdocument": "^1.0.12"
67
+ },
68
+ "devDependencies": {
69
+ "@types/node": "^25.3.0",
70
+ "oxfmt": "^0.34.0",
71
+ "oxlint": "^1.49.0",
72
+ "tsdown": "^0.20.3",
73
+ "typescript": "^5.9.3",
74
+ "vitest": "^4.0.18"
75
+ },
76
+ "engines": {
77
+ "node": ">=22.0.0",
78
+ "pnpm": ">=10.0.0"
79
+ },
80
+ "packageManager": "pnpm@10.30.1"
81
+ }