tailwint 1.1.7 → 1.1.9

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/dist/edits.js CHANGED
@@ -1,109 +1,2 @@
1
- /**
2
- * Text edit application and fix orchestration.
3
- */
4
- import { writeFileSync } from "fs";
5
- import { send, notify, fileUri, waitForDiagnostic } from "./lsp.js";
6
- export function applyEdits(content, edits) {
7
- if (edits.length === 0)
8
- return content;
9
- const lineOffsets = [0];
10
- for (let i = 0; i < content.length; i++) {
11
- if (content[i] === "\n")
12
- lineOffsets.push(i + 1);
13
- }
14
- function toOffset(line, char) {
15
- if (line >= lineOffsets.length)
16
- return content.length;
17
- return Math.min(lineOffsets[line] + char, content.length);
18
- }
19
- const absolute = edits.map((e) => ({
20
- start: toOffset(e.range.start.line, e.range.start.character),
21
- end: toOffset(e.range.end.line, e.range.end.character),
22
- newText: e.newText,
23
- }));
24
- absolute.sort((a, b) => a.start - b.start);
25
- const parts = [];
26
- let cursor = 0;
27
- for (const e of absolute) {
28
- if (e.start > cursor)
29
- parts.push(content.slice(cursor, e.start));
30
- parts.push(e.newText);
31
- cursor = e.end;
32
- }
33
- if (cursor < content.length)
34
- parts.push(content.slice(cursor));
35
- return parts.join("");
36
- }
37
- function rangeKey(range) {
38
- return `${range.start.line}:${range.start.character}-${range.end.line}:${range.end.character}`;
39
- }
40
- function posLte(a, b) {
41
- return a.line < b.line || (a.line === b.line && a.character <= b.character);
42
- }
43
- function rangeContains(outer, inner) {
44
- return posLte(outer.range.start, inner.range.start) && posLte(inner.range.end, outer.range.end);
45
- }
46
- function filterContainedEdits(edits) {
47
- const result = [];
48
- for (const edit of edits) {
49
- const containedByAnother = edits.some((other) => other !== edit && rangeContains(other, edit));
50
- if (!containedByAnother)
51
- result.push(edit);
52
- }
53
- return result;
54
- }
55
- async function waitForFreshDiagnostics(uri) {
56
- return waitForDiagnostic(uri);
57
- }
58
- export async function fixFile(filePath, initialDiags, fileContents, version, onPass) {
59
- const DEBUG = process.env.DEBUG === "1";
60
- const uri = fileUri(filePath);
61
- let content = fileContents.get(filePath);
62
- let ver = version.get(filePath);
63
- const issueCount = initialDiags.length;
64
- let diags = initialDiags;
65
- for (let pass = 0; diags.length > 0; pass++) {
66
- onPass?.(pass + 1);
67
- if (DEBUG)
68
- console.error(` pass ${pass + 1}: ${diags.length} remaining`);
69
- const actionResults = await Promise.all(diags.map((diag) => send("textDocument/codeAction", {
70
- textDocument: { uri },
71
- range: diag.range,
72
- context: { diagnostics: [diag], only: ["quickfix"] },
73
- }).catch(() => null)));
74
- const editsByRange = new Map();
75
- for (let i = 0; i < diags.length; i++) {
76
- const actions = actionResults[i];
77
- if (!actions || actions.length === 0)
78
- continue;
79
- const action = actions[0];
80
- const edits = action.edit?.changes?.[uri] || action.edit?.documentChanges?.[0]?.edits || [];
81
- if (edits.length === 0)
82
- continue;
83
- for (const e of edits) {
84
- const key = rangeKey(e.range);
85
- editsByRange.set(key, e);
86
- }
87
- }
88
- let candidates = [...editsByRange.values()];
89
- if (candidates.length === 0)
90
- break;
91
- candidates = filterContainedEdits(candidates);
92
- const prev = content;
93
- content = applyEdits(content, candidates);
94
- if (content === prev)
95
- break;
96
- ver++;
97
- notify("textDocument/didChange", {
98
- textDocument: { uri, version: ver },
99
- contentChanges: [{ text: content }],
100
- });
101
- diags = (await waitForFreshDiagnostics(uri)).filter((d) => d.severity === 1 || d.severity === 2);
102
- }
103
- if (content !== fileContents.get(filePath)) {
104
- writeFileSync(filePath, content);
105
- fileContents.set(filePath, content);
106
- version.set(filePath, ver);
107
- }
108
- return issueCount;
109
- }
1
+ import{writeFileSync as v}from"fs";import{send as D,notify as $,fileUri as B,waitForDiagnostic as k}from"./lsp.js";function C(t,n){if(n.length===0)return t;const r=[0];for(let e=0;e<t.length;e++)t[e]===`
2
+ `&&r.push(e+1);function c(e,o){return e>=r.length?t.length:Math.min(r[e]+o,t.length)}const a=n.map(e=>({start:c(e.range.start.line,e.range.start.character),end:c(e.range.end.line,e.range.end.character),newText:e.newText}));a.sort((e,o)=>e.start-o.start);const u=[];let i=0;for(const e of a)e.start>i&&u.push(t.slice(i,e.start)),u.push(e.newText),i=e.end;return i<t.length&&u.push(t.slice(i)),u.join("")}function M(t){return`${t.start.line}:${t.start.character}-${t.end.line}:${t.end.character}`}function b(t,n){return t.line<n.line||t.line===n.line&&t.character<=n.character}function A(t,n){return b(t.range.start,n.range.start)&&b(n.range.end,t.range.end)}function F(t){const n=[];for(const r of t)t.some(a=>a!==r&&A(a,r))||n.push(r);return n}async function O(t,n,r,c,a){const u=process.env.DEBUG==="1",i=B(t);let e=r.get(t),o=c.get(t);const y=n.length;let g=n;for(let d=0;g.length>0;d++){a?.(d+1),u&&console.error(` pass ${d+1}: ${g.length} remaining`);const E=await Promise.all(g.map(s=>D("textDocument/codeAction",{textDocument:{uri:i},range:s.range,context:{diagnostics:[s],only:["quickfix"]}}).catch(()=>null))),m=new Map;for(let s=0;s<g.length;s++){const f=E[s];if(!f||f.length===0)continue;const h=f[0],x=h.edit?.changes?.[i]||h.edit?.documentChanges?.[0]?.edits||[];if(x.length!==0)for(const p of x){const w=M(p.range);m.set(w,p)}}let l=[...m.values()];if(l.length===0)break;l=F(l);const T=e;if(e=C(e,l),e===T)break;o++,$("textDocument/didChange",{textDocument:{uri:i,version:o},contentChanges:[{text:e}]}),g=(await k(i)).filter(s=>s.severity===1||s.severity===2)}return e!==r.get(t)&&(v(t,e),r.set(t,e),c.set(t,o)),y}export{C as applyEdits,O as fixFile};
package/dist/index.js CHANGED
@@ -1,270 +1,2 @@
1
- /**
2
- * tailwint Tailwind CSS linter powered by the official language server.
3
- *
4
- * Usage: tailwint [--fix] [glob...]
5
- * tailwint # default: **\/*.{tsx,jsx,html,vue,svelte,astro,mdx,css}
6
- * tailwint --fix # auto-fix all issues
7
- * tailwint "src/**\/*.tsx" # custom glob
8
- *
9
- * Set DEBUG=1 for verbose LSP message logging.
10
- */
11
- import { resolve, relative } from "path";
12
- import { readFileSync } from "fs";
13
- import { glob } from "glob";
14
- import { startServer, send, notify, shutdown, fileUri, langId, diagnosticsReceived, settledProjects, brokenProjects, warnings, waitForAllProjects, resetState, } from "./lsp.js";
15
- import { fixFile } from "./edits.js";
16
- import { prescanCssFiles } from "./prescan.js";
17
- import { c, setTitle, windTrail, braille, windWave, dots, tick, advanceTick, startSpinner, progressBar, banner, fileBadge, diagLine, rainbowText, celebrationAnimation, } from "./ui.js";
18
- // Re-export for tests
19
- export { applyEdits } from "./edits.js";
20
- const DEFAULT_PATTERNS = ["**/*.{tsx,jsx,html,vue,svelte,astro,mdx,css}"];
21
- export async function run(options = {}) {
22
- resetState();
23
- const t0 = Date.now();
24
- const cwd = resolve(options.cwd || process.cwd());
25
- const fix = options.fix ?? false;
26
- const patterns = options.patterns ?? DEFAULT_PATTERNS;
27
- const fileSet = new Set();
28
- for (const pattern of patterns) {
29
- const matches = await glob(pattern, {
30
- cwd,
31
- absolute: true,
32
- nodir: true,
33
- ignore: [
34
- "**/node_modules/**",
35
- "**/dist/**",
36
- "**/build/**",
37
- "**/out/**",
38
- "**/coverage/**",
39
- "**/public/**",
40
- "**/tmp/**",
41
- "**/.tmp/**",
42
- "**/.cache/**",
43
- "**/vendor/**",
44
- "**/storybook-static/**",
45
- "**/.next/**",
46
- "**/.nuxt/**",
47
- "**/.output/**",
48
- "**/.svelte-kit/**",
49
- "**/.astro/**",
50
- "**/.vercel/**",
51
- "**/.expo/**",
52
- ],
53
- });
54
- for (const m of matches)
55
- fileSet.add(m);
56
- }
57
- const files = [...fileSet];
58
- await banner();
59
- if (files.length === 0) {
60
- const VP = 54;
61
- const l1 = "the wind blows";
62
- const l2 = "but there is nothing here";
63
- const l3 = "~ no files matched ~";
64
- const p1 = 4;
65
- const p2 = Math.floor((VP - l2.length - 2) / 2);
66
- const p3 = VP - l3.length - 8;
67
- const lw1 = p1, rw1 = VP - p1 - l1.length - 2;
68
- const lw2 = p2, rw2 = VP - p2 - l2.length - 2;
69
- const lw3 = p3, rw3 = VP - p3 - l3.length - 2;
70
- console.error(` ${windTrail(lw1)} ${c.dim}${l1}${c.reset} ${windTrail(rw1, 4)}`);
71
- console.error(` ${windTrail(lw2, 2)} ${c.dim}${l2}${c.reset} ${windTrail(rw2, 7)}`);
72
- console.error(` ${windTrail(lw3, 5)} ${c.dim}${c.italic}${l3}${c.reset} ${windTrail(rw3, 9)}`);
73
- console.error("");
74
- return 0;
75
- }
76
- // Phase 1: Boot the LSP
77
- setTitle("tailwint ~ booting...");
78
- const stopBoot = startSpinner(() => {
79
- setTitle(`tailwint ~ booting${".".repeat(Date.now() % 4)}`);
80
- return ` ${braille()} ${c.dim}booting language server${dots()}${c.reset} ${windTrail(24, tick)}`;
81
- });
82
- startServer(cwd);
83
- await send("initialize", {
84
- processId: process.pid,
85
- rootUri: fileUri(cwd),
86
- capabilities: {
87
- textDocument: {
88
- publishDiagnostics: { relatedInformation: true },
89
- codeAction: {
90
- codeActionLiteralSupport: {
91
- codeActionKind: { valueSet: ["quickfix"] },
92
- },
93
- },
94
- },
95
- workspace: { workspaceFolders: true, configuration: true },
96
- },
97
- workspaceFolders: [{ uri: fileUri(cwd), name: "workspace" }],
98
- });
99
- notify("initialized", {});
100
- stopBoot();
101
- console.error(` ${c.green}\u2714${c.reset} ${c.dim}language server ready${c.reset} ${windTrail(30)}`);
102
- // Pre-scan CSS files to predict project count
103
- const prescan = prescanCssFiles(files);
104
- // Open found files — triggers the server's project discovery
105
- const fileContents = new Map();
106
- const fileVersions = new Map();
107
- const found = files.length;
108
- const skipped = prescan.unrelatedCssFiles.size;
109
- let sent = 0;
110
- for (const filePath of files) {
111
- // Skip CSS files with no Tailwind signals — sending them wastes server CPU
112
- if (prescan.unrelatedCssFiles.has(filePath))
113
- continue;
114
- let content;
115
- try {
116
- content = readFileSync(filePath, "utf-8");
117
- }
118
- catch {
119
- continue; // file may have been deleted between glob and read
120
- }
121
- fileContents.set(filePath, content);
122
- fileVersions.set(filePath, 1);
123
- notify("textDocument/didOpen", {
124
- textDocument: {
125
- uri: fileUri(filePath),
126
- languageId: langId(filePath),
127
- version: 1,
128
- text: content,
129
- },
130
- });
131
- sent++;
132
- }
133
- const sentText = `sent ${sent} file${sent === 1 ? "" : "s"} to lsp`;
134
- const sentPad = 54 - 2 - sentText.length - 1;
135
- console.error(` ${c.green}\u2714${c.reset} ${c.dim}${sentText}${c.reset} ${windTrail(sentPad)}`);
136
- if (skipped > 0) {
137
- const skipText = `${skipped} file${skipped === 1 ? "" : "s"} skipped`;
138
- const skipPad = 54 - 2 - skipText.length - 1;
139
- console.error(` ${c.dim}\u2500${c.reset} ${c.dim}${skipText}${c.reset} ${windTrail(skipPad)}`);
140
- for (const f of prescan.unrelatedCssFiles) {
141
- const rel = relative(cwd, f);
142
- console.error(` ${c.dim}${fileBadge(rel)}${c.reset}`);
143
- }
144
- }
145
- // Wait for all projects to be resolved (settled or broken)
146
- setTitle("tailwint ~ scanning...");
147
- const stopScan = startSpinner(() => {
148
- const received = diagnosticsReceived.size;
149
- const resolved = settledProjects + brokenProjects;
150
- const label = received > 0 ? "scanning" : "initializing";
151
- setTitle(`tailwint ~ ${label} ${resolved}/${prescan.maxProjects}`);
152
- const pct = sent > 0 ? Math.round((received / sent) * 100) : 0;
153
- const bar = progressBar(pct, 18, true);
154
- const totalStr = String(sent);
155
- const recvStr = String(received).padStart(totalStr.length);
156
- const usedCols = 2 + 1 + 1 + 20 + 1 + label.length + 3 + 1 - 2;
157
- const waveCols = Math.max(0, 54 - usedCols);
158
- // labelCol = 2(indent) + 1(braille) + 1(space) + 20(bar) + 1(space) = 25
159
- // "received" starts at labelCol, numbers right-aligned before it
160
- const countStr = `${recvStr}/${totalStr}`;
161
- const countPad = " ".repeat(Math.max(0, 25 - countStr.length - 1));
162
- const recvLabel = "files received";
163
- const recvUsed = countPad.length + countStr.length + 1 + recvLabel.length + 1 - 2;
164
- const recvWave = Math.max(0, 54 - recvUsed);
165
- return ` ${braille()} ${bar} ${c.dim}${label}${dots()}${c.reset} ${windTrail(waveCols, tick)}\n${countPad}${c.bold}${countStr}${c.reset} ${c.dim}${recvLabel}${c.reset} ${windTrail(recvWave, tick + 3)}`;
166
- }, 80);
167
- await waitForAllProjects(prescan.predictedRoots, prescan.maxProjects);
168
- stopScan();
169
- // Log warnings for broken projects
170
- for (const w of warnings) {
171
- console.error(` ${c.yellow}\u26A0${c.reset} ${c.dim}${w}${c.reset}`);
172
- }
173
- const scanned = diagnosticsReceived.size;
174
- const scannedText = `${scanned}/${sent} files received`;
175
- const scannedPad = 54 - 2 - scannedText.length - 1;
176
- console.error(` ${c.green}\u2714${c.reset} ${c.dim}${scannedText}${c.reset} ${windTrail(scannedPad)}`);
177
- console.error("");
178
- // Collect issues
179
- let totalIssues = 0;
180
- const issuesByFile = new Map();
181
- for (const filePath of files) {
182
- const diags = diagnosticsReceived.get(fileUri(filePath)) || [];
183
- const meaningful = diags.filter((d) => d.severity === 1 || d.severity === 2);
184
- if (meaningful.length > 0) {
185
- issuesByFile.set(filePath, meaningful);
186
- totalIssues += meaningful.length;
187
- }
188
- }
189
- const conflicts = [...issuesByFile.values()]
190
- .flat()
191
- .filter((d) => d.code === "cssConflict").length;
192
- const canonical = totalIssues - conflicts;
193
- // All clear
194
- if (totalIssues === 0) {
195
- const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
196
- setTitle("tailwint \u2714 all clear");
197
- await celebrationAnimation();
198
- console.error(` ${c.green}\u2714${c.reset} ${c.bold}${scanned}${c.reset} files scanned ${c.dim}// ${rainbowText("all clear")} ${c.dim}${elapsed}s${c.reset}`);
199
- console.error("");
200
- await shutdown();
201
- return 0;
202
- }
203
- // Summary
204
- console.error(` ${c.bold}${c.white}${scanned}${c.reset} files scanned ${c.dim}//${c.reset} ${c.orange}${c.bold}${conflicts}${c.reset}${c.orange} conflicts${c.reset} ${c.dim}\u2502${c.reset} ${c.yellow}${c.bold}${canonical}${c.reset}${c.yellow} canonical${c.reset}`);
205
- console.error("");
206
- // Report
207
- let fileNum = 0;
208
- for (const [filePath, diags] of issuesByFile) {
209
- if (fileNum > 0)
210
- console.log(` ${c.dim}${windWave()}${c.reset}`);
211
- fileNum++;
212
- const rel = relative(cwd, filePath);
213
- console.log(` ${c.dim}\u250C${c.reset} ${fileBadge(rel)} ${c.dim}(${diags.length})${c.reset}`);
214
- for (const d of diags) {
215
- console.log(diagLine(d));
216
- }
217
- console.log(` ${c.dim}\u2514${windTrail(3)}${c.reset}`);
218
- advanceTick();
219
- }
220
- // Fix
221
- if (fix) {
222
- console.error("");
223
- console.error(` ${c.bgCyan}${c.bold} \u2699 FIX ${c.reset} ${c.dim}conflicts first, then canonical${c.reset}`);
224
- console.error("");
225
- let totalFixed = 0;
226
- let fileIdx = 0;
227
- for (const [filePath, diags] of issuesByFile) {
228
- fileIdx++;
229
- const rel = relative(cwd, filePath);
230
- let pass = 0;
231
- const shortName = rel.includes("/")
232
- ? rel.slice(rel.lastIndexOf("/") + 1)
233
- : rel;
234
- setTitle(`tailwint ~ fixing ${shortName} (${fileIdx}/${issuesByFile.size})`);
235
- const stopFix = startSpinner(() => {
236
- const pct = Math.round(((fileIdx - 1 + pass / 10) / issuesByFile.size) * 100);
237
- const bar = progressBar(pct, 18, true);
238
- const passText = `pass ${pass}`;
239
- const fixUsed = 2 + 20 + shortName.length + 1 + passText.length + 3 + 1;
240
- const fixWave = Math.max(0, 56 - fixUsed);
241
- return ` ${braille()} ${bar} ${c.bold}${c.white}${shortName}${c.reset} ${c.dim}${passText}${dots()}${c.reset} ${windTrail(fixWave, tick)}`;
242
- });
243
- const fixed = await fixFile(filePath, diags, fileContents, fileVersions, (p) => {
244
- pass = p;
245
- });
246
- stopFix();
247
- totalFixed += fixed;
248
- const pct = Math.round((fileIdx / issuesByFile.size) * 100);
249
- const bar = progressBar(pct, 18);
250
- console.error(` ${c.green}\u2714${c.reset} ${bar} ${c.bold}${c.white}${shortName}${c.reset} ${c.green}${diags.length} fixed${c.reset}`);
251
- }
252
- console.error("");
253
- const fixElapsed = ((Date.now() - t0) / 1000).toFixed(1);
254
- setTitle(`tailwint \u2714 fixed ${totalFixed} issues`);
255
- await celebrationAnimation();
256
- console.error(` ${windWave()} ${c.bgGreen}${c.bold} \u2714 FIXED ${c.reset} ${c.green}${c.bold}${totalFixed}${c.reset} of ${c.bold}${totalIssues}${c.reset} issues across ${c.bold}${issuesByFile.size}${c.reset} files ${c.dim}${fixElapsed}s${c.reset} ${windWave()}`);
257
- console.error("");
258
- await shutdown();
259
- return 0;
260
- }
261
- // Fail
262
- const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
263
- setTitle(`tailwint \u2718 ${totalIssues} issues`);
264
- console.log("");
265
- console.log(` ${windWave()} ${c.bgRed}${c.bold} \u2718 FAIL ${c.reset} ${c.red}${c.bold}${totalIssues}${c.reset} issues in ${c.bold}${issuesByFile.size}${c.reset} files ${c.dim}${elapsed}s${c.reset} ${windWave()}`);
266
- console.log(` ${c.dim}run with ${c.white}--fix${c.dim} to auto-fix${c.reset}`);
267
- console.log("");
268
- await shutdown();
269
- return 1;
270
- }
1
+ import{resolve as ne,relative as M}from"path";import{readFileSync as re}from"fs";import{glob as ie}from"glob";import{startServer as le,send as ce,notify as _,shutdown as C,fileUri as F,langId as $e,diagnosticsReceived as I,settledProjects as ae,brokenProjects as de,warnings as fe,waitForAllProjects as ge,resetState as ue}from"./lsp.js";import{fixFile as pe}from"./edits.js";import{prescanCssFiles as me}from"./prescan.js";import{c as e,setTitle as a,windTrail as r,braille as A,windWave as v,dots as V,tick as y,advanceTick as we,startSpinner as j,progressBar as E,banner as he,fileBadge as q,diagLine as xe,rainbowText as be,celebrationAnimation as G}from"./ui.js";import{applyEdits as Ae}from"./edits.js";const ve=["**/*.{tsx,jsx,html,vue,svelte,astro,mdx,css}"];async function Me(S={}){ue();const T=Date.now(),d=ne(S.cwd||process.cwd()),K=S.fix??!1,Y=S.patterns??ve,L=new Set;for(const t of Y){const o=await ie(t,{cwd:d,absolute:!0,nodir:!0,ignore:["**/node_modules/**","**/dist/**","**/build/**","**/out/**","**/coverage/**","**/public/**","**/tmp/**","**/.tmp/**","**/.cache/**","**/vendor/**","**/storybook-static/**","**/.next/**","**/.nuxt/**","**/.output/**","**/.svelte-kit/**","**/.astro/**","**/.vercel/**","**/.expo/**"]});for(const s of o)L.add(s)}const P=[...L];if(await he(),P.length===0){const o="the wind blows",s="but there is nothing here",n="~ no files matched ~",i=Math.floor((54-s.length-2)/2),c=54-n.length-8,$=4,b=50-o.length-2,g=i,w=54-i-s.length-2,h=c,u=54-c-n.length-2;return console.error(` ${r($)} ${e.dim}${o}${e.reset} ${r(b,4)}`),console.error(` ${r(g,2)} ${e.dim}${s}${e.reset} ${r(w,7)}`),console.error(` ${r(h,5)} ${e.dim}${e.italic}${n}${e.reset} ${r(u,9)}`),console.error(""),0}a("tailwint ~ booting...");const H=j(()=>(a(`tailwint ~ booting${".".repeat(Date.now()%4)}`),` ${A()} ${e.dim}booting language server${V()}${e.reset} ${r(24,y)}`));le(d),await ce("initialize",{processId:process.pid,rootUri:F(d),capabilities:{textDocument:{publishDiagnostics:{relatedInformation:!0},codeAction:{codeActionLiteralSupport:{codeActionKind:{valueSet:["quickfix"]}}}},workspace:{workspaceFolders:!0,configuration:!0}},workspaceFolders:[{uri:F(d),name:"workspace"}]}),_("initialized",{}),H(),console.error(` ${e.green}\u2714${e.reset} ${e.dim}language server ready${e.reset} ${r(30)}`);const p=me(P),U=new Map,B=new Map,k=p.unrelatedCssFiles.size;let f=0;for(const t of P){if(p.unrelatedCssFiles.has(t))continue;let o;try{o=re(t,"utf-8")}catch{continue}U.set(t,o),B.set(t,1),_("textDocument/didOpen",{textDocument:{uri:F(t),languageId:$e(t),version:1,text:o}}),f++}const O=`sent ${f} file${f===1?"":"s"} to lsp`,J=52-O.length-1;if(console.error(` ${e.green}\u2714${e.reset} ${e.dim}${O}${e.reset} ${r(J)}`),k>0){const t=`${k} file${k===1?"":"s"} skipped`,o=52-t.length-1;console.error(` ${e.dim}\u2500${e.reset} ${e.dim}${t}${e.reset} ${r(o)}`);for(const s of p.unrelatedCssFiles){const n=M(d,s);console.error(` ${e.dim}${q(n)}${e.reset}`)}}a("tailwint ~ scanning...");const Q=j(()=>{const t=I.size,o=ae+de,s=t>0?"scanning":"initializing";a(`tailwint ~ ${s} ${o}/${p.maxProjects}`);const n=f>0?Math.round(t/f*100):0,x=E(n,18,!0),i=String(f),c=String(t).padStart(i.length),$=25+s.length+3+1-2,b=Math.max(0,54-$),g=`${c}/${i}`,w=" ".repeat(Math.max(0,25-g.length-1)),h="files received",u=w.length+g.length+1+h.length+1-2,D=Math.max(0,54-u);return` ${A()} ${x} ${e.dim}${s}${V()}${e.reset} ${r(b,y)}
2
+ ${w}${e.bold}${g}${e.reset} ${e.dim}${h}${e.reset} ${r(D,y+3)}`},80);await ge(p.predictedRoots,p.maxProjects),Q();for(const t of fe)console.error(` ${e.yellow}\u26A0${e.reset} ${e.dim}${t}${e.reset}`);const z=I.size,R=`${z}/${f} files received`,Z=52-R.length-1;console.error(` ${e.green}\u2714${e.reset} ${e.dim}${R}${e.reset} ${r(Z)}`),console.error("");let m=0;const l=new Map;for(const t of P){const s=(I.get(F(t))||[]).filter(n=>n.severity===1||n.severity===2);s.length>0&&(l.set(t,s),m+=s.length)}const N=[...l.values()].flat().filter(t=>t.code==="cssConflict").length,ee=m-N;if(m===0){const t=((Date.now()-T)/1e3).toFixed(1);return a("tailwint \u2714 all clear"),await G(),console.error(` ${e.green}\u2714${e.reset} ${e.bold}${z}${e.reset} files scanned ${e.dim}// ${be("all clear")} ${e.dim}${t}s${e.reset}`),console.error(""),await C(),0}console.error(` ${e.bold}${e.white}${z}${e.reset} files scanned ${e.dim}//${e.reset} ${e.orange}${e.bold}${N}${e.reset}${e.orange} conflicts${e.reset} ${e.dim}\u2502${e.reset} ${e.yellow}${e.bold}${ee}${e.reset}${e.yellow} canonical${e.reset}`),console.error("");let W=0;for(const[t,o]of l){W>0&&console.log(` ${e.dim}${v()}${e.reset}`),W++;const s=M(d,t);console.log(` ${e.dim}\u250C${e.reset} ${q(s)} ${e.dim}(${o.length})${e.reset}`);for(const n of o)console.log(xe(n));console.log(` ${e.dim}\u2514${r(3)}${e.reset}`),we()}if(K){console.error(""),console.error(` ${e.bgCyan}${e.bold} \u2699 FIX ${e.reset} ${e.dim}conflicts first, then canonical${e.reset}`),console.error("");let t=0,o=0;for(const[n,x]of l){o++;const i=M(d,n);let c=0;const $=i.includes("/")?i.slice(i.lastIndexOf("/")+1):i;a(`tailwint ~ fixing ${$} (${o}/${l.size})`);const b=j(()=>{const u=Math.round((o-1+c/10)/l.size*100),D=E(u,18,!0),X=`pass ${c}`,oe=22+$.length+1+X.length+3+1,se=Math.max(0,56-oe);return` ${A()} ${D} ${e.bold}${e.white}${$}${e.reset} ${e.dim}${X}${V()}${e.reset} ${r(se,y)}`}),g=await pe(n,x,U,B,u=>{c=u});b(),t+=g;const w=Math.round(o/l.size*100),h=E(w,18);console.error(` ${e.green}\u2714${e.reset} ${h} ${e.bold}${e.white}${$}${e.reset} ${e.green}${x.length} fixed${e.reset}`)}console.error("");const s=((Date.now()-T)/1e3).toFixed(1);return a(`tailwint \u2714 fixed ${t} issues`),await G(),console.error(` ${v()} ${e.bgGreen}${e.bold} \u2714 FIXED ${e.reset} ${e.green}${e.bold}${t}${e.reset} of ${e.bold}${m}${e.reset} issues across ${e.bold}${l.size}${e.reset} files ${e.dim}${s}s${e.reset} ${v()}`),console.error(""),await C(),0}const te=((Date.now()-T)/1e3).toFixed(1);return a(`tailwint \u2718 ${m} issues`),console.log(""),console.log(` ${v()} ${e.bgRed}${e.bold} \u2718 FAIL ${e.reset} ${e.red}${e.bold}${m}${e.reset} issues in ${e.bold}${l.size}${e.reset} files ${e.dim}${te}s${e.reset} ${v()}`),console.log(` ${e.dim}run with ${e.white}--fix${e.dim} to auto-fix${e.reset}`),console.log(""),await C(),1}export{Ae as applyEdits,Me as run};
package/dist/lsp.js CHANGED
@@ -1,487 +1,7 @@
1
- /**
2
- * LSP client — spawns tailwindcss-language-server over stdio and speaks JSON-RPC.
3
- */
4
- import { spawn } from "child_process";
5
- import { resolve } from "path";
6
- import { existsSync, readFileSync } from "fs";
7
- const DEBUG = process.env.DEBUG === "1";
8
- let workspaceRoot = "";
9
- let vscodeSettings = null;
10
- /** Load .vscode/settings.json once, cache the result. */
11
- function loadVscodeSettings() {
12
- if (vscodeSettings !== null)
13
- return vscodeSettings;
14
- const settingsPath = resolve(workspaceRoot, ".vscode/settings.json");
15
- if (!existsSync(settingsPath)) {
16
- vscodeSettings = {};
17
- return vscodeSettings;
18
- }
19
- try {
20
- // Strip single-line comments (// ...) and trailing commas for JSON compat
21
- const raw = readFileSync(settingsPath, "utf-8")
22
- .replace(/\/\/[^\n]*/g, "")
23
- .replace(/,\s*([\]}])/g, "$1");
24
- vscodeSettings = JSON.parse(raw);
25
- }
26
- catch {
27
- vscodeSettings = {};
28
- }
29
- return vscodeSettings;
30
- }
31
- /**
32
- * Extract a section from flat VS Code settings into a nested object.
33
- * e.g. section "tailwindCSS" turns { "tailwindCSS.lint.cssConflict": "error" }
34
- * into { lint: { cssConflict: "error" } }
35
- */
36
- function getSettingsSection(section) {
37
- const settings = loadVscodeSettings();
38
- const prefix = section + ".";
39
- const result = {};
40
- for (const [key, value] of Object.entries(settings)) {
41
- if (!key.startsWith(prefix))
42
- continue;
43
- const path = key.slice(prefix.length).split(".");
44
- let target = result;
45
- for (let i = 0; i < path.length - 1; i++) {
46
- if (!(path[i] in target) || typeof target[path[i]] !== "object") {
47
- target[path[i]] = {};
48
- }
49
- target = target[path[i]];
50
- }
51
- target[path[path.length - 1]] = value;
52
- }
53
- return result;
54
- }
55
- let server;
56
- let serverDead = false;
57
- let msgId = 0;
58
- const chunks = [];
59
- let chunksLen = 0;
60
- const pending = new Map();
61
- export const diagnosticsReceived = new Map();
62
- export let projectReady = false;
63
- // ---------------------------------------------------------------------------
64
- // Project-aware wait state
65
- // ---------------------------------------------------------------------------
66
- /** Tracking for projectInitialized events */
67
- export let projectInitCount = 0;
68
- export let settledProjects = 0;
69
- export let brokenProjects = 0;
70
- let lastInitMs = 0;
71
- let inBrokenSequence = false;
72
- let awaitingFirstDiag = false;
73
- let currentProjectDiagCount = 0;
74
- export const warnings = [];
75
- /** Internal waiter state */
76
- let projectWaitResolve = null;
77
- let diagDebounceTimer = null;
78
- let projectInitTimer = null;
79
- let outerTimer = null;
80
- const diagWaiters = new Map();
81
- /** Config for the current wait */
82
- let waitConfig = { predictedRoots: 0, maxProjects: 0, initTimeoutMs: 5000, debounceMs: 500 };
83
- /** Reset module state between runs (for programmatic multi-run usage). */
84
- export function resetState() {
85
- msgId = 0;
86
- serverDead = false;
87
- chunks.length = 0;
88
- chunksLen = 0;
89
- pending.clear();
90
- diagnosticsReceived.clear();
91
- projectReady = false;
92
- projectInitCount = 0;
93
- settledProjects = 0;
94
- brokenProjects = 0;
95
- lastInitMs = 0;
96
- inBrokenSequence = false;
97
- awaitingFirstDiag = false;
98
- currentProjectDiagCount = 0;
99
- warnings.length = 0;
100
- projectWaitResolve = null;
101
- if (diagDebounceTimer) {
102
- clearTimeout(diagDebounceTimer);
103
- diagDebounceTimer = null;
104
- }
105
- if (projectInitTimer) {
106
- clearTimeout(projectInitTimer);
107
- projectInitTimer = null;
108
- }
109
- if (outerTimer) {
110
- clearTimeout(outerTimer);
111
- outerTimer = null;
112
- }
113
- diagWaiters.clear();
114
- vscodeSettings = null;
115
- }
116
- function cleanupWaitTimers() {
117
- if (diagDebounceTimer) {
118
- clearTimeout(diagDebounceTimer);
119
- diagDebounceTimer = null;
120
- }
121
- if (projectInitTimer) {
122
- clearTimeout(projectInitTimer);
123
- projectInitTimer = null;
124
- }
125
- if (outerTimer) {
126
- clearTimeout(outerTimer);
127
- outerTimer = null;
128
- }
129
- }
130
- function finishWait() {
131
- if (!projectWaitResolve)
132
- return;
133
- const resolve = projectWaitResolve;
134
- projectWaitResolve = null;
135
- cleanupWaitTimers();
136
- resolve();
137
- }
138
- function isAllResolved() {
139
- const resolved = settledProjects + brokenProjects;
140
- return resolved >= waitConfig.maxProjects;
141
- }
142
- function startProjectInitTimeout() {
143
- if (projectInitTimer)
144
- clearTimeout(projectInitTimer);
145
- projectInitTimer = setTimeout(() => {
146
- // Timer fired — either no project init came, or we were waiting for
147
- // more diagnostics after a single early one. If we got any diagnostics
148
- // for the current project, settle it before finishing.
149
- if (currentProjectDiagCount > 0 && !awaitingFirstDiag) {
150
- settleCurrentProject();
151
- }
152
- else {
153
- finishWait();
154
- }
155
- }, waitConfig.initTimeoutMs);
156
- }
157
- function onProjectInitialized() {
158
- projectInitCount++;
159
- const now = Date.now();
160
- projectReady = true;
161
- if (lastInitMs > 0 && (now - lastInitMs) < 500) {
162
- // Rapid re-init — broken project
163
- if (!inBrokenSequence) {
164
- // First rapid init after a healthy one — the previous healthy init was actually broken
165
- inBrokenSequence = true;
166
- brokenProjects++;
167
- warnings.push("A CSS file failed to initialize (likely an @apply referencing an unknown utility). " +
168
- "That project's files will not receive diagnostics. " +
169
- "See https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1121");
170
- // The previous init was counted as starting a healthy project's diagnostic wait.
171
- // Cancel that wait — this project won't produce diagnostics.
172
- if (diagDebounceTimer) {
173
- clearTimeout(diagDebounceTimer);
174
- diagDebounceTimer = null;
175
- }
176
- }
177
- // Additional rapid inits for the same broken project — just update timestamp
178
- }
179
- else {
180
- // Healthy init — new project starting
181
- inBrokenSequence = false;
182
- awaitingFirstDiag = true;
183
- currentProjectDiagCount = 0;
184
- // Cancel any pending project-init timeout since we just got a new one
185
- if (projectInitTimer) {
186
- clearTimeout(projectInitTimer);
187
- projectInitTimer = null;
188
- }
189
- if (diagDebounceTimer) {
190
- clearTimeout(diagDebounceTimer);
191
- diagDebounceTimer = null;
192
- }
193
- // Don't start the diagnostic debounce yet — wait for the first diagnostic to arrive.
194
- // Use the init timeout as the safety net (if no diagnostics arrive at all,
195
- // this project is effectively broken even though it didn't rapid-fire).
196
- startProjectInitTimeout();
197
- }
198
- lastInitMs = now;
199
- // Check if broken projects pushed us to completion
200
- if (isAllResolved()) {
201
- finishWait();
202
- }
203
- }
204
- function settleCurrentProject() {
205
- settledProjects++;
206
- if (isAllResolved()) {
207
- finishWait();
208
- }
209
- else {
210
- startProjectInitTimeout();
211
- }
212
- }
213
- function startDiagDebounce() {
214
- if (diagDebounceTimer)
215
- clearTimeout(diagDebounceTimer);
216
- // Cancel the init timeout — we're now in diagnostic-settling mode
217
- if (projectInitTimer) {
218
- clearTimeout(projectInitTimer);
219
- projectInitTimer = null;
220
- }
221
- diagDebounceTimer = setTimeout(settleCurrentProject, waitConfig.debounceMs);
222
- }
223
- function onDiagnosticReceived() {
224
- if (!projectWaitResolve)
225
- return;
226
- currentProjectDiagCount++;
227
- if (awaitingFirstDiag) {
228
- // First diagnostic after a healthy init — don't start the debounce yet.
229
- // The first diagnostic is often just the CSS entry point, followed by a
230
- // ~1s pause before the bulk TSX diagnostics arrive. Starting the debounce
231
- // here would settle too early on large projects.
232
- awaitingFirstDiag = false;
233
- }
234
- else if (currentProjectDiagCount >= 2) {
235
- // Second diagnostic and beyond — the bulk is flowing, debounce is safe
236
- startDiagDebounce();
237
- }
238
- }
239
- /**
240
- * Wait for all expected projects to be resolved (settled or broken).
241
- *
242
- * @param predictedRoots - Number of CSS files predicted to be project roots
243
- * @param maxProjects - Upper bound (predictedRoots + predictedNonRoots)
244
- * @param initTimeoutMs - How long to wait for each projectInitialized event
245
- * @param debounceMs - Silence window to consider diagnostics "settled"
246
- */
247
- export function waitForAllProjects(predictedRoots, maxProjects, initTimeoutMs = 5_000, debounceMs = 500) {
248
- if (serverDead || maxProjects === 0)
249
- return Promise.resolve();
250
- waitConfig = { predictedRoots, maxProjects, initTimeoutMs, debounceMs };
251
- return new Promise((res) => {
252
- projectWaitResolve = res;
253
- // Start waiting for first project init
254
- startProjectInitTimeout();
255
- // Hard outer timeout — never wait longer than this
256
- const outerMs = initTimeoutMs + (maxProjects * 3000) + 5000;
257
- outerTimer = setTimeout(finishWait, Math.min(outerMs, 30_000));
258
- });
259
- }
260
- /** Returns a promise that resolves when diagnostics are published for a specific URI. */
261
- export function waitForDiagnostic(uri, timeoutMs = 10_000) {
262
- if (serverDead)
263
- return Promise.resolve([]);
264
- // Clear stale entry so we wait for the server to re-publish
265
- diagnosticsReceived.delete(uri);
266
- return new Promise((res) => {
267
- const timer = setTimeout(() => {
268
- if (diagWaiters.has(uri)) {
269
- diagWaiters.delete(uri);
270
- res([]);
271
- }
272
- }, timeoutMs);
273
- diagWaiters.set(uri, (diags) => { clearTimeout(timer); res(diags); });
274
- });
275
- }
276
- // ---------------------------------------------------------------------------
277
- // JSON-RPC framing
278
- // ---------------------------------------------------------------------------
279
- function encode(obj) {
280
- const body = JSON.stringify(obj);
281
- return `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`;
282
- }
283
- function getRawBuf() {
284
- if (chunks.length === 0)
285
- return Buffer.alloc(0);
286
- if (chunks.length === 1)
287
- return chunks[0];
288
- const buf = Buffer.concat(chunks, chunksLen);
289
- chunks.length = 0;
290
- chunks.push(buf);
291
- return buf;
292
- }
293
- function setRawBuf(buf) {
294
- chunks.length = 0;
295
- if (buf.length > 0) {
296
- chunks.push(buf);
297
- chunksLen = buf.length;
298
- }
299
- else {
300
- chunksLen = 0;
301
- }
302
- }
303
- function processMessages() {
304
- while (true) {
305
- const rawBuf = getRawBuf();
306
- if (rawBuf.length === 0)
307
- break;
308
- const str = rawBuf.toString("ascii", 0, Math.min(rawBuf.length, 256));
309
- const headerEnd = str.indexOf("\r\n\r\n");
310
- if (headerEnd === -1) {
311
- setRawBuf(rawBuf);
312
- break;
313
- }
314
- const headerBlock = str.slice(0, headerEnd);
315
- const clMatch = headerBlock.match(/Content-Length:\s*(\d+)/i);
316
- if (!clMatch) {
317
- setRawBuf(rawBuf.subarray(headerEnd + 4));
318
- continue;
319
- }
320
- const len = parseInt(clMatch[1], 10);
321
- const bodyStart = headerEnd + 4;
322
- if (rawBuf.length < bodyStart + len) {
323
- setRawBuf(rawBuf);
324
- break;
325
- }
326
- const body = rawBuf.subarray(bodyStart, bodyStart + len).toString("utf-8");
327
- setRawBuf(rawBuf.subarray(bodyStart + len));
328
- let msg;
329
- try {
330
- msg = JSON.parse(body);
331
- }
332
- catch {
333
- continue;
334
- }
335
- if (DEBUG)
336
- console.error(`<- ${msg.method || `response#${msg.id}`}`);
337
- // Response to our request
338
- if (msg.id != null && !msg.method && pending.has(msg.id)) {
339
- const p = pending.get(msg.id);
340
- pending.delete(msg.id);
341
- if (msg.error)
342
- p.reject(msg.error);
343
- else
344
- p.resolve(msg.result);
345
- continue;
346
- }
347
- // Server-initiated requests — must respond
348
- if (msg.id != null && msg.method) {
349
- let result = null;
350
- if (msg.method === "workspace/configuration") {
351
- result = (msg.params?.items || []).map((item) => item.section ? getSettingsSection(item.section) : {});
352
- }
353
- server.stdin.write(encode({ jsonrpc: "2.0", id: msg.id, result }));
354
- continue;
355
- }
356
- // Published diagnostics
357
- if (msg.method === "textDocument/publishDiagnostics" && msg.params) {
358
- const uri = msg.params.uri;
359
- const diags = msg.params.diagnostics || [];
360
- diagnosticsReceived.set(uri, diags);
361
- // Resolve URI-specific waiter
362
- if (diagWaiters.has(uri)) {
363
- const resolve = diagWaiters.get(uri);
364
- diagWaiters.delete(uri);
365
- resolve(diags);
366
- }
367
- // Notify the project-aware wait system
368
- onDiagnosticReceived();
369
- }
370
- // Tailwind project initialized
371
- if (msg.method === "@/tailwindCSS/projectInitialized") {
372
- onProjectInitialized();
373
- }
374
- }
375
- }
376
- // ---------------------------------------------------------------------------
377
- // Server lifecycle
378
- // ---------------------------------------------------------------------------
379
- function findLanguageServer(cwd) {
380
- const local = resolve(cwd, "node_modules/.bin/tailwindcss-language-server");
381
- return existsSync(local) ? local : "tailwindcss-language-server";
382
- }
383
- /** Reject all pending requests and resolve all waiters. Called when the server dies. */
384
- function drainAll(reason) {
385
- serverDead = true;
386
- for (const [id, p] of pending) {
387
- p.reject(reason);
388
- pending.delete(id);
389
- }
390
- // Resolve project wait (so run() doesn't hang)
391
- finishWait();
392
- // Resolve all URI-specific waiters with empty arrays
393
- for (const [uri, r] of diagWaiters) {
394
- r([]);
395
- }
396
- diagWaiters.clear();
397
- }
398
- export function startServer(root) {
399
- workspaceRoot = root;
400
- const bin = findLanguageServer(root);
401
- server = spawn(bin, ["--stdio"], { stdio: ["pipe", "pipe", "pipe"] });
402
- server.on("error", (err) => {
403
- if (err.code === "ENOENT") {
404
- console.error("\n \x1b[38;5;203m\x1b[1mERROR\x1b[0m @tailwindcss/language-server not found.");
405
- console.error(" Install it: \x1b[1mnpm install -D @tailwindcss/language-server\x1b[0m\n");
406
- }
407
- drainAll(new Error(err.code === "ENOENT"
408
- ? "@tailwindcss/language-server not found"
409
- : `language server error: ${err.message}`));
410
- });
411
- server.on("close", (code, signal) => {
412
- if (!serverDead) {
413
- drainAll(new Error(signal ? `language server killed by ${signal}` : `language server exited with code ${code}`));
414
- }
415
- });
416
- server.stdout.on("data", (chunk) => {
417
- chunks.push(chunk);
418
- chunksLen += chunk.length;
419
- processMessages();
420
- });
421
- server.stderr.on("data", (chunk) => {
422
- if (DEBUG)
423
- process.stderr.write(chunk);
424
- });
425
- }
426
- export function send(method, params) {
427
- if (serverDead)
428
- return Promise.reject(new Error("language server is not running"));
429
- const id = ++msgId;
430
- return new Promise((res, rej) => {
431
- pending.set(id, { resolve: res, reject: rej });
432
- try {
433
- server.stdin.write(encode({ jsonrpc: "2.0", id, method, params }));
434
- }
435
- catch {
436
- pending.delete(id);
437
- rej(new Error("language server is not running"));
438
- }
439
- });
440
- }
441
- export function notify(method, params) {
442
- if (serverDead)
443
- return;
444
- try {
445
- server.stdin.write(encode({ jsonrpc: "2.0", method, params }));
446
- }
447
- catch {
448
- // Server pipe is dead — drainAll will handle cleanup via the close event
449
- }
450
- }
451
- export async function shutdown() {
452
- if (serverDead)
453
- return;
454
- await Promise.race([
455
- send("shutdown", {}).catch(() => { }),
456
- new Promise(r => setTimeout(r, 500)),
457
- ]);
458
- notify("exit", {});
459
- serverDead = true;
460
- try {
461
- server.stdin.end();
462
- }
463
- catch { }
464
- try {
465
- server.stdout.destroy();
466
- }
467
- catch { }
468
- try {
469
- server.stderr.destroy();
470
- }
471
- catch { }
472
- server.kill();
473
- }
474
- export function fileUri(absPath) {
475
- return `file://${absPath}`;
476
- }
477
- export function langId(filePath) {
478
- if (filePath.endsWith(".css"))
479
- return "css";
480
- if (filePath.endsWith(".html") || filePath.endsWith(".vue") || filePath.endsWith(".svelte") || filePath.endsWith(".astro"))
481
- return "html";
482
- if (filePath.endsWith(".mdx"))
483
- return "mdx";
484
- if (filePath.endsWith(".jsx"))
485
- return "javascriptreact";
486
- return "typescriptreact";
487
- }
1
+ import{spawn as q}from"child_process";import{resolve as I}from"path";import{existsSync as $,readFileSync as V}from"fs";const N=process.env.DEBUG==="1";let O="",m=null;function H(){if(m!==null)return m;const e=I(O,".vscode/settings.json");if(!$(e))return m={},m;try{const t=V(e,"utf-8").replace(/\/\/[^\n]*/g,"").replace(/,\s*([\]}])/g,"$1");m=JSON.parse(t)}catch{m={}}return m}function K(e){const t=H(),n=e+".",o={};for(const[l,y]of Object.entries(t)){if(!l.startsWith(n))continue;const c=l.slice(n.length).split(".");let p=o;for(let r=0;r<c.length-1;r++)(!(c[r]in p)||typeof p[c[r]]!="object")&&(p[c[r]]={}),p=p[c[r]];p[c[c.length-1]]=y}return o}let a,d=!1,L=0;const f=[];let T=0;const h=new Map,P=new Map;let A=!1,F=0,D=0,M=0,R=0,B=!1,x=!1,j=0;const J=[];let w=null,i=null,s=null,v=null;const g=new Map;let k={predictedRoots:0,maxProjects:0,initTimeoutMs:5e3,debounceMs:500};function ce(){L=0,d=!1,f.length=0,T=0,h.clear(),P.clear(),A=!1,F=0,D=0,M=0,R=0,B=!1,x=!1,j=0,J.length=0,w=null,i&&(clearTimeout(i),i=null),s&&(clearTimeout(s),s=null),v&&(clearTimeout(v),v=null),g.clear(),m=null}function Q(){i&&(clearTimeout(i),i=null),s&&(clearTimeout(s),s=null),v&&(clearTimeout(v),v=null)}function S(){if(!w)return;const e=w;w=null,Q(),e()}function _(){return D+M>=k.maxProjects}function W(){s&&clearTimeout(s),s=setTimeout(()=>{j>0&&!x?z():S()},k.initTimeoutMs)}function X(){F++;const e=Date.now();A=!0,R>0&&e-R<500?B||(B=!0,M++,J.push("A CSS file failed to initialize (likely an @apply referencing an unknown utility). That project's files will not receive diagnostics. See https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1121"),i&&(clearTimeout(i),i=null)):(B=!1,x=!0,j=0,s&&(clearTimeout(s),s=null),i&&(clearTimeout(i),i=null),W()),R=e,_()&&S()}function z(){D++,_()?S():W()}function Y(){i&&clearTimeout(i),s&&(clearTimeout(s),s=null),i=setTimeout(z,k.debounceMs)}function Z(){w&&(j++,x?x=!1:j>=2&&Y())}function ae(e,t,n=5e3,o=500){return d||t===0?Promise.resolve():(k={predictedRoots:e,maxProjects:t,initTimeoutMs:n,debounceMs:o},new Promise(l=>{w=l,W();const y=n+t*3e3+5e3;v=setTimeout(S,Math.min(y,3e4))}))}function ue(e,t=1e4){return d?Promise.resolve([]):(P.delete(e),new Promise(n=>{const o=setTimeout(()=>{g.has(e)&&(g.delete(e),n([]))},t);g.set(e,l=>{clearTimeout(o),n(l)})}))}function C(e){const t=JSON.stringify(e);return`Content-Length: ${Buffer.byteLength(t)}\r
2
+ \r
3
+ ${t}`}function ee(){if(f.length===0)return Buffer.alloc(0);if(f.length===1)return f[0];const e=Buffer.concat(f,T);return f.length=0,f.push(e),e}function E(e){f.length=0,e.length>0?(f.push(e),T=e.length):T=0}function te(){for(;;){const e=ee();if(e.length===0)break;const t=e.toString("ascii",0,Math.min(e.length,256)),n=t.indexOf(`\r
4
+ \r
5
+ `);if(n===-1){E(e);break}const l=t.slice(0,n).match(/Content-Length:\s*(\d+)/i);if(!l){E(e.subarray(n+4));continue}const y=parseInt(l[1],10),c=n+4;if(e.length<c+y){E(e);break}const p=e.subarray(c,c+y).toString("utf-8");E(e.subarray(c+y));let r;try{r=JSON.parse(p)}catch{continue}if(N&&console.error(`<- ${r.method||`response#${r.id}`}`),r.id!=null&&!r.method&&h.has(r.id)){const u=h.get(r.id);h.delete(r.id),r.error?u.reject(r.error):u.resolve(r.result);continue}if(r.id!=null&&r.method){let u=null;r.method==="workspace/configuration"&&(u=(r.params?.items||[]).map(b=>b.section?K(b.section):{})),a.stdin.write(C({jsonrpc:"2.0",id:r.id,result:u}));continue}if(r.method==="textDocument/publishDiagnostics"&&r.params){const u=r.params.uri,b=r.params.diagnostics||[];if(P.set(u,b),g.has(u)){const G=g.get(u);g.delete(u),G(b)}Z()}r.method==="@/tailwindCSS/projectInitialized"&&X()}}function ne(e){const t=I(e,"node_modules/.bin/tailwindcss-language-server");return $(t)?t:"tailwindcss-language-server"}function U(e){d=!0;for(const t of h.values())t.reject(e);h.clear(),S();for(const t of g.values())t([]);g.clear()}function fe(e){O=e;const t=ne(e);a=q(t,["--stdio"],{stdio:["pipe","pipe","pipe"]}),a.on("error",n=>{n.code==="ENOENT"&&(console.error(`
6
+ \x1B[38;5;203m\x1B[1mERROR\x1B[0m @tailwindcss/language-server not found.`),console.error(` Install it: \x1B[1mnpm install -D @tailwindcss/language-server\x1B[0m
7
+ `)),U(new Error(n.code==="ENOENT"?"@tailwindcss/language-server not found":`language server error: ${n.message}`))}),a.on("close",(n,o)=>{d||U(new Error(o?`language server killed by ${o}`:`language server exited with code ${n}`))}),a.stdout.on("data",n=>{f.push(n),T+=n.length,te()}),a.stderr.on("data",n=>{N&&process.stderr.write(n)})}function re(e,t){if(d)return Promise.reject(new Error("language server is not running"));const n=++L;return new Promise((o,l)=>{h.set(n,{resolve:o,reject:l});try{a.stdin.write(C({jsonrpc:"2.0",id:n,method:e,params:t}))}catch{h.delete(n),l(new Error("language server is not running"))}})}function ie(e,t){if(!d)try{a.stdin.write(C({jsonrpc:"2.0",method:e,params:t}))}catch{}}async function de(){if(!d){await Promise.race([re("shutdown",{}).catch(()=>{}),new Promise(e=>setTimeout(e,500))]),ie("exit",{}),d=!0;try{a.stdin.end()}catch{}try{a.stdout.destroy()}catch{}try{a.stderr.destroy()}catch{}a.kill()}}function ge(e){return`file://${e}`}function pe(e){return e.endsWith(".css")?"css":e.endsWith(".html")||e.endsWith(".vue")||e.endsWith(".svelte")||e.endsWith(".astro")?"html":e.endsWith(".mdx")?"mdx":e.endsWith(".jsx")?"javascriptreact":"typescriptreact"}export{M as brokenProjects,P as diagnosticsReceived,ge as fileUri,pe as langId,ie as notify,F as projectInitCount,A as projectReady,ce as resetState,re as send,D as settledProjects,de as shutdown,fe as startServer,ae as waitForAllProjects,ue as waitForDiagnostic,J as warnings};
package/dist/prescan.js CHANGED
@@ -1,79 +1 @@
1
- /**
2
- * Pre-scan CSS files to predict how many projects the language server will create.
3
- *
4
- * analyzeStylesheet() is adapted from tailwindlabs/tailwindcss-intellisense
5
- * (packages/tailwindcss-language-server/src/version-guesser.ts, MIT licensed).
6
- */
7
- import { readFileSync } from "fs";
8
- const HAS_V4_IMPORT = /@import\s*['"]tailwindcss(?:\/[^'"]+)?['"]/;
9
- const HAS_V4_DIRECTIVE = /@(theme|plugin|utility|custom-variant|variant|reference)\s*[^;{]+[;{]/;
10
- const HAS_V4_FN = /--(alpha|spacing|theme)\(/;
11
- const HAS_LEGACY_TAILWIND = /@tailwind\s*(base|preflight|components|variants|screens)+;/;
12
- const HAS_TAILWIND_UTILITIES = /@tailwind\s*utilities\s*[^;]*;/;
13
- const HAS_TAILWIND = /@tailwind\s*[^;]+;/;
14
- const HAS_COMMON_DIRECTIVE = /@(config|apply)\s*[^;{]+[;{]/;
15
- const HAS_NON_URL_IMPORT = /@import\s*['"](?!([a-z]+:|\/\/))/;
16
- export function analyzeStylesheet(content) {
17
- if (HAS_V4_IMPORT.test(content)) {
18
- return { root: true, versions: ["4"], explicitImport: true };
19
- }
20
- if (HAS_V4_DIRECTIVE.test(content)) {
21
- if (HAS_TAILWIND_UTILITIES.test(content)) {
22
- return { root: true, versions: ["4"], explicitImport: false };
23
- }
24
- return { root: false, versions: ["4"], explicitImport: false };
25
- }
26
- if (HAS_V4_FN.test(content)) {
27
- return { root: false, versions: ["4"], explicitImport: false };
28
- }
29
- if (HAS_LEGACY_TAILWIND.test(content)) {
30
- return { root: false, versions: ["3"], explicitImport: false };
31
- }
32
- if (HAS_TAILWIND.test(content)) {
33
- return { root: true, versions: ["4", "3"], explicitImport: false };
34
- }
35
- if (HAS_COMMON_DIRECTIVE.test(content)) {
36
- return { root: false, versions: ["4", "3"], explicitImport: false };
37
- }
38
- if (HAS_NON_URL_IMPORT.test(content)) {
39
- return { root: true, versions: ["4", "3"], explicitImport: false };
40
- }
41
- return { root: false, versions: [], explicitImport: false };
42
- }
43
- export function prescanCssFiles(files) {
44
- let predictedRoots = 0;
45
- let predictedNonRoots = 0;
46
- let predictedUnrelated = 0;
47
- const unrelatedCssFiles = new Set();
48
- for (const filePath of files) {
49
- if (!filePath.endsWith(".css"))
50
- continue;
51
- let content;
52
- try {
53
- content = readFileSync(filePath, "utf-8");
54
- }
55
- catch {
56
- continue;
57
- }
58
- const result = analyzeStylesheet(content);
59
- if (result.versions.length === 0) {
60
- predictedUnrelated++;
61
- unrelatedCssFiles.add(filePath);
62
- }
63
- else if (result.root) {
64
- predictedRoots++;
65
- }
66
- else {
67
- predictedNonRoots++;
68
- }
69
- }
70
- const totalCssFiles = predictedRoots + predictedNonRoots + predictedUnrelated;
71
- return {
72
- totalCssFiles,
73
- predictedRoots,
74
- predictedNonRoots,
75
- unrelatedCssFiles,
76
- predictedUnrelated,
77
- maxProjects: predictedRoots + predictedNonRoots,
78
- };
79
- }
1
+ import{readFileSync as a}from"fs";const c=/@import\s*['"]tailwindcss(?:\/[^'"]+)?['"]/,p=/@(theme|plugin|utility|custom-variant|variant|reference)\s*[^;{]+[;{]/,f=/--(alpha|spacing|theme)\(/,u=/@tailwind\s*(base|preflight|components|variants|screens)+;/,d=/@tailwind\s*utilities\s*[^;]*;/,m=/@tailwind\s*[^;]+;/,I=/@(config|apply)\s*[^;{]+[;{]/,x=/@import\s*['"](?!([a-z]+:|\/\/))/;function _(e){return c.test(e)?{root:!0,versions:["4"],explicitImport:!0}:p.test(e)?d.test(e)?{root:!0,versions:["4"],explicitImport:!1}:{root:!1,versions:["4"],explicitImport:!1}:f.test(e)?{root:!1,versions:["4"],explicitImport:!1}:u.test(e)?{root:!1,versions:["3"],explicitImport:!1}:m.test(e)?{root:!0,versions:["4","3"],explicitImport:!1}:I.test(e)?{root:!1,versions:["4","3"],explicitImport:!1}:x.test(e)?{root:!0,versions:["4","3"],explicitImport:!1}:{root:!1,versions:[],explicitImport:!1}}function v(e){let t=0,s=0,r=0;const o=new Set;for(const i of e){if(!i.endsWith(".css"))continue;let n;try{n=a(i,"utf-8")}catch{continue}const l=_(n);l.versions.length===0?(r++,o.add(i)):l.root?t++:s++}return{totalCssFiles:t+s+r,predictedRoots:t,predictedNonRoots:s,unrelatedCssFiles:o,predictedUnrelated:r,maxProjects:t+s}}export{_ as analyzeStylesheet,v as prescanCssFiles};
package/dist/ui.js CHANGED
@@ -1,203 +1,3 @@
1
- /**
2
- * Terminal UI colors, spinners, animations, progress bars.
3
- */
4
- function sleep(ms) {
5
- return new Promise((r) => setTimeout(r, ms));
6
- }
7
- export const isTTY = !!(process.stderr.isTTY && process.stdout.isTTY);
8
- export const c = {
9
- reset: isTTY ? "\x1b[0m" : "",
10
- bold: isTTY ? "\x1b[1m" : "",
11
- dim: isTTY ? "\x1b[2m" : "",
12
- italic: isTTY ? "\x1b[3m" : "",
13
- under: isTTY ? "\x1b[4m" : "",
14
- red: isTTY ? "\x1b[38;5;203m" : "",
15
- orange: isTTY ? "\x1b[38;5;208m" : "",
16
- yellow: isTTY ? "\x1b[38;5;221m" : "",
17
- green: isTTY ? "\x1b[38;5;114m" : "",
18
- cyan: isTTY ? "\x1b[38;5;80m" : "",
19
- blue: isTTY ? "\x1b[38;5;75m" : "",
20
- purple: isTTY ? "\x1b[38;5;141m" : "",
21
- pink: isTTY ? "\x1b[38;5;211m" : "",
22
- gray: isTTY ? "\x1b[38;5;243m" : "",
23
- white: isTTY ? "\x1b[38;5;255m" : "",
24
- bgRed: isTTY ? "\x1b[48;5;52m" : "",
25
- bgGreen: isTTY ? "\x1b[48;5;22m" : "",
26
- bgOrange: isTTY ? "\x1b[48;5;94m" : "",
27
- bgCyan: isTTY ? "\x1b[48;5;30m" : "",
28
- hide: isTTY ? "\x1b[?25l" : "",
29
- show: isTTY ? "\x1b[?25h" : "",
30
- clear: isTTY ? "\x1b[2K\r" : "",
31
- };
32
- // ---------------------------------------------------------------------------
33
- // Terminal title
34
- // ---------------------------------------------------------------------------
35
- export function setTitle(text) {
36
- if (isTTY)
37
- process.stderr.write(`\x1b]2;${text}\x07`);
38
- }
39
- // ---------------------------------------------------------------------------
40
- // Wind trail — the tailwint signature
41
- // ---------------------------------------------------------------------------
42
- const WIND_CHARS = ["~", "\u2248", "\u223C", "\u301C"];
43
- const WIND_WIDTHS = [1, 1, 1, 2]; // 〜 is fullwidth
44
- const WIND_COLORS = [c.cyan, c.blue, c.purple, c.pink, c.cyan, c.blue];
45
- /** Generates a wind trail that fills exactly `cols` terminal columns. */
46
- export function windTrail(cols, offset = 0) {
47
- const parts = [];
48
- let used = 0;
49
- let i = 0;
50
- while (used < cols) {
51
- const charIdx = (i + offset) % WIND_CHARS.length;
52
- const colorIdx = (i + offset) % WIND_COLORS.length;
53
- const w = WIND_WIDTHS[charIdx];
54
- if (used + w > cols)
55
- break; // don't overshoot
56
- parts.push(`${WIND_COLORS[colorIdx]}${WIND_CHARS[charIdx]}${c.reset}`);
57
- used += w;
58
- i++;
59
- }
60
- // Fill remaining columns with single-width chars
61
- while (used < cols) {
62
- const colorIdx = (i + offset) % WIND_COLORS.length;
63
- parts.push(`${WIND_COLORS[colorIdx]}~${c.reset}`);
64
- used++;
65
- i++;
66
- }
67
- return parts.join("");
68
- }
69
- // ---------------------------------------------------------------------------
70
- // Spinners & animation primitives
71
- // ---------------------------------------------------------------------------
72
- const BRAILLE_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2807"];
73
- export let tick = 0;
74
- export function advanceTick() { tick++; }
75
- const SPIN_COLORS = [c.cyan, c.blue, c.purple, c.pink, c.purple, c.blue];
76
- export function braille() {
77
- const color = SPIN_COLORS[Math.floor(tick / 2) % SPIN_COLORS.length];
78
- return `${color}${BRAILLE_FRAMES[tick % BRAILLE_FRAMES.length]}${c.reset}`;
79
- }
80
- export function windWave() {
81
- return windTrail(6, tick % 24);
82
- }
83
- export function dots() {
84
- const n = tick % 4;
85
- return `${c.dim}${".".repeat(n)}${" ".repeat(3 - n)}${c.reset}`;
86
- }
87
- export function startSpinner(render, intervalMs = 100) {
88
- if (!isTTY)
89
- return () => { };
90
- process.stderr.write(c.hide);
91
- let lastLines = 1;
92
- const id = setInterval(() => {
93
- tick++;
94
- const output = render();
95
- const lines = output.split("\n").length;
96
- // Move up and clear previous lines
97
- let clear = "\x1b[2K\r";
98
- for (let i = 1; i < lastLines; i++)
99
- clear = `\x1b[A\x1b[2K` + clear;
100
- process.stderr.write(`${clear}${output}`);
101
- lastLines = lines;
102
- }, intervalMs);
103
- return () => {
104
- clearInterval(id);
105
- // Move cursor to the first line of the spinner
106
- for (let i = 1; i < lastLines; i++)
107
- process.stderr.write("\x1b[A");
108
- // Clear from cursor to end of screen (clears all spinner lines)
109
- process.stderr.write(`\x1b[2K\x1b[J\r${c.show}`);
110
- };
111
- }
112
- // ---------------------------------------------------------------------------
113
- // Progress bar
114
- // ---------------------------------------------------------------------------
115
- export function progressBar(pct, width, animate = false) {
116
- const filled = Math.round((pct / 100) * width);
117
- const empty = width - filled;
118
- const gradient = [c.cyan, c.cyan, c.blue, c.blue, c.purple, c.purple, c.pink];
119
- const chars = Array.from({ length: filled }, (_, i) => {
120
- const colorIdx = Math.floor((i / width) * gradient.length);
121
- const shift = animate ? (tick + i) % gradient.length : colorIdx;
122
- const color = gradient[Math.min(shift, gradient.length - 1)];
123
- return `${color}\u2501${c.reset}`;
124
- }).join("");
125
- const emptyBar = `${c.dim}${"\u2501".repeat(empty)}${c.reset}`;
126
- return `${c.dim}\u2503${c.reset}${chars}${emptyBar}${c.dim}\u2503${c.reset}`;
127
- }
128
- // ---------------------------------------------------------------------------
129
- // Banner & celebrations
130
- // ---------------------------------------------------------------------------
131
- // Viewport: 56 visible chars (2 indent + 54 content)
132
- const VP = 54;
133
- const WIND_SIDE = Math.floor((VP - 10) / 2); // 22 each side of " tailwint "
134
- export async function banner() {
135
- console.error("");
136
- if (isTTY) {
137
- process.stderr.write(c.hide);
138
- for (let frame = 0; frame < 8; frame++) {
139
- const len = Math.min(frame + 2, WIND_SIDE);
140
- const pad = " ".repeat(WIND_SIDE - len);
141
- const left = windTrail(len, frame);
142
- const right = windTrail(len, frame + 6);
143
- const titleColor = frame < 4 ? c.dim : c.bold;
144
- process.stderr.write(`${c.clear} ${pad}${left} ${titleColor}${c.cyan}tailwint${c.reset} ${right}`);
145
- await sleep(60);
146
- }
147
- process.stderr.write(`${c.clear}${c.show}`);
148
- }
149
- console.error(` ${windTrail(WIND_SIDE)} ${c.bold}${c.cyan}tailwint${c.reset} ${windTrail(WIND_SIDE, 6)}`);
150
- console.error("");
151
- console.error(` ${c.dim}tailwind css linter ${c.gray}// powered by the official lsp${c.reset}`);
152
- console.error("");
153
- }
154
- function celebrationBurst() {
155
- const sparks = ["\u2728", "\u2727", "\u2726", "\u2729", "\u00B7", "\u2728"];
156
- const colors = [c.cyan, c.blue, c.purple, c.pink, c.yellow, c.green];
157
- return Array.from({ length: 6 }, (_, i) => `${colors[i % colors.length]}${sparks[i % sparks.length]}${c.reset}`).join(" ");
158
- }
159
- export function rainbowText(text) {
160
- const colors = [c.cyan, c.blue, c.purple, c.pink, c.orange, c.yellow, c.green];
161
- return text
162
- .split("")
163
- .map((ch, i) => (ch === " " ? ch : `${colors[i % colors.length]}${ch}${c.reset}`))
164
- .join("");
165
- }
166
- export async function celebrationAnimation() {
167
- if (!isTTY)
168
- return;
169
- // Center each frame within the 56-col viewport
170
- const pad = (visible) => " ".repeat(Math.floor((56 - visible) / 2));
171
- process.stderr.write(c.hide);
172
- const frames = [
173
- `${pad(5)}${c.dim}. ${c.reset}${c.cyan}\u2728${c.reset}${c.dim} .${c.reset}`,
174
- `${pad(5)}${c.blue}\u2727${c.reset} ${c.purple}\u2728${c.reset} ${c.pink}\u2727${c.reset}`,
175
- `${pad(7)}${c.cyan}\u2728${c.reset} ${c.yellow}\u2726${c.reset} ${c.green}\u2728${c.reset} ${c.purple}\u2729${c.reset}`,
176
- `${pad(11)}${celebrationBurst()}`,
177
- ];
178
- for (const frame of frames) {
179
- process.stderr.write(`${c.clear}${frame}`);
180
- await sleep(150);
181
- }
182
- process.stderr.write(`${c.clear}${c.show}`);
183
- console.error(`${pad(11)}${celebrationBurst()}\n`);
184
- }
185
- // ---------------------------------------------------------------------------
186
- // Diagnostic & file formatting
187
- // ---------------------------------------------------------------------------
188
- export function fileBadge(rel) {
189
- const dir = rel.includes("/") ? `${c.dim}${rel.slice(0, rel.lastIndexOf("/") + 1)}${c.reset}` : "";
190
- const name = rel.includes("/") ? rel.slice(rel.lastIndexOf("/") + 1) : rel;
191
- return `${dir}${c.bold}${c.white}${name}${c.reset}`;
192
- }
193
- export function diagLine(d) {
194
- const line = (d.range?.start?.line ?? 0) + 1;
195
- const col = (d.range?.start?.character ?? 0) + 1;
196
- const loc = `${c.dim}${line}:${col}${c.reset}`;
197
- const isConflict = d.code === "cssConflict";
198
- const icon = isConflict ? `${c.orange}\u26A1${c.reset}` : `${c.yellow}\u25CB${c.reset}`;
199
- const tag = isConflict
200
- ? `${c.bgOrange}${c.bold} conflict ${c.reset}`
201
- : `${c.yellow}canonical${c.reset}`;
202
- return ` ${icon} ${loc} ${tag} ${c.white}${d.message}${c.reset}`;
203
- }
1
+ function g(r){return new Promise(t=>setTimeout(t,r))}const n=!!(process.stderr.isTTY&&process.stdout.isTTY),e={reset:n?"\x1B[0m":"",bold:n?"\x1B[1m":"",dim:n?"\x1B[2m":"",italic:n?"\x1B[3m":"",under:n?"\x1B[4m":"",red:n?"\x1B[38;5;203m":"",orange:n?"\x1B[38;5;208m":"",yellow:n?"\x1B[38;5;221m":"",green:n?"\x1B[38;5;114m":"",cyan:n?"\x1B[38;5;80m":"",blue:n?"\x1B[38;5;75m":"",purple:n?"\x1B[38;5;141m":"",pink:n?"\x1B[38;5;211m":"",gray:n?"\x1B[38;5;243m":"",white:n?"\x1B[38;5;255m":"",bgRed:n?"\x1B[48;5;52m":"",bgGreen:n?"\x1B[48;5;22m":"",bgOrange:n?"\x1B[48;5;94m":"",bgCyan:n?"\x1B[48;5;30m":"",hide:n?"\x1B[?25l":"",show:n?"\x1B[?25h":"",clear:n?"\x1B[2K\r":""};function A(r){n&&process.stderr.write(`\x1B]2;${r}\x07`)}const x=["~","\u2248","\u223C","\u301C"],I=[1,1,1,2],p=[e.cyan,e.blue,e.purple,e.pink,e.cyan,e.blue];function u(r,t=0){const o=[];let s=0,i=0;for(;s<r;){const c=(i+t)%x.length,l=(i+t)%p.length,$=I[c];if(s+$>r)break;o.push(`${p[l]}${x[c]}${e.reset}`),s+=$,i++}for(;s<r;){const c=(i+t)%p.length;o.push(`${p[c]}~${e.reset}`),s++,i++}return o.join("")}const m=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2807"];let a=0;function B(){a++}const f=[e.cyan,e.blue,e.purple,e.pink,e.purple,e.blue];function M(){return`${f[Math.floor(a/2)%f.length]}${m[a%m.length]}${e.reset}`}function S(){return u(6,a%24)}function _(){const r=a%4;return`${e.dim}${".".repeat(r)}${" ".repeat(3-r)}${e.reset}`}function O(r,t=100){if(!n)return()=>{};process.stderr.write(e.hide);let o=1;const s=setInterval(()=>{a++;const i=r(),c=i.split(`
2
+ `).length;let l="\x1B[2K\r";for(let $=1;$<o;$++)l="\x1B[A\x1B[2K"+l;process.stderr.write(`${l}${i}`),o=c},t);return()=>{clearInterval(s);for(let i=1;i<o;i++)process.stderr.write("\x1B[A");process.stderr.write(`\x1B[2K\x1B[J\r${e.show}`)}}function v(r,t,o=!1){const s=Math.round(r/100*t),i=t-s,c=[e.cyan,e.cyan,e.blue,e.blue,e.purple,e.purple,e.pink],l=Array.from({length:s},(k,d)=>{const w=Math.floor(d/t*c.length),y=o?(a+d)%c.length:w;return`${c[Math.min(y,c.length-1)]}\u2501${e.reset}`}).join(""),$=`${e.dim}${"\u2501".repeat(i)}${e.reset}`;return`${e.dim}\u2503${e.reset}${l}${$}${e.dim}\u2503${e.reset}`}const T=54,b=Math.floor((T-10)/2);async function D(){if(console.error(""),n){process.stderr.write(e.hide);for(let r=0;r<8;r++){const t=Math.min(r+2,b),o=" ".repeat(b-t),s=u(t,r),i=u(t,r+6),c=r<4?e.dim:e.bold;process.stderr.write(`${e.clear} ${o}${s} ${c}${e.cyan}tailwint${e.reset} ${i}`),await g(60)}process.stderr.write(`${e.clear}${e.show}`)}console.error(` ${u(b)} ${e.bold}${e.cyan}tailwint${e.reset} ${u(b,6)}`),console.error(""),console.error(` ${e.dim}tailwind css linter ${e.gray}// powered by the official lsp${e.reset}`),console.error("")}function h(){const r=["\u2728","\u2727","\u2726","\u2729","\xB7","\u2728"],t=[e.cyan,e.blue,e.purple,e.pink,e.yellow,e.green];return Array.from({length:6},(o,s)=>`${t[s%t.length]}${r[s%r.length]}${e.reset}`).join(" ")}function L(r){const t=[e.cyan,e.blue,e.purple,e.pink,e.orange,e.yellow,e.green];return r.split("").map((o,s)=>o===" "?o:`${t[s%t.length]}${o}${e.reset}`).join("")}async function R(){if(!n)return;const r=o=>" ".repeat(Math.floor((56-o)/2));process.stderr.write(e.hide);const t=[`${r(5)}${e.dim}. ${e.reset}${e.cyan}\u2728${e.reset}${e.dim} .${e.reset}`,`${r(5)}${e.blue}\u2727${e.reset} ${e.purple}\u2728${e.reset} ${e.pink}\u2727${e.reset}`,`${r(7)}${e.cyan}\u2728${e.reset} ${e.yellow}\u2726${e.reset} ${e.green}\u2728${e.reset} ${e.purple}\u2729${e.reset}`,`${r(11)}${h()}`];for(const o of t)process.stderr.write(`${e.clear}${o}`),await g(150);process.stderr.write(`${e.clear}${e.show}`),console.error(`${r(11)}${h()}
3
+ `)}function W(r){const t=r.lastIndexOf("/"),o=t>=0?`${e.dim}${r.slice(0,t+1)}${e.reset}`:"",s=t>=0?r.slice(t+1):r;return`${o}${e.bold}${e.white}${s}${e.reset}`}function N(r){const t=(r.range?.start?.line??0)+1,o=(r.range?.start?.character??0)+1,s=`${e.dim}${t}:${o}${e.reset}`,i=r.code==="cssConflict",c=i?`${e.orange}\u26A1${e.reset}`:`${e.yellow}\u25CB${e.reset}`,l=i?`${e.bgOrange}${e.bold} conflict ${e.reset}`:`${e.yellow}canonical${e.reset}`;return` ${c} ${s} ${l} ${e.white}${r.message}${e.reset}`}export{B as advanceTick,D as banner,M as braille,e as c,R as celebrationAnimation,N as diagLine,_ as dots,W as fileBadge,n as isTTY,v as progressBar,L as rainbowText,A as setTitle,O as startSpinner,a as tick,u as windTrail,S as windWave};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tailwint",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
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",
@@ -45,7 +45,7 @@
45
45
  ],
46
46
  "scripts": {
47
47
  "clean": "rm -rf dist",
48
- "build": "rm -rf dist && tsc",
48
+ "build": "rm -rf dist && esbuild src/index.ts src/edits.ts src/lsp.ts src/prescan.ts src/ui.ts --outdir=dist --format=esm --platform=node --target=node18 --minify && tsc --emitDeclarationOnly",
49
49
  "test": "tsx --test tests/*.test.ts",
50
50
  "prepublishOnly": "npm run clean && npm run build"
51
51
  },
@@ -58,6 +58,7 @@
58
58
  "devDependencies": {
59
59
  "@tailwindcss/language-server": "^0.14.29",
60
60
  "@types/node": "^25.5.0",
61
+ "esbuild": "^0.27.4",
61
62
  "tsx": "^4.21.0",
62
63
  "typescript": "^6.0.2"
63
64
  }