@empline/preflight 1.1.4 → 1.1.6

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.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Auth Error Handling Preflight
3
+ *
4
+ * Detects client components that make authenticated API calls without proper
5
+ * auth error handling. This prevents confusing "Seller account required" or
6
+ * similar errors when the session has expired but the UI still shows as logged in.
7
+ *
8
+ * What it checks:
9
+ * 1. Client components using fetch() to /api/store/* endpoints
10
+ * 2. Whether they use authFetch or have explicit auth error handling
11
+ * 3. Flags components that may show confusing errors on stale sessions
12
+ *
13
+ * Fix:
14
+ * Replace `fetch("/api/store/...")` with `authFetch("/api/store/...")`
15
+ * from "@/lib/auth-fetch"
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=auth-error-handling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-error-handling.d.ts","sourceRoot":"","sources":["../../../src/checks/auth/auth-error-handling.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ /**
3
+ * Auth Error Handling Preflight
4
+ *
5
+ * Detects client components that make authenticated API calls without proper
6
+ * auth error handling. This prevents confusing "Seller account required" or
7
+ * similar errors when the session has expired but the UI still shows as logged in.
8
+ *
9
+ * What it checks:
10
+ * 1. Client components using fetch() to /api/store/* endpoints
11
+ * 2. Whether they use authFetch or have explicit auth error handling
12
+ * 3. Flags components that may show confusing errors on stale sessions
13
+ *
14
+ * Fix:
15
+ * Replace `fetch("/api/store/...")` with `authFetch("/api/store/...")`
16
+ * from "@/lib/auth-fetch"
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ const fs = __importStar(require("fs"));
53
+ const path = __importStar(require("path"));
54
+ // Direct execution - parse args manually
55
+ const args = process.argv.slice(2);
56
+ const jsonOutput = args.includes("--json");
57
+ const showAll = args.includes("--all");
58
+ /** Protected API prefixes that require authentication */
59
+ const PROTECTED_API_PREFIXES = [
60
+ "/api/store/",
61
+ "/api/admin/",
62
+ "/api/seller/",
63
+ "/api/account/",
64
+ "/api/listings/bulk",
65
+ "/api/listings/save",
66
+ "/api/listings/submit",
67
+ "/api/listings/create",
68
+ "/api/listings/delete",
69
+ "/api/listings/load",
70
+ ];
71
+ /** Files/patterns to skip */
72
+ const SKIP_PATTERNS = [
73
+ "node_modules",
74
+ ".next",
75
+ "dist",
76
+ ".test.",
77
+ ".spec.",
78
+ "__tests__",
79
+ "__mocks__",
80
+ ];
81
+ function shouldSkipFile(filePath) {
82
+ return SKIP_PATTERNS.some((pattern) => filePath.includes(pattern));
83
+ }
84
+ function isClientComponent(content) {
85
+ return content.trimStart().startsWith('"use client"') ||
86
+ content.trimStart().startsWith("'use client'");
87
+ }
88
+ function isProtectedEndpoint(endpoint) {
89
+ return PROTECTED_API_PREFIXES.some((prefix) => endpoint.includes(prefix));
90
+ }
91
+ function hasAuthFetchImport(content) {
92
+ return content.includes("from \"@/lib/auth-fetch\"") ||
93
+ content.includes("from '@/lib/auth-fetch'") ||
94
+ content.includes("authFetch");
95
+ }
96
+ function hasExplicitAuthHandling(content, lineIndex) {
97
+ // Check if there's auth error handling within ~30 lines after the fetch
98
+ const lines = content.split("\n");
99
+ const contextLines = lines.slice(lineIndex, lineIndex + 30).join("\n").toLowerCase();
100
+ // Patterns that indicate auth error handling
101
+ const authHandlingPatterns = [
102
+ "401",
103
+ "403",
104
+ "unauthorized",
105
+ "forbidden",
106
+ "session",
107
+ "signin",
108
+ "signout",
109
+ "login",
110
+ "logout",
111
+ "redirect",
112
+ "onunauthorized",
113
+ "onforbidden",
114
+ "handleapierror",
115
+ ];
116
+ return authHandlingPatterns.some((pattern) => contextLines.includes(pattern));
117
+ }
118
+ function extractEndpoint(line) {
119
+ // Match fetch("/api/...") or fetch('/api/...')
120
+ const match = line.match(/fetch\s*\(\s*["'`](\/api[^"'`]+)["'`]/);
121
+ if (match) {
122
+ return match[1] || null;
123
+ }
124
+ // Match fetch(url) where url contains /api/
125
+ if (line.includes("fetch") && line.includes("/api/")) {
126
+ const apiMatch = line.match(/["'`](\/api[^"'`]+)["'`]/);
127
+ return apiMatch?.[1] || "/api/... (dynamic)";
128
+ }
129
+ return null;
130
+ }
131
+ function scanFile(filePath) {
132
+ const issues = [];
133
+ try {
134
+ const content = fs.readFileSync(filePath, "utf-8");
135
+ // Only check client components
136
+ if (!isClientComponent(content)) {
137
+ return issues;
138
+ }
139
+ // If the file already uses authFetch, it's good
140
+ if (hasAuthFetchImport(content)) {
141
+ return issues;
142
+ }
143
+ const lines = content.split("\n");
144
+ for (let i = 0; i < lines.length; i++) {
145
+ const line = lines[i] || "";
146
+ // Skip comments
147
+ if (line.trim().startsWith("//") || line.trim().startsWith("*")) {
148
+ continue;
149
+ }
150
+ // Check for fetch calls
151
+ if (line.includes("fetch(") || line.includes("fetch (")) {
152
+ const endpoint = extractEndpoint(line);
153
+ if (endpoint && isProtectedEndpoint(endpoint)) {
154
+ // Check if there's explicit auth handling nearby
155
+ if (!hasExplicitAuthHandling(content, i)) {
156
+ issues.push({
157
+ file: filePath,
158
+ line: i + 1,
159
+ endpoint,
160
+ severity: "warning",
161
+ message: `Unprotected fetch to authenticated endpoint. Use authFetch from "@/lib/auth-fetch" to handle stale session errors.`,
162
+ });
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+ catch (error) {
169
+ // Skip files that can't be read
170
+ }
171
+ return issues;
172
+ }
173
+ function findClientComponents(dir) {
174
+ const files = [];
175
+ function walk(directory) {
176
+ try {
177
+ const entries = fs.readdirSync(directory, { withFileTypes: true });
178
+ for (const entry of entries) {
179
+ const fullPath = path.join(directory, entry.name);
180
+ if (shouldSkipFile(fullPath)) {
181
+ continue;
182
+ }
183
+ if (entry.isDirectory()) {
184
+ walk(fullPath);
185
+ }
186
+ else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name)) {
187
+ files.push(fullPath);
188
+ }
189
+ }
190
+ }
191
+ catch {
192
+ // Skip directories that can't be read
193
+ }
194
+ }
195
+ walk(dir);
196
+ return files;
197
+ }
198
+ function main() {
199
+ const projectRoot = process.cwd();
200
+ const scanDirs = ["app", "components", "lib"].map((d) => path.join(projectRoot, d));
201
+ let allFiles = [];
202
+ for (const dir of scanDirs) {
203
+ if (fs.existsSync(dir)) {
204
+ allFiles = allFiles.concat(findClientComponents(dir));
205
+ }
206
+ }
207
+ const allIssues = [];
208
+ for (const file of allFiles) {
209
+ const issues = scanFile(file);
210
+ allIssues.push(...issues);
211
+ }
212
+ // Output results
213
+ if (jsonOutput) {
214
+ console.log(JSON.stringify(allIssues, null, 2));
215
+ process.exit(allIssues.length > 0 ? 1 : 0);
216
+ }
217
+ // Human-readable output
218
+ console.log("");
219
+ console.log("🔐 Auth Error Handling Preflight");
220
+ console.log("━".repeat(50));
221
+ if (allIssues.length === 0) {
222
+ console.log("✅ No issues found. All authenticated endpoints are properly protected.");
223
+ process.exit(0);
224
+ }
225
+ console.log(`⚠️ Found ${allIssues.length} unprotected authenticated API calls\n`);
226
+ // Group by file
227
+ const byFile = new Map();
228
+ for (const issue of allIssues) {
229
+ const relativePath = path.relative(projectRoot, issue.file);
230
+ if (!byFile.has(relativePath)) {
231
+ byFile.set(relativePath, []);
232
+ }
233
+ byFile.get(relativePath).push(issue);
234
+ }
235
+ // Display (limit to 10 files unless --all)
236
+ const entries = [...byFile.entries()];
237
+ const displayEntries = showAll ? entries : entries.slice(0, 10);
238
+ for (const [file, issues] of displayEntries) {
239
+ console.log(`📁 ${file}`);
240
+ for (const issue of issues) {
241
+ console.log(` Line ${issue.line}: ${issue.endpoint}`);
242
+ }
243
+ console.log("");
244
+ }
245
+ if (!showAll && entries.length > 10) {
246
+ console.log(` ... and ${entries.length - 10} more files`);
247
+ console.log(` Use --all to see all issues`);
248
+ console.log("");
249
+ }
250
+ console.log("━".repeat(50));
251
+ console.log(`\n🔧 Fix: Replace fetch("/api/store/...") with authFetch("/api/store/...")`);
252
+ console.log(` Import: import { authFetch } from "@/lib/auth-fetch";`);
253
+ console.log("");
254
+ process.exit(1);
255
+ }
256
+ main();
257
+ //# sourceMappingURL=auth-error-handling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-error-handling.js","sourceRoot":"","sources":["../../../src/checks/auth/auth-error-handling.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Undefined Tailwind Utilities Preflight (BLOCKING)
4
+ *
5
+ * Detects Tailwind utility classes that reference undefined CSS variables.
6
+ * This prevents invisible UI elements caused by missing theme tokens.
7
+ *
8
+ * Common issue detected:
9
+ * - `bg-background` used when theme defines `--bg-primary` instead of `--background`
10
+ * - Other utility classes that expect shadcn-style CSS variables
11
+ *
12
+ * Why this matters:
13
+ * - Using undefined CSS variables results in invisible elements
14
+ * - Components may appear broken (e.g., toggle thumbs not visible)
15
+ * - These issues are hard to detect visually until specific states are reached
16
+ *
17
+ * Usage:
18
+ * pnpm preflight:undefined-tailwind-utilities
19
+ * pnpm preflight:undefined-tailwind-utilities --verbose
20
+ */
21
+ export declare const id = "tailwind/undefined-tailwind-utilities";
22
+ export declare const name = "Undefined Tailwind Utilities";
23
+ export declare const category = "tailwind";
24
+ export declare const blocking = true;
25
+ export declare const description = "Detects Tailwind classes using undefined CSS variables (BLOCKING)";
26
+ export declare const tags: string[];
27
+ export declare const requires: string[];
28
+ export declare function run(): Promise<void>;
29
+ //# sourceMappingURL=undefined-tailwind-utilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"undefined-tailwind-utilities.d.ts","sourceRoot":"","sources":["../../../src/checks/tailwind/undefined-tailwind-utilities.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;GAkBG;AASH,eAAO,MAAM,EAAE,0CAA0C,CAAC;AAC1D,eAAO,MAAM,IAAI,iCAAiC,CAAC;AACnD,eAAO,MAAM,QAAQ,aAAa,CAAC;AACnC,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,WAAW,sEAAsE,CAAC;AAC/F,eAAO,MAAM,IAAI,UAAiD,CAAC;AACnE,eAAO,MAAM,QAAQ,UAAkB,CAAC;AAoLxC,wBAAsB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAuHzC"}
@@ -0,0 +1,337 @@
1
+ #!/usr/bin/env tsx
2
+ "use strict";
3
+ /**
4
+ * Undefined Tailwind Utilities Preflight (BLOCKING)
5
+ *
6
+ * Detects Tailwind utility classes that reference undefined CSS variables.
7
+ * This prevents invisible UI elements caused by missing theme tokens.
8
+ *
9
+ * Common issue detected:
10
+ * - `bg-background` used when theme defines `--bg-primary` instead of `--background`
11
+ * - Other utility classes that expect shadcn-style CSS variables
12
+ *
13
+ * Why this matters:
14
+ * - Using undefined CSS variables results in invisible elements
15
+ * - Components may appear broken (e.g., toggle thumbs not visible)
16
+ * - These issues are hard to detect visually until specific states are reached
17
+ *
18
+ * Usage:
19
+ * pnpm preflight:undefined-tailwind-utilities
20
+ * pnpm preflight:undefined-tailwind-utilities --verbose
21
+ */
22
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ var desc = Object.getOwnPropertyDescriptor(m, k);
25
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
26
+ desc = { enumerable: true, get: function() { return m[k]; } };
27
+ }
28
+ Object.defineProperty(o, k2, desc);
29
+ }) : (function(o, m, k, k2) {
30
+ if (k2 === undefined) k2 = k;
31
+ o[k2] = m[k];
32
+ }));
33
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
34
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
35
+ }) : function(o, v) {
36
+ o["default"] = v;
37
+ });
38
+ var __importStar = (this && this.__importStar) || (function () {
39
+ var ownKeys = function(o) {
40
+ ownKeys = Object.getOwnPropertyNames || function (o) {
41
+ var ar = [];
42
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
43
+ return ar;
44
+ };
45
+ return ownKeys(o);
46
+ };
47
+ return function (mod) {
48
+ if (mod && mod.__esModule) return mod;
49
+ var result = {};
50
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
51
+ __setModuleDefault(result, mod);
52
+ return result;
53
+ };
54
+ })();
55
+ Object.defineProperty(exports, "__esModule", { value: true });
56
+ exports.requires = exports.tags = exports.description = exports.blocking = exports.category = exports.name = exports.id = void 0;
57
+ exports.run = run;
58
+ const fs = __importStar(require("fs"));
59
+ const file_cache_1 = require("../../shared/file-cache");
60
+ const glob_patterns_1 = require("../../shared/glob-patterns");
61
+ const console_chars_1 = require("../../utils/console-chars");
62
+ const universal_progress_reporter_1 = require("../system/universal-progress-reporter");
63
+ // Check metadata
64
+ exports.id = "tailwind/undefined-tailwind-utilities";
65
+ exports.name = "Undefined Tailwind Utilities";
66
+ exports.category = "tailwind";
67
+ exports.blocking = true;
68
+ exports.description = "Detects Tailwind classes using undefined CSS variables (BLOCKING)";
69
+ exports.tags = ["tailwind", "css-variables", "theming", "ui"];
70
+ exports.requires = ["tailwindcss"];
71
+ const EXCLUDED = (0, glob_patterns_1.extendExcludes)(glob_patterns_1.STANDARD_EXCLUDES, [
72
+ "**/test-results/**",
73
+ "**/*.test.tsx",
74
+ "**/*.spec.tsx",
75
+ "**/*.stories.tsx",
76
+ ]);
77
+ /**
78
+ * Tailwind utility classes that rely on CSS variables not defined in our theme.
79
+ * These often come from copying shadcn/ui components that expect different variable names.
80
+ *
81
+ * Patterns use (?:\/\d+)? to optionally match opacity modifiers like /20, /50, etc.
82
+ *
83
+ * Format: { pattern, name, message, suggestion, severity }
84
+ */
85
+ const UNDEFINED_UTILITIES = [
86
+ {
87
+ // shadcn expects --background, we use --bg-primary
88
+ pattern: /\bbg-background(?:\/\d+)?\b/g,
89
+ name: "bg-background",
90
+ message: "bg-background uses undefined --background CSS variable",
91
+ suggestion: "Use bg-white, bg-[var(--bg-primary)], or appropriate color class",
92
+ severity: "error",
93
+ },
94
+ {
95
+ // shadcn expects --foreground, we use --text-primary
96
+ pattern: /\btext-foreground(?:\/\d+)?\b/g,
97
+ name: "text-foreground",
98
+ message: "text-foreground uses undefined --foreground CSS variable",
99
+ suggestion: "Use text-[var(--text-primary)] or appropriate color class",
100
+ severity: "error",
101
+ },
102
+ {
103
+ // shadcn expects --muted, we use --text-secondary or --bg-tertiary
104
+ // Matches: bg-muted, bg-muted/20, bg-muted/50, etc.
105
+ pattern: /\bbg-muted(?:\/\d+)?\b/g,
106
+ name: "bg-muted",
107
+ message: "bg-muted uses undefined --muted CSS variable",
108
+ suggestion: "Use bg-[var(--bg-tertiary)] or bg-[var(--bg-tertiary)]/20 for opacity",
109
+ severity: "error",
110
+ },
111
+ {
112
+ // Matches: text-muted-foreground, text-muted-foreground/70, etc.
113
+ pattern: /\btext-muted-foreground(?:\/\d+)?\b/g,
114
+ name: "text-muted-foreground",
115
+ message: "text-muted-foreground uses undefined --muted-foreground CSS variable",
116
+ suggestion: "Use text-[var(--text-secondary)] or text-[var(--text-secondary)]/70 for opacity",
117
+ severity: "error",
118
+ },
119
+ {
120
+ // shadcn expects --card, we use --bg-primary
121
+ pattern: /\bbg-card(?:\/\d+)?\b/g,
122
+ name: "bg-card",
123
+ message: "bg-card uses undefined --card CSS variable",
124
+ suggestion: "Use bg-[var(--bg-primary)] or appropriate color class",
125
+ severity: "error",
126
+ },
127
+ {
128
+ // shadcn expects --popover
129
+ pattern: /\bbg-popover(?:\/\d+)?\b/g,
130
+ name: "bg-popover",
131
+ message: "bg-popover uses undefined --popover CSS variable",
132
+ suggestion: "Use bg-[var(--bg-primary)] or bg-white for popover backgrounds",
133
+ severity: "error",
134
+ },
135
+ {
136
+ // shadcn expects --accent
137
+ pattern: /\bbg-accent(?:\/\d+)?\b/g,
138
+ name: "bg-accent",
139
+ message: "bg-accent uses undefined --accent CSS variable",
140
+ suggestion: "Use bg-[var(--color-primary-light)] or appropriate accent color",
141
+ severity: "warning",
142
+ },
143
+ {
144
+ pattern: /\btext-accent-foreground(?:\/\d+)?\b/g,
145
+ name: "text-accent-foreground",
146
+ message: "text-accent-foreground uses undefined --accent-foreground CSS variable",
147
+ suggestion: "Use text-[var(--color-primary)] or appropriate color class",
148
+ severity: "warning",
149
+ },
150
+ {
151
+ // shadcn expects --border, we use --border-color
152
+ pattern: /\bborder-border(?:\/\d+)?\b/g,
153
+ name: "border-border",
154
+ message: "border-border uses undefined --border CSS variable",
155
+ suggestion: "Use border-[var(--border-color)] instead",
156
+ severity: "error",
157
+ },
158
+ {
159
+ // shadcn expects --input
160
+ pattern: /\bbg-input(?:\/\d+)?\b/g,
161
+ name: "bg-input",
162
+ message: "bg-input uses undefined --input CSS variable",
163
+ suggestion: "Use bg-[var(--bg-secondary)] or bg-white for input backgrounds",
164
+ severity: "warning",
165
+ },
166
+ {
167
+ // shadcn expects --ring
168
+ pattern: /\bring-ring(?:\/\d+)?\b/g,
169
+ name: "ring-ring",
170
+ message: "ring-ring uses undefined --ring CSS variable",
171
+ suggestion: "Use ring-[var(--color-primary)] for focus rings",
172
+ severity: "warning",
173
+ },
174
+ {
175
+ // shadcn expects --destructive
176
+ pattern: /\bbg-destructive(?:\/\d+)?\b/g,
177
+ name: "bg-destructive",
178
+ message: "bg-destructive uses undefined --destructive CSS variable",
179
+ suggestion: "Use bg-[var(--error-main)] for destructive/error backgrounds",
180
+ severity: "warning",
181
+ },
182
+ {
183
+ pattern: /\btext-destructive(?:\/\d+)?\b/g,
184
+ name: "text-destructive",
185
+ message: "text-destructive uses undefined --destructive CSS variable",
186
+ suggestion: "Use text-[var(--error-main)] for error text",
187
+ severity: "warning",
188
+ },
189
+ ];
190
+ function checkFile(filePath, content) {
191
+ const issues = [];
192
+ const lines = content.split("\n");
193
+ for (const utility of UNDEFINED_UTILITIES) {
194
+ let match;
195
+ // Reset lastIndex for global regex
196
+ utility.pattern.lastIndex = 0;
197
+ while ((match = utility.pattern.exec(content)) !== null) {
198
+ // Find line number
199
+ let lineNumber = 1;
200
+ let charCount = 0;
201
+ for (let i = 0; i < lines.length; i++) {
202
+ charCount += lines[i].length + 1;
203
+ if (charCount > match.index) {
204
+ lineNumber = i + 1;
205
+ break;
206
+ }
207
+ }
208
+ issues.push({
209
+ file: filePath,
210
+ line: lineNumber,
211
+ type: utility.name,
212
+ severity: utility.severity,
213
+ message: utility.message,
214
+ suggestion: utility.suggestion,
215
+ snippet: lines[lineNumber - 1]?.trim().substring(0, 100),
216
+ match: match[0],
217
+ });
218
+ }
219
+ }
220
+ return issues;
221
+ }
222
+ // CACHED FILE LISTS
223
+ let _cachedComponentFiles = null;
224
+ async function getComponentFiles() {
225
+ if (!_cachedComponentFiles) {
226
+ _cachedComponentFiles = await file_cache_1.fileCache.getComponentFiles();
227
+ }
228
+ return _cachedComponentFiles;
229
+ }
230
+ async function run() {
231
+ const reporter = (0, universal_progress_reporter_1.createUniversalProgressReporter)(exports.name);
232
+ const args = process.argv.slice(2);
233
+ const verbose = args.includes("--verbose") || args.includes("-v");
234
+ const warnOnly = args.includes("--warn") || args.includes("-w");
235
+ console.log(`\n${console_chars_1.emoji.palette} Undefined Tailwind Utilities Preflight`);
236
+ console.log((0, console_chars_1.createDivider)(80, "heavy"));
237
+ if (warnOnly) {
238
+ console.log(`${console_chars_1.emoji.warning} Running in warning mode - not blocking.\n`);
239
+ }
240
+ const startTime = Date.now();
241
+ const allIssues = [];
242
+ // Find all component files
243
+ const files = await getComponentFiles();
244
+ let filesScanned = 0;
245
+ for (const file of files) {
246
+ try {
247
+ const content = fs.readFileSync(file, "utf-8");
248
+ filesScanned++;
249
+ allIssues.push(...checkFile(file, content));
250
+ }
251
+ catch (error) {
252
+ if (verbose) {
253
+ console.error(`${console_chars_1.emoji.warning} Error reading ${file}: ${error.message}`);
254
+ }
255
+ }
256
+ }
257
+ const duration = Date.now() - startTime;
258
+ // Report results
259
+ console.log(`\n${console_chars_1.emoji.chart} Scan Summary:`);
260
+ console.log(` Files scanned: ${filesScanned}`);
261
+ console.log(` Duration: ${duration}ms`);
262
+ const errors = allIssues.filter((i) => i.severity === "error");
263
+ const warnings = allIssues.filter((i) => i.severity === "warning");
264
+ const infos = allIssues.filter((i) => i.severity === "info");
265
+ if (allIssues.length === 0) {
266
+ console.log(`\n${console_chars_1.emoji.success} No undefined Tailwind utility classes found`);
267
+ console.log(` All CSS variable references are valid.`);
268
+ process.exit(0);
269
+ }
270
+ // Group by type
271
+ const byType = new Map();
272
+ for (const issue of allIssues) {
273
+ const existing = byType.get(issue.type) || [];
274
+ existing.push(issue);
275
+ byType.set(issue.type, existing);
276
+ }
277
+ console.log(`\n${console_chars_1.emoji.clipboard} Issues Found:`);
278
+ console.log(` ${console_chars_1.emoji.error} Errors: ${errors.length}`);
279
+ console.log(` ${console_chars_1.emoji.warning} Warnings: ${warnings.length}`);
280
+ console.log(` ${console_chars_1.emoji.info} Info: ${infos.length}`);
281
+ // Print issues by type
282
+ for (const [type, issues] of byType) {
283
+ const icon = issues[0].severity === "error"
284
+ ? `${console_chars_1.emoji.error}`
285
+ : issues[0].severity === "warning"
286
+ ? `${console_chars_1.emoji.warning}`
287
+ : `${console_chars_1.emoji.info}`;
288
+ console.log(`\n${icon} ${type} (${issues.length} occurrences)`);
289
+ if (verbose) {
290
+ for (const issue of issues.slice(0, 5)) {
291
+ console.log(` ${issue.file}:${issue.line}`);
292
+ console.log(` ${issue.message}`);
293
+ console.log(` ${console_chars_1.emoji.hint} ${issue.suggestion}`);
294
+ if (issue.snippet) {
295
+ console.log(` ${console_chars_1.emoji.memo} ${issue.snippet}`);
296
+ }
297
+ console.log("");
298
+ }
299
+ if (issues.length > 5) {
300
+ console.log(` ... and ${issues.length - 5} more`);
301
+ }
302
+ }
303
+ else {
304
+ console.log(` ${issues[0].message}`);
305
+ console.log(` ${console_chars_1.emoji.hint} ${issues[0].suggestion}`);
306
+ }
307
+ }
308
+ // Print background info
309
+ console.log(`\n${console_chars_1.emoji.docs} Background:`);
310
+ console.log(` This project uses custom CSS variable names (--bg-primary,`);
311
+ console.log(` --text-primary, etc.) instead of shadcn defaults (--background,`);
312
+ console.log(` --foreground, etc.). Using shadcn-style classes results in`);
313
+ console.log(` undefined colors and invisible UI elements.`);
314
+ console.log("");
315
+ console.log(`${console_chars_1.emoji.hint} Common fixes:`);
316
+ console.log(` • bg-background → bg-white or bg-[var(--bg-primary)]`);
317
+ console.log(` • text-foreground → text-[var(--text-primary)]`);
318
+ console.log(` • bg-muted → bg-[var(--bg-tertiary)]`);
319
+ console.log(` • border-border → border-[var(--border-color)]`);
320
+ // Exit code
321
+ if (warnOnly) {
322
+ console.log(`\n${console_chars_1.emoji.warning} Running in warning mode - not blocking`);
323
+ process.exit(0);
324
+ }
325
+ if (errors.length > 0) {
326
+ console.log(`\n⛔ ${errors.length} blocking issue(s) found. Fix before deploying.`);
327
+ console.log(` These will cause invisible or broken UI elements.`);
328
+ process.exit(1);
329
+ }
330
+ console.log(`\n${console_chars_1.emoji.success} No blocking issues (warnings/info only)`);
331
+ process.exit(0);
332
+ }
333
+ // Allow direct execution
334
+ if (require.main === module) {
335
+ run().catch(console.error);
336
+ }
337
+ //# sourceMappingURL=undefined-tailwind-utilities.js.map