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/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
+ }