tailwint 1.0.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/LICENSE +21 -0
- package/README.md +52 -0
- package/bin/tailwint.js +2 -0
- package/dist/applyEdits.test.d.ts +1 -0
- package/dist/applyEdits.test.js +133 -0
- package/dist/batchEdits.test.d.ts +1 -0
- package/dist/batchEdits.test.js +132 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +21 -0
- package/dist/edgeCases.test.d.ts +1 -0
- package/dist/edgeCases.test.js +99 -0
- package/dist/edits.d.ts +18 -0
- package/dist/edits.js +109 -0
- package/dist/edits.test.d.ts +4 -0
- package/dist/edits.test.js +410 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +210 -0
- package/dist/index.test.d.ts +4 -0
- package/dist/index.test.js +410 -0
- package/dist/lsp.d.ts +19 -0
- package/dist/lsp.js +239 -0
- package/dist/ui.d.ts +43 -0
- package/dist/ui.js +190 -0
- package/package.json +49 -0
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal UI — colors, spinners, animations, progress bars.
|
|
3
|
+
*/
|
|
4
|
+
export declare const isTTY: boolean;
|
|
5
|
+
export declare const c: {
|
|
6
|
+
reset: string;
|
|
7
|
+
bold: string;
|
|
8
|
+
dim: string;
|
|
9
|
+
italic: string;
|
|
10
|
+
under: string;
|
|
11
|
+
red: string;
|
|
12
|
+
orange: string;
|
|
13
|
+
yellow: string;
|
|
14
|
+
green: string;
|
|
15
|
+
cyan: string;
|
|
16
|
+
blue: string;
|
|
17
|
+
purple: string;
|
|
18
|
+
pink: string;
|
|
19
|
+
gray: string;
|
|
20
|
+
white: string;
|
|
21
|
+
bgRed: string;
|
|
22
|
+
bgGreen: string;
|
|
23
|
+
bgOrange: string;
|
|
24
|
+
bgCyan: string;
|
|
25
|
+
hide: string;
|
|
26
|
+
show: string;
|
|
27
|
+
clear: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function setTitle(text: string): void;
|
|
30
|
+
/** Generates a wind trail that fills exactly `cols` terminal columns. */
|
|
31
|
+
export declare function windTrail(cols: number, offset?: number): string;
|
|
32
|
+
export declare let tick: number;
|
|
33
|
+
export declare function advanceTick(): void;
|
|
34
|
+
export declare function braille(): string;
|
|
35
|
+
export declare function windWave(): string;
|
|
36
|
+
export declare function dots(): string;
|
|
37
|
+
export declare function startSpinner(render: () => string, intervalMs?: number): () => void;
|
|
38
|
+
export declare function progressBar(pct: number, width: number, animate?: boolean): string;
|
|
39
|
+
export declare function banner(): Promise<void>;
|
|
40
|
+
export declare function rainbowText(text: string): string;
|
|
41
|
+
export declare function celebrationAnimation(): Promise<void>;
|
|
42
|
+
export declare function fileBadge(rel: string): string;
|
|
43
|
+
export declare function diagLine(d: any): string;
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
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
|
+
const id = setInterval(() => {
|
|
92
|
+
tick++;
|
|
93
|
+
process.stderr.write(`${c.clear}${render()}`);
|
|
94
|
+
}, intervalMs);
|
|
95
|
+
return () => {
|
|
96
|
+
clearInterval(id);
|
|
97
|
+
process.stderr.write(`${c.clear}${c.show}`);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Progress bar
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
export function progressBar(pct, width, animate = false) {
|
|
104
|
+
const filled = Math.round((pct / 100) * width);
|
|
105
|
+
const empty = width - filled;
|
|
106
|
+
const gradient = [c.cyan, c.cyan, c.blue, c.blue, c.purple, c.purple, c.pink];
|
|
107
|
+
const chars = Array.from({ length: filled }, (_, i) => {
|
|
108
|
+
const colorIdx = Math.floor((i / width) * gradient.length);
|
|
109
|
+
const shift = animate ? (tick + i) % gradient.length : colorIdx;
|
|
110
|
+
const color = gradient[Math.min(shift, gradient.length - 1)];
|
|
111
|
+
return `${color}\u2501${c.reset}`;
|
|
112
|
+
}).join("");
|
|
113
|
+
const emptyBar = `${c.dim}${"\u2501".repeat(empty)}${c.reset}`;
|
|
114
|
+
return `${c.dim}\u2503${c.reset}${chars}${emptyBar}${c.dim}\u2503${c.reset}`;
|
|
115
|
+
}
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Banner & celebrations
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Viewport: 56 visible chars (2 indent + 54 content)
|
|
120
|
+
const VP = 54;
|
|
121
|
+
const WIND_SIDE = Math.floor((VP - 10) / 2); // 22 each side of " tailwint "
|
|
122
|
+
export async function banner() {
|
|
123
|
+
console.error("");
|
|
124
|
+
if (isTTY) {
|
|
125
|
+
process.stderr.write(c.hide);
|
|
126
|
+
for (let frame = 0; frame < 8; frame++) {
|
|
127
|
+
const len = Math.min(frame + 2, WIND_SIDE);
|
|
128
|
+
const pad = " ".repeat(WIND_SIDE - len);
|
|
129
|
+
const left = windTrail(len, frame);
|
|
130
|
+
const right = windTrail(len, frame + 6);
|
|
131
|
+
const titleColor = frame < 4 ? c.dim : c.bold;
|
|
132
|
+
process.stderr.write(`${c.clear} ${pad}${left} ${titleColor}${c.cyan}tailwint${c.reset} ${right}`);
|
|
133
|
+
await sleep(60);
|
|
134
|
+
}
|
|
135
|
+
process.stderr.write(`${c.clear}${c.show}`);
|
|
136
|
+
}
|
|
137
|
+
console.error(` ${windTrail(WIND_SIDE)} ${c.bold}${c.cyan}tailwint${c.reset} ${windTrail(WIND_SIDE, 6)}`);
|
|
138
|
+
console.error(` ${c.dim}tailwind css linter ${c.gray}// powered by the official lsp${c.reset}`);
|
|
139
|
+
console.error("");
|
|
140
|
+
}
|
|
141
|
+
function celebrationBurst() {
|
|
142
|
+
const sparks = ["\u2728", "\u2727", "\u2726", "\u2729", "\u00B7", "\u2728"];
|
|
143
|
+
const colors = [c.cyan, c.blue, c.purple, c.pink, c.yellow, c.green];
|
|
144
|
+
return Array.from({ length: 6 }, (_, i) => `${colors[i % colors.length]}${sparks[i % sparks.length]}${c.reset}`).join(" ");
|
|
145
|
+
}
|
|
146
|
+
export function rainbowText(text) {
|
|
147
|
+
const colors = [c.cyan, c.blue, c.purple, c.pink, c.orange, c.yellow, c.green];
|
|
148
|
+
return text
|
|
149
|
+
.split("")
|
|
150
|
+
.map((ch, i) => (ch === " " ? ch : `${colors[i % colors.length]}${ch}${c.reset}`))
|
|
151
|
+
.join("");
|
|
152
|
+
}
|
|
153
|
+
export async function celebrationAnimation() {
|
|
154
|
+
if (!isTTY)
|
|
155
|
+
return;
|
|
156
|
+
// Center each frame within the 56-col viewport
|
|
157
|
+
const pad = (visible) => " ".repeat(Math.floor((56 - visible) / 2));
|
|
158
|
+
process.stderr.write(c.hide);
|
|
159
|
+
const frames = [
|
|
160
|
+
`${pad(5)}${c.dim}. ${c.reset}${c.cyan}\u2728${c.reset}${c.dim} .${c.reset}`,
|
|
161
|
+
`${pad(5)}${c.blue}\u2727${c.reset} ${c.purple}\u2728${c.reset} ${c.pink}\u2727${c.reset}`,
|
|
162
|
+
`${pad(7)}${c.cyan}\u2728${c.reset} ${c.yellow}\u2726${c.reset} ${c.green}\u2728${c.reset} ${c.purple}\u2729${c.reset}`,
|
|
163
|
+
`${pad(11)}${celebrationBurst()}`,
|
|
164
|
+
];
|
|
165
|
+
for (const frame of frames) {
|
|
166
|
+
process.stderr.write(`${c.clear}${frame}`);
|
|
167
|
+
await sleep(150);
|
|
168
|
+
}
|
|
169
|
+
process.stderr.write(`${c.clear}${c.show}`);
|
|
170
|
+
console.error(`${pad(11)}${celebrationBurst()}\n`);
|
|
171
|
+
}
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// Diagnostic & file formatting
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
export function fileBadge(rel) {
|
|
176
|
+
const dir = rel.includes("/") ? `${c.dim}${rel.slice(0, rel.lastIndexOf("/") + 1)}${c.reset}` : "";
|
|
177
|
+
const name = rel.includes("/") ? rel.slice(rel.lastIndexOf("/") + 1) : rel;
|
|
178
|
+
return `${dir}${c.bold}${c.white}${name}${c.reset}`;
|
|
179
|
+
}
|
|
180
|
+
export function diagLine(d) {
|
|
181
|
+
const line = (d.range?.start?.line ?? 0) + 1;
|
|
182
|
+
const col = (d.range?.start?.character ?? 0) + 1;
|
|
183
|
+
const loc = `${c.dim}${line}:${col}${c.reset}`;
|
|
184
|
+
const isConflict = d.code === "cssConflict";
|
|
185
|
+
const icon = isConflict ? `${c.orange}\u26A1${c.reset}` : `${c.yellow}\u25CB${c.reset}`;
|
|
186
|
+
const tag = isConflict
|
|
187
|
+
? `${c.bgOrange}${c.bold} conflict ${c.reset}`
|
|
188
|
+
: `${c.yellow}canonical${c.reset}`;
|
|
189
|
+
return ` ${icon} ${loc} ${tag} ${c.white}${d.message}${c.reset}`;
|
|
190
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tailwint",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Tailwind CSS linter for CI — drives the official language server to catch class issues and auto-fix them",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Peter Wang",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/peterwangsc/tailwint.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"tailwindcss",
|
|
13
|
+
"tailwind",
|
|
14
|
+
"lint",
|
|
15
|
+
"linter",
|
|
16
|
+
"ci",
|
|
17
|
+
"language-server",
|
|
18
|
+
"lsp"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"tailwint": "./bin/tailwint.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"bin",
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc",
|
|
34
|
+
"test": "tsx --test src/*.test.ts",
|
|
35
|
+
"prepublishOnly": "npm run build"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"glob": "^13.0.6"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@tailwindcss/language-server": ">=0.14.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@tailwindcss/language-server": "^0.14.29",
|
|
45
|
+
"@types/node": "^25.5.0",
|
|
46
|
+
"tsx": "^4.21.0",
|
|
47
|
+
"typescript": "^6.0.2"
|
|
48
|
+
}
|
|
49
|
+
}
|