tailwint 1.0.1 → 1.0.2

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
@@ -1,8 +1,37 @@
1
- # tailwint
1
+ <p align="center">
2
+ <img src="assets/header.svg" alt="tailwint">
3
+ </p>
2
4
 
3
- Tailwind CSS linter for CI. Drives the official `@tailwindcss/language-server` to catch class errors and auto-fix them — the same diagnostics VS Code shows, but from the command line.
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/tailwint"><img src="https://img.shields.io/npm/v/tailwint?color=0ea5e9&label=npm" alt="npm version"></a>
7
+ <a href="https://github.com/peterwangsc/tailwint/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/tailwint?color=a78bfa" alt="license"></a>
8
+ <a href="https://www.npmjs.com/package/tailwint"><img src="https://img.shields.io/npm/dm/tailwint?color=f472b6" alt="downloads"></a>
9
+ </p>
4
10
 
5
- Works with Tailwind CSS v4.
11
+ ---
12
+
13
+ The same diagnostics VS Code shows — but from the command line. Catches class conflicts, suggests canonical rewrites, and auto-fixes everything. Built on the official `@tailwindcss/language-server`.
14
+
15
+ **Works with Tailwind CSS v4.**
16
+
17
+ ## What it catches
18
+
19
+ tailwint detects two categories of issues:
20
+
21
+ **⚡ Conflicts** — classes that apply the same CSS properties, where the last one wins and the rest are dead code:
22
+
23
+ ```
24
+ ⚡ 3:21 conflict 'w-full' applies the same CSS properties as 'w-auto'
25
+ ⚡ 3:28 conflict 'w-auto' applies the same CSS properties as 'w-full'
26
+ ```
27
+
28
+ **○ Canonical** — classes that can be written in a shorter or more idiomatic form:
29
+
30
+ ```
31
+ ○ 3:21 canonical The class `flex-shrink-0` can be written as `shrink-0`
32
+ ○ 3:35 canonical The class `z-[1]` can be written as `z-1`
33
+ ○ 3:41 canonical The class `min-w-[200px]` can be written as `min-w-50`
34
+ ```
6
35
 
7
36
  ## Install
8
37
 
@@ -13,18 +42,82 @@ npm install -D tailwint @tailwindcss/language-server
13
42
  ## Usage
14
43
 
15
44
  ```bash
16
- # Check default file types (tsx, jsx, html, vue, svelte)
45
+ # Scan default file types (tsx, jsx, html, vue, svelte, css)
17
46
  npx tailwint
18
47
 
19
- # Check specific files
48
+ # Scan specific files
20
49
  npx tailwint "src/**/*.tsx"
21
50
 
22
- # Auto-fix issues
51
+ # Auto-fix all issues
23
52
  npx tailwint --fix
24
53
 
25
54
  # Fix specific files
26
55
  npx tailwint --fix "app/**/*.tsx"
56
+
57
+ # Verbose LSP logging
58
+ DEBUG=1 npx tailwint
59
+ ```
60
+
61
+ ## Example output
62
+
27
63
  ```
64
+ ~≈∼〜~≈∼〜~≈ tailwint ∼〜~≈∼〜~≈∼~
65
+ tailwind css linter // powered by the official lsp
66
+
67
+ ✔ language server ready ~≈∼〜~≈∼〜~≈∼〜
68
+ ✔ 42 files analyzed ~≈∼〜~≈∼〜~≈∼〜~≈
69
+
70
+ 42 files scanned // 8 conflicts │ 12 canonical
71
+
72
+ ┌ components/Card.tsx (3)
73
+ ⚡ 5:21 conflict 'w-full' applies the same CSS properties as 'w-auto'
74
+ ○ 5:35 canonical The class `flex-shrink-0` can be written as `shrink-0`
75
+ ○ 5:49 canonical The class `z-[1]` can be written as `z-1`
76
+ └~≈∼
77
+
78
+ ≈∼〜~≈ ✘ FAIL 20 issues in 3 files 2.1s ≈∼〜~≈
79
+ run with --fix to auto-fix
80
+ ```
81
+
82
+ With `--fix`:
83
+
84
+ ```
85
+ ⚙ FIX conflicts first, then canonical
86
+
87
+ ✔ ┃━━━━━━━━━━━━━━━━━━┃ Card.tsx 3 fixed
88
+ ✔ ┃━━━━━━━━━━━━━━━━━━┃ Header.tsx 12 fixed
89
+ ✔ ┃━━━━━━━━━━━━━━━━━━┃ Sidebar.tsx 5 fixed
90
+
91
+ ≈∼〜~≈ ✔ FIXED 20 of 20 issues across 3 files 3.4s ≈∼〜~≈
92
+ ```
93
+
94
+ ## Supported file types
95
+
96
+ | Extension | Language ID | Notes |
97
+ |-----------|-----------|-------|
98
+ | `.tsx` | typescriptreact | React / Next.js components |
99
+ | `.jsx` | javascriptreact | React components |
100
+ | `.html` | html | Static HTML files |
101
+ | `.vue` | html | Vue single-file components |
102
+ | `.svelte` | html | Svelte components |
103
+ | `.css` | css | `@apply` directives and Tailwind at-rules |
104
+
105
+ ## Tailwind v4 support
106
+
107
+ tailwint fully supports Tailwind CSS v4 features:
108
+
109
+ - **Opacity shorthand** — `bg-red-500/50`, `text-blue-500/75`
110
+ - **`size-*` utility** — `size-10`, `size-full`
111
+ - **Container queries** — `@container`, `@lg:flex`, `@md:grid`
112
+ - **`has-*` / `not-*` variants** — `has-checked:bg-blue-500`, `not-disabled:opacity-100`
113
+ - **`aria-*` variants** — `aria-expanded:bg-blue-500`, `aria-disabled:opacity-50`
114
+ - **`data-*` variants** — `data-[state=open]:bg-blue-500`
115
+ - **`supports-*` variants** — `supports-[display:grid]:grid`
116
+ - **`forced-colors` variant** — `forced-colors:bg-[ButtonFace]`
117
+ - **Logical properties** — `ms-4`, `me-4`, `ps-4`, `pe-4`
118
+ - **Text wrap utilities** — `text-balance`, `text-pretty`, `text-nowrap`
119
+ - **Named groups/peers** — `group/sidebar`, `group-hover/sidebar:bg-blue-500`
120
+ - **CSS-first config** — `@import "tailwindcss"` with `@theme` directive
28
121
 
29
122
  ## Programmatic API
30
123
 
@@ -38,9 +131,54 @@ const exitCode = await run({
38
131
  });
39
132
  ```
40
133
 
134
+ ### Options
135
+
136
+ | Option | Type | Default | Description |
137
+ |--------|------|---------|-------------|
138
+ | `patterns` | `string[]` | `["**/*.{tsx,jsx,html,vue,svelte,css}"]` | Glob patterns for files to scan |
139
+ | `fix` | `boolean` | `false` | Auto-fix issues using LSP code actions |
140
+ | `cwd` | `string` | `process.cwd()` | Working directory for glob resolution and LSP root |
141
+
142
+ ### Exports
143
+
144
+ | Export | Description |
145
+ |--------|-------------|
146
+ | `run(options?)` | Run the linter, returns exit code |
147
+ | `applyEdits(content, edits)` | Apply LSP text edits to a string |
148
+ | `TextEdit` | TypeScript type for LSP text edits |
149
+
150
+ ## CI integration
151
+
152
+ tailwint exits with meaningful codes for CI pipelines:
153
+
154
+ | Exit code | Meaning |
155
+ |-----------|---------|
156
+ | `0` | No issues found, or all issues fixed with `--fix` |
157
+ | `1` | Issues found (without `--fix`) |
158
+ | `2` | Fatal error (language server not found, crash) |
159
+
160
+ ### GitHub Actions
161
+
162
+ ```yaml
163
+ - name: Lint Tailwind classes
164
+ run: npx tailwint
165
+ ```
166
+
167
+ ### Pre-commit hook
168
+
169
+ ```bash
170
+ npx tailwint --fix && git add -u
171
+ ```
172
+
41
173
  ## How it works
42
174
 
43
- tailwint spawns the official Tailwind CSS language server over stdio, opens your files via LSP, and collects the published diagnostics. In `--fix` mode it requests quickfix code actions and applies the resulting edits.
175
+ 1. **Boot** spawns `@tailwindcss/language-server` over stdio
176
+ 2. **Open** — sends all matched files to the server via `textDocument/didOpen`
177
+ 3. **Analyze** — waits for `textDocument/publishDiagnostics` notifications (event-driven, no polling)
178
+ 4. **Report** — collects diagnostics, categorizes as conflicts or canonical
179
+ 5. **Fix** *(if `--fix`)* — requests `textDocument/codeAction` quickfixes and applies edits in a loop until no diagnostics remain
180
+
181
+ The fix loop is unbounded — it keeps applying edits until the file stabilizes. A single pass may not resolve everything (e.g., fixing a conflict can reveal a canonical issue underneath), so the loop continues as long as edits produce changes.
44
182
 
45
183
  ## Requirements
46
184
 
package/bin/tailwint.js CHANGED
@@ -1,2 +1,16 @@
1
1
  #!/usr/bin/env node
2
- import "../dist/index.js";
2
+ import { run } from "../dist/index.js";
3
+ import { c, isTTY } from "../dist/ui.js";
4
+
5
+ const args = process.argv.slice(2);
6
+ const fix = args.includes("--fix");
7
+ const patterns = args.filter((a) => a !== "--fix");
8
+
9
+ run({ fix, patterns: patterns.length > 0 ? patterns : undefined }).then(
10
+ (code) => process.exit(code),
11
+ (err) => {
12
+ console.error(`\n ${c.red}${c.bold}tailwint crashed:${c.reset} ${err}`);
13
+ process.stderr.write(isTTY ? "\x1b[?25h" : "");
14
+ process.exit(2);
15
+ },
16
+ );
package/dist/index.js CHANGED
@@ -13,10 +13,10 @@ import { readFileSync } from "fs";
13
13
  import { glob } from "glob";
14
14
  import { startServer, send, notify, shutdown, fileUri, langId, diagnosticsReceived, waitForProjectReady, waitForDiagnosticCount, resetState, } from "./lsp.js";
15
15
  import { fixFile } from "./edits.js";
16
- import { c, isTTY, setTitle, windTrail, braille, windWave, dots, tick, advanceTick, startSpinner, progressBar, banner, fileBadge, diagLine, rainbowText, celebrationAnimation, } from "./ui.js";
16
+ import { c, setTitle, windTrail, braille, windWave, dots, tick, advanceTick, startSpinner, progressBar, banner, fileBadge, diagLine, rainbowText, celebrationAnimation, } from "./ui.js";
17
17
  // Re-export for tests
18
18
  export { applyEdits } from "./edits.js";
19
- const DEFAULT_PATTERNS = ["**/*.{tsx,jsx,html,vue,svelte}"];
19
+ const DEFAULT_PATTERNS = ["**/*.{tsx,jsx,html,vue,svelte,css}"];
20
20
  export async function run(options = {}) {
21
21
  resetState();
22
22
  const t0 = Date.now();
@@ -194,17 +194,3 @@ export async function run(options = {}) {
194
194
  await shutdown();
195
195
  return 1;
196
196
  }
197
- // CLI entry point — only runs when executed directly, not when imported
198
- const isCLI = process.argv[1] != null &&
199
- (import.meta.url === new URL(process.argv[1], "file:").href ||
200
- process.argv[1].endsWith("/tailwint.js"));
201
- if (isCLI) {
202
- const args = process.argv.slice(2);
203
- const fix = args.includes("--fix");
204
- const patterns = args.filter((a) => a !== "--fix");
205
- run({ fix, patterns: patterns.length > 0 ? patterns : undefined }).then((code) => process.exit(code), (err) => {
206
- console.error(`\n ${c.red}${c.bold}tailwint crashed:${c.reset} ${err}`);
207
- process.stderr.write(isTTY ? "\x1b[?25h" : "");
208
- process.exit(2);
209
- });
210
- }
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "tailwint",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Tailwind CSS linter for CI — drives the official language server to catch class issues and auto-fix them",
5
5
  "license": "MIT",
6
6
  "author": "Peter Wang",
7
+ "homepage": "https://github.com/peterwangsc/tailwint",
8
+ "bugs": "https://github.com/peterwangsc/tailwint/issues",
7
9
  "repository": {
8
10
  "type": "git",
9
11
  "url": "git+https://github.com/peterwangsc/tailwint.git"
@@ -14,25 +16,35 @@
14
16
  "lint",
15
17
  "linter",
16
18
  "ci",
19
+ "autofix",
17
20
  "language-server",
18
- "lsp"
21
+ "lsp",
22
+ "tailwind-v4",
23
+ "css",
24
+ "diagnostics",
25
+ "code-quality"
19
26
  ],
20
27
  "type": "module",
21
28
  "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
22
30
  "exports": {
23
- ".": "./dist/index.js"
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ }
24
35
  },
25
36
  "bin": {
26
- "tailwint": "./bin/tailwint.js"
37
+ "tailwint": "bin/tailwint.js"
27
38
  },
28
39
  "files": [
29
40
  "bin",
30
41
  "dist"
31
42
  ],
32
43
  "scripts": {
44
+ "clean": "rm -rf dist",
33
45
  "build": "tsc",
34
46
  "test": "tsx --test src/*.test.ts",
35
- "prepublishOnly": "npm run build"
47
+ "prepublishOnly": "npm run clean && npm run build"
36
48
  },
37
49
  "dependencies": {
38
50
  "glob": "^13.0.6"