@empline/preflight 1.1.28 → 1.1.30

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,14 @@
1
+ #!/usr/bin/env node
2
+ export declare const id = "api/integration-schema-consistency";
3
+ export declare const name = "Integration Schema Consistency";
4
+ export declare const description = "Detects mismatches between UI types and API schemas";
5
+ export declare const category = "api";
6
+ export declare const blocking = true;
7
+ export declare const tags: string[];
8
+ export declare const requires: string[];
9
+ export declare function run(): Promise<{
10
+ success: boolean;
11
+ errors: number;
12
+ warnings: number;
13
+ }>;
14
+ //# sourceMappingURL=integration-schema-consistency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration-schema-consistency.d.ts","sourceRoot":"","sources":["../../../src/checks/api/integration-schema-consistency.ts"],"names":[],"mappings":";AAkBA,eAAO,MAAM,EAAE,uCAAuC,CAAC;AACvD,eAAO,MAAM,IAAI,mCAAmC,CAAC;AACrD,eAAO,MAAM,WAAW,wDAAwD,CAAC;AACjF,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,IAAI,UAA4C,CAAC;AAC9D,eAAO,MAAM,QAAQ,UAA0B,CAAC;AA+ChD,wBAAsB,GAAG,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAoG3F"}
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.requires = exports.tags = exports.blocking = exports.category = exports.description = exports.name = exports.id = void 0;
8
+ exports.run = run;
9
+ /**
10
+ * Integration Schema Consistency Preflight
11
+ *
12
+ * Detects mismatches between UI type definitions and API schemas for integration endpoints.
13
+ * Common issues this catches:
14
+ * - Field name mismatches (e.g., UI uses `wooField` but API expects `sourceField`)
15
+ * - Feature flag mismatches (e.g., UI uses `linkProducts` but API expects `exportListings`)
16
+ * - Missing fields in API schemas that UI depends on
17
+ *
18
+ * This helps prevent silent data loss where UI sends fields that get stripped by validation.
19
+ */
20
+ const fs_1 = __importDefault(require("fs"));
21
+ const path_1 = __importDefault(require("path"));
22
+ const console_chars_1 = require("../../utils/console-chars");
23
+ // METADATA - Required for plugin loader discovery
24
+ exports.id = "api/integration-schema-consistency";
25
+ exports.name = "Integration Schema Consistency";
26
+ exports.description = "Detects mismatches between UI types and API schemas";
27
+ exports.category = "api";
28
+ exports.blocking = true; // This is a critical issue that can cause data loss
29
+ exports.tags = ["api", "schema", "types", "integration"];
30
+ exports.requires = ["trading-card-system"];
31
+ /**
32
+ * Integration endpoints to check
33
+ */
34
+ const INTEGRATION_CHECKS = [
35
+ {
36
+ name: "WooCommerce",
37
+ uiTypePath: "components/woocommerce/types.ts",
38
+ apiRoutePath: "app/api/store/integrations/woocommerce/route.ts",
39
+ fieldMappings: [
40
+ { uiField: "wooField", apiField: "sourceField" },
41
+ { uiField: "supercatchField", apiField: "targetField" },
42
+ { uiField: "linkProducts", apiField: "linkProducts" },
43
+ { uiField: "importProducts", apiField: "importProducts" },
44
+ { uiField: "syncInventory", apiField: "syncInventory" },
45
+ { uiField: "syncPrices", apiField: "syncPrices" },
46
+ { uiField: "syncOrders", apiField: "syncOrders" },
47
+ ],
48
+ },
49
+ {
50
+ name: "Shopify",
51
+ uiTypePath: "components/shopify/types.ts",
52
+ apiRoutePath: "app/api/store/integrations/shopify/route.ts",
53
+ fieldMappings: [
54
+ { uiField: "importProducts", apiField: "importProducts" },
55
+ { uiField: "syncInventory", apiField: "syncInventory" },
56
+ { uiField: "syncPrices", apiField: "syncPrices" },
57
+ ],
58
+ },
59
+ ];
60
+ async function run() {
61
+ console.log(`\n${console_chars_1.emoji.check} INTEGRATION SCHEMA CONSISTENCY`);
62
+ console.log((0, console_chars_1.createDivider)(65, "heavy"));
63
+ const issues = [];
64
+ for (const check of INTEGRATION_CHECKS) {
65
+ const uiPath = path_1.default.join(process.cwd(), check.uiTypePath);
66
+ const apiPath = path_1.default.join(process.cwd(), check.apiRoutePath);
67
+ // Check if files exist
68
+ if (!fs_1.default.existsSync(uiPath)) {
69
+ console.log(` Skipping ${check.name}: UI types file not found`);
70
+ continue;
71
+ }
72
+ if (!fs_1.default.existsSync(apiPath)) {
73
+ console.log(` Skipping ${check.name}: API route file not found`);
74
+ continue;
75
+ }
76
+ const uiContent = fs_1.default.readFileSync(uiPath, "utf-8");
77
+ const apiContent = fs_1.default.readFileSync(apiPath, "utf-8");
78
+ console.log(`\n Checking ${check.name}...`);
79
+ // Check field mappings
80
+ for (const mapping of check.fieldMappings) {
81
+ const uiHasField = new RegExp(`\\b${mapping.uiField}\\b`).test(uiContent);
82
+ const apiHasField = new RegExp(`\\b${mapping.apiField}\\b`).test(apiContent);
83
+ if (uiHasField && !apiHasField) {
84
+ issues.push({
85
+ integration: check.name,
86
+ type: "missing_in_api",
87
+ uiField: mapping.uiField,
88
+ apiField: mapping.apiField,
89
+ description: `UI uses '${mapping.uiField}' but API schema doesn't accept '${mapping.apiField}'`,
90
+ });
91
+ }
92
+ // Check for field name mismatch (UI sends different name than API expects)
93
+ if (mapping.uiField !== mapping.apiField) {
94
+ const apiAcceptsBoth = new RegExp(`\\b${mapping.uiField}\\b`).test(apiContent) ||
95
+ new RegExp(`\\b${mapping.apiField}\\b`).test(apiContent);
96
+ if (uiHasField && !apiAcceptsBoth) {
97
+ issues.push({
98
+ integration: check.name,
99
+ type: "field_mismatch",
100
+ uiField: mapping.uiField,
101
+ apiField: mapping.apiField,
102
+ description: `UI sends '${mapping.uiField}' but API may only accept '${mapping.apiField}' - ensure API handles both`,
103
+ });
104
+ }
105
+ }
106
+ }
107
+ // Check for exportListings vs linkProducts mismatch specifically
108
+ const uiHasLinkProducts = /\blinkProducts\b/.test(uiContent);
109
+ const apiHasLinkProducts = /\blinkProducts\b/.test(apiContent);
110
+ const apiHasExportListings = /\bexportListings\b/.test(apiContent);
111
+ if (uiHasLinkProducts && apiHasExportListings && !apiHasLinkProducts) {
112
+ issues.push({
113
+ integration: check.name,
114
+ type: "field_mismatch",
115
+ uiField: "linkProducts",
116
+ apiField: "exportListings",
117
+ description: `UI uses 'linkProducts' but API schema uses 'exportListings' - data will be silently dropped`,
118
+ });
119
+ }
120
+ }
121
+ // Summary
122
+ console.log(`\n${console_chars_1.emoji.chart} Summary:`);
123
+ console.log(` Integrations checked: ${INTEGRATION_CHECKS.length}`);
124
+ console.log(` Issues found: ${issues.length}`);
125
+ if (issues.length === 0) {
126
+ console.log(`\n${console_chars_1.emoji.success} INTEGRATION SCHEMA CONSISTENCY PASSED`);
127
+ return { success: true, errors: 0, warnings: 0 };
128
+ }
129
+ console.log(`\n${console_chars_1.emoji.error} Schema mismatches found:`);
130
+ for (const issue of issues) {
131
+ console.log(`\n ${issue.integration}:`);
132
+ console.log(` Type: ${issue.type}`);
133
+ console.log(` UI Field: ${issue.uiField}`);
134
+ console.log(` API Field: ${issue.apiField}`);
135
+ console.log(` ${issue.description}`);
136
+ }
137
+ console.log(`\n${console_chars_1.emoji.info} To fix schema mismatches:`);
138
+ console.log(` 1. Ensure API schema accepts the field names UI sends`);
139
+ console.log(` 2. Use Zod transform() to normalize field names if needed`);
140
+ console.log(` 3. Update UI types to match API expectations`);
141
+ console.log(`\n${console_chars_1.emoji.error} INTEGRATION SCHEMA CONSISTENCY FAILED`);
142
+ return { success: false, errors: issues.length, warnings: 0 };
143
+ }
144
+ // Allow direct execution
145
+ if (require.main === module) {
146
+ run()
147
+ .then((result) => {
148
+ process.exit(result.success ? 0 : 1);
149
+ })
150
+ .catch((err) => {
151
+ console.error(`${console_chars_1.emoji.error} Preflight failed:`, err);
152
+ process.exit(1);
153
+ });
154
+ }
155
+ //# sourceMappingURL=integration-schema-consistency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration-schema-consistency.js","sourceRoot":"","sources":["../../../src/checks/api/integration-schema-consistency.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ export declare const id = "react/state-update-during-render";
3
+ export declare const name = "State Update During Render Detection";
4
+ export declare const description = "Detects setState calls outside of effects and callbacks";
5
+ export declare const category = "react";
6
+ export declare const blocking = true;
7
+ export declare const tags: string[];
8
+ export declare function run(): Promise<{
9
+ success: boolean;
10
+ errors: number;
11
+ warnings: number;
12
+ }>;
13
+ //# sourceMappingURL=state-update-during-render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-update-during-render.d.ts","sourceRoot":"","sources":["../../../src/checks/react/state-update-during-render.ts"],"names":[],"mappings":";AA0CA,eAAO,MAAM,EAAE,qCAAqC,CAAC;AACrD,eAAO,MAAM,IAAI,yCAAyC,CAAC;AAC3D,eAAO,MAAM,WAAW,4DAA4D,CAAC;AACrF,eAAO,MAAM,QAAQ,UAAU,CAAC;AAChC,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,IAAI,UAAwC,CAAC;AAuG1D,wBAAsB,GAAG,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA6F3F"}
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.tags = exports.blocking = exports.category = exports.description = exports.name = exports.id = void 0;
8
+ exports.run = run;
9
+ /**
10
+ * State Update During Render Preflight
11
+ *
12
+ * Detects React anti-pattern of calling setState during render.
13
+ * This can cause:
14
+ * - Infinite render loops
15
+ * - UI flickering
16
+ * - Unpredictable behavior
17
+ *
18
+ * Common problematic patterns:
19
+ * ```tsx
20
+ * function Component() {
21
+ * if (condition) {
22
+ * setState(newValue); // BAD: called during render
23
+ * return null;
24
+ * }
25
+ * }
26
+ * ```
27
+ *
28
+ * Correct pattern:
29
+ * ```tsx
30
+ * function Component() {
31
+ * useEffect(() => {
32
+ * if (condition) {
33
+ * setState(newValue); // GOOD: called in effect
34
+ * }
35
+ * }, [condition]);
36
+ *
37
+ * if (condition) {
38
+ * return null;
39
+ * }
40
+ * }
41
+ * ```
42
+ */
43
+ const fs_1 = __importDefault(require("fs"));
44
+ const path_1 = __importDefault(require("path"));
45
+ const glob_1 = require("glob");
46
+ const console_chars_1 = require("../../utils/console-chars");
47
+ // METADATA - Required for plugin loader discovery
48
+ exports.id = "react/state-update-during-render";
49
+ exports.name = "State Update During Render Detection";
50
+ exports.description = "Detects setState calls outside of effects and callbacks";
51
+ exports.category = "react";
52
+ exports.blocking = true; // This is a critical React bug
53
+ exports.tags = ["react", "state", "hooks", "render"];
54
+ /**
55
+ * File patterns to check
56
+ */
57
+ const FILE_PATTERNS = [
58
+ "app/**/*.tsx",
59
+ "components/**/*.tsx",
60
+ ];
61
+ /**
62
+ * Files/directories to exclude
63
+ */
64
+ const EXCLUDE_PATTERNS = [
65
+ "node_modules/**",
66
+ "**/*.test.tsx",
67
+ "**/*.spec.tsx",
68
+ "**/*.stories.tsx",
69
+ ];
70
+ /**
71
+ * Patterns that indicate a setState call is inside a safe context
72
+ */
73
+ const SAFE_CONTEXTS = [
74
+ /useEffect\s*\(\s*\(\)\s*=>\s*\{/,
75
+ /useCallback\s*\(\s*\([^)]*\)\s*=>\s*\{/,
76
+ /useMemo\s*\(\s*\(\)\s*=>\s*\{/,
77
+ /useLayoutEffect\s*\(\s*\(\)\s*=>\s*\{/,
78
+ /onClick\s*=\s*\{/,
79
+ /onChange\s*=\s*\{/,
80
+ /onSubmit\s*=\s*\{/,
81
+ /onBlur\s*=\s*\{/,
82
+ /onFocus\s*=\s*\{/,
83
+ /addEventListener\s*\(/,
84
+ /\.then\s*\(/,
85
+ /\.catch\s*\(/,
86
+ /async\s+function/,
87
+ /const\s+\w+\s*=\s*async/,
88
+ /=>\s*\{[\s\S]*?await\b/,
89
+ ];
90
+ /**
91
+ * Patterns for setState calls
92
+ */
93
+ const SET_STATE_PATTERN = /\b(set[A-Z]\w+)\s*\(/g;
94
+ /**
95
+ * Check if position is inside a safe context (effect, callback, handler)
96
+ */
97
+ function isInSafeContext(content, position) {
98
+ // Look backwards to find context
99
+ const before = content.substring(Math.max(0, position - 500), position);
100
+ // Count braces to determine nesting
101
+ let braceCount = 0;
102
+ for (let i = before.length - 1; i >= 0; i--) {
103
+ if (before[i] === "}")
104
+ braceCount++;
105
+ if (before[i] === "{")
106
+ braceCount--;
107
+ // If we've exited the current block, check what opened it
108
+ if (braceCount < 0) {
109
+ const contextBefore = before.substring(0, i + 1);
110
+ return SAFE_CONTEXTS.some(pattern => pattern.test(contextBefore));
111
+ }
112
+ }
113
+ return false;
114
+ }
115
+ /**
116
+ * Check if the setState is immediately followed by return (common problematic pattern)
117
+ */
118
+ function isFollowedByReturn(content, position) {
119
+ const after = content.substring(position, Math.min(content.length, position + 100));
120
+ // Pattern: setState(...); return or setState(...)\n return
121
+ return /^\([^)]*\);\s*\n?\s*return\b/.test(after);
122
+ }
123
+ /**
124
+ * Get line number from position
125
+ */
126
+ function getLineNumber(content, position) {
127
+ return content.substring(0, position).split("\n").length;
128
+ }
129
+ /**
130
+ * Get surrounding code context
131
+ */
132
+ function getCodeContext(content, position) {
133
+ const lines = content.split("\n");
134
+ const lineNum = getLineNumber(content, position);
135
+ const start = Math.max(0, lineNum - 2);
136
+ const end = Math.min(lines.length, lineNum + 2);
137
+ return lines.slice(start, end).join("\n").trim();
138
+ }
139
+ async function run() {
140
+ console.log(`\n${console_chars_1.emoji.components} STATE UPDATE DURING RENDER DETECTION`);
141
+ console.log((0, console_chars_1.createDivider)(65, "heavy"));
142
+ const issues = [];
143
+ // Find all TSX files
144
+ const allFiles = [];
145
+ for (const pattern of FILE_PATTERNS) {
146
+ const matches = await (0, glob_1.glob)(pattern, {
147
+ cwd: process.cwd(),
148
+ ignore: EXCLUDE_PATTERNS,
149
+ });
150
+ allFiles.push(...matches);
151
+ }
152
+ const uniqueFiles = [...new Set(allFiles)];
153
+ console.log(`\n${console_chars_1.emoji.search} Scanning ${uniqueFiles.length} TSX files...`);
154
+ for (const relativePath of uniqueFiles) {
155
+ const filePath = path_1.default.join(process.cwd(), relativePath);
156
+ if (!fs_1.default.existsSync(filePath))
157
+ continue;
158
+ const content = fs_1.default.readFileSync(filePath, "utf-8");
159
+ // Find all setState calls
160
+ let match;
161
+ while ((match = SET_STATE_PATTERN.exec(content)) !== null) {
162
+ const position = match.index;
163
+ const setter = match[1];
164
+ // Skip if in a safe context
165
+ if (isInSafeContext(content, position)) {
166
+ continue;
167
+ }
168
+ // Check if this looks like a render-time state update
169
+ // (followed by return, which is a common pattern)
170
+ if (isFollowedByReturn(content, position)) {
171
+ const lineNum = getLineNumber(content, position);
172
+ // Check for preflight-ignore comment
173
+ const lineStart = content.lastIndexOf('\n', position) + 1;
174
+ const lineBefore = content.substring(Math.max(0, lineStart - 100), lineStart);
175
+ if (/preflight-ignore/.test(lineBefore)) {
176
+ continue;
177
+ }
178
+ issues.push({
179
+ file: relativePath,
180
+ line: lineNum,
181
+ setter,
182
+ context: getCodeContext(content, position),
183
+ });
184
+ }
185
+ }
186
+ }
187
+ // Summary
188
+ console.log(`\n${console_chars_1.emoji.chart} Summary:`);
189
+ console.log(` Files scanned: ${uniqueFiles.length}`);
190
+ console.log(` Potential issues found: ${issues.length}`);
191
+ if (issues.length === 0) {
192
+ console.log(`\n${console_chars_1.emoji.success} STATE UPDATE DURING RENDER CHECK PASSED`);
193
+ return { success: true, errors: 0, warnings: 0 };
194
+ }
195
+ console.log(`\n${console_chars_1.emoji.error} Potential state updates during render:`);
196
+ for (const issue of issues) {
197
+ console.log(`\n ${issue.file}:${issue.line}`);
198
+ console.log(` Setter: ${issue.setter}`);
199
+ console.log(` ${"-".repeat(50)}`);
200
+ const indentedCode = issue.context
201
+ .split("\n")
202
+ .map(line => ` ${line}`)
203
+ .join("\n");
204
+ console.log(indentedCode);
205
+ }
206
+ console.log(`\n${console_chars_1.emoji.info} To fix state updates during render:`);
207
+ console.log(` 1. Move the setState call to a useEffect hook`);
208
+ console.log(` 2. Make sure the effect has proper dependencies`);
209
+ console.log(` 3. Example:`);
210
+ console.log(` useEffect(() => {`);
211
+ console.log(` if (condition) {`);
212
+ console.log(` setState(newValue);`);
213
+ console.log(` }`);
214
+ console.log(` }, [condition]);`);
215
+ console.log(`\n${console_chars_1.emoji.error} STATE UPDATE DURING RENDER CHECK FAILED`);
216
+ return { success: false, errors: issues.length, warnings: 0 };
217
+ }
218
+ // Allow direct execution
219
+ if (require.main === module) {
220
+ run()
221
+ .then((result) => {
222
+ process.exit(result.success ? 0 : 1);
223
+ })
224
+ .catch((err) => {
225
+ console.error(`${console_chars_1.emoji.error} Preflight failed:`, err);
226
+ process.exit(1);
227
+ });
228
+ }
229
+ //# sourceMappingURL=state-update-during-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-update-during-render.js","sourceRoot":"","sources":["../../../src/checks/react/state-update-during-render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ export declare const id = "ui/empty-wrapper-spacing";
3
+ export declare const name = "Empty Wrapper Spacing Detection";
4
+ export declare const description = "Detects potential spacing issues from conditionally-rendered wrapper elements";
5
+ export declare const category = "ui";
6
+ export declare const blocking = false;
7
+ export declare const tags: string[];
8
+ export declare function run(): Promise<{
9
+ success: boolean;
10
+ errors: number;
11
+ warnings: number;
12
+ }>;
13
+ //# sourceMappingURL=empty-wrapper-spacing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-wrapper-spacing.d.ts","sourceRoot":"","sources":["../../../src/checks/ui/empty-wrapper-spacing.ts"],"names":[],"mappings":";AAwCA,eAAO,MAAM,EAAE,6BAA6B,CAAC;AAC7C,eAAO,MAAM,IAAI,oCAAoC,CAAC;AACtD,eAAO,MAAM,WAAW,kFAAkF,CAAC;AAC3G,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,IAAI,UAAyD,CAAC;AAqI3E,wBAAsB,GAAG,IAAI,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA0I3F"}
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.tags = exports.blocking = exports.category = exports.description = exports.name = exports.id = void 0;
8
+ exports.run = run;
9
+ /**
10
+ * Empty Wrapper Spacing Preflight
11
+ *
12
+ * Detects potential spacing issues caused by conditionally-rendered wrapper
13
+ * elements inside space-y-* or gap-* containers. When a wrapper element
14
+ * renders with no visible children, it still takes up space due to the
15
+ * parent's spacing utilities.
16
+ *
17
+ * Common problematic pattern:
18
+ * ```tsx
19
+ * <div className="space-y-6">
20
+ * <Box className="..."> // Always rendered
21
+ * {condition && <Button />} // Conditionally rendered
22
+ * </Box> // When condition is false, Box is empty but takes up space
23
+ * <Card>...</Card>
24
+ * </div>
25
+ * ```
26
+ *
27
+ * Better pattern:
28
+ * ```tsx
29
+ * <div className="space-y-6">
30
+ * {condition && (
31
+ * <Box className="...">
32
+ * <Button />
33
+ * </Box>
34
+ * )}
35
+ * <Card>...</Card>
36
+ * </div>
37
+ * ```
38
+ *
39
+ * This check flags potential issues for manual review.
40
+ */
41
+ const fs_1 = __importDefault(require("fs"));
42
+ const path_1 = __importDefault(require("path"));
43
+ const glob_1 = require("glob");
44
+ const console_chars_1 = require("../../utils/console-chars");
45
+ // METADATA - Required for plugin loader discovery
46
+ exports.id = "ui/empty-wrapper-spacing";
47
+ exports.name = "Empty Wrapper Spacing Detection";
48
+ exports.description = "Detects potential spacing issues from conditionally-rendered wrapper elements";
49
+ exports.category = "ui";
50
+ exports.blocking = false; // Warning level - requires manual review
51
+ exports.tags = ["ui", "spacing", "tailwind", "conditional-rendering"];
52
+ /**
53
+ * File patterns to check
54
+ */
55
+ const FILE_PATTERNS = [
56
+ "app/**/*.tsx",
57
+ "components/**/*.tsx",
58
+ ];
59
+ /**
60
+ * Files/directories to exclude
61
+ */
62
+ const EXCLUDE_PATTERNS = [
63
+ "node_modules/**",
64
+ "**/*.test.tsx",
65
+ "**/*.spec.tsx",
66
+ "**/*.stories.tsx",
67
+ ".storybook/**",
68
+ ];
69
+ /**
70
+ * Spacing container patterns (Tailwind utilities that create gaps between children)
71
+ */
72
+ const SPACING_CONTAINER_PATTERNS = [
73
+ /className="[^"]*space-y-[1-9][0-6]?[^"]*"/,
74
+ /className="[^"]*space-x-[1-9][0-6]?[^"]*"/,
75
+ /className="[^"]*gap-[1-9][0-6]?[^"]*"/,
76
+ /className=\{[^}]*space-y-[1-9][0-6]?[^}]*\}/,
77
+ /className=\{[^}]*space-x-[1-9][0-6]?[^}]*\}/,
78
+ /className=\{[^}]*gap-[1-9][0-6]?[^}]*\}/,
79
+ ];
80
+ /**
81
+ * Patterns for wrapper elements that might render empty
82
+ * These are elements with conditional children that don't have the condition at the wrapper level
83
+ *
84
+ * We specifically look for:
85
+ * - Simple conditional: {condition && <Element />}
86
+ *
87
+ * We exclude:
88
+ * - .map() calls (will produce content if array has items)
89
+ * - Ternary expressions (usually have content in both branches)
90
+ */
91
+ const EMPTY_WRAPPER_PATTERNS = [
92
+ // Box/div with conditional children (NOT .map or ternary)
93
+ // Must be followed by && but NOT by .map( or ?
94
+ /<(?:Box|div)\s+className="[^"]*">\s*\{[^}]+\s*&&\s*[^?]/,
95
+ // Stack with conditional children
96
+ /<Stack[^>]*>\s*\{[^}]+\s*&&\s*[^?]/,
97
+ ];
98
+ /**
99
+ * Patterns that indicate this is NOT an empty wrapper issue
100
+ */
101
+ const FALSE_POSITIVE_PATTERNS = [
102
+ // .map() calls will produce content when array has items
103
+ /\.map\s*\(/,
104
+ // Ternary expressions usually have content in both branches
105
+ /\?\s*\(/,
106
+ /\?\s*</,
107
+ // Object.entries/keys/values mapping
108
+ /Object\.(?:entries|keys|values)\s*\(/,
109
+ // Array spread or filter followed by map
110
+ /\)\s*\.map\s*\(/,
111
+ // Fixed dimensions - won't collapse when empty
112
+ /className="[^"]*(?:w-\d+|h-\d+)[^"]*"/,
113
+ // Absolutely positioned - not in document flow
114
+ /className="[^"]*absolute[^"]*"/,
115
+ // Has a Switch, Button, or other always-rendered element nearby
116
+ /<Switch\s/,
117
+ /<Button\s[^>]*>/,
118
+ // TableCell context - spacing typically not an issue
119
+ /<TableCell/,
120
+ // Tab panel content - one tab always shows
121
+ /tabValue\s*===\s*\d+\s*&&/,
122
+ // Has a Tooltip that always renders
123
+ /<Tooltip\s/,
124
+ // UI library components (forwardRef) - flexible by design
125
+ /forwardRef</,
126
+ // Has {children} which receives content from parent
127
+ /\{children\}/,
128
+ // Flex-1 - takes remaining space, not causing spacing issues
129
+ /className="[^"]*flex-1[^"]*"/,
130
+ // Non-conditional sibling after conditional (closing )} followed by element)
131
+ /\)\}\s*\n\s*<[A-Z]/,
132
+ // Card always follows (common pattern)
133
+ /<Card[\s>]/,
134
+ // Alert followed by other content
135
+ /<Alert[\s\S]*?<Card/,
136
+ // LoadingButton followed by Button (common pattern in actions)
137
+ /<LoadingButton[\s\S]*?<Button\s/,
138
+ // Early return pattern - component returns null when empty
139
+ /if\s*\([^)]*===\s*0[^)]*\)\s*\{\s*return\s+null/,
140
+ // Early return with multiple conditions
141
+ /return\s+null;\s*\}\s*\n\s*return\s*\(/,
142
+ ];
143
+ /**
144
+ * Extract line number from content and position
145
+ */
146
+ function getLineNumber(content, position) {
147
+ return content.substring(0, position).split("\n").length;
148
+ }
149
+ /**
150
+ * Get surrounding code context
151
+ */
152
+ function getCodeContext(content, position, lines = 3) {
153
+ const allLines = content.split("\n");
154
+ const lineNum = getLineNumber(content, position);
155
+ const start = Math.max(0, lineNum - 2);
156
+ const end = Math.min(allLines.length, lineNum + lines);
157
+ return allLines.slice(start, end).join("\n").trim();
158
+ }
159
+ /**
160
+ * Check if position is inside a conditionally-rendered block
161
+ */
162
+ function isInsideConditionalWrapper(content, position) {
163
+ // Look backwards for pattern like { condition && (
164
+ const before = content.substring(Math.max(0, position - 200), position);
165
+ return /\{[^{}]+&&\s*\(\s*$/.test(before);
166
+ }
167
+ async function run() {
168
+ console.log(`\n${console_chars_1.emoji.ruler} EMPTY WRAPPER SPACING DETECTION`);
169
+ console.log((0, console_chars_1.createDivider)(65, "heavy"));
170
+ const issues = [];
171
+ // Find all TSX files
172
+ const allFiles = [];
173
+ for (const pattern of FILE_PATTERNS) {
174
+ const matches = await (0, glob_1.glob)(pattern, {
175
+ cwd: process.cwd(),
176
+ ignore: EXCLUDE_PATTERNS,
177
+ });
178
+ allFiles.push(...matches);
179
+ }
180
+ const uniqueFiles = [...new Set(allFiles)];
181
+ console.log(`\n${console_chars_1.emoji.search} Scanning ${uniqueFiles.length} TSX files...`);
182
+ let filesWithSpacingContainers = 0;
183
+ let potentialIssuesFound = 0;
184
+ for (const relativePath of uniqueFiles) {
185
+ const filePath = path_1.default.join(process.cwd(), relativePath);
186
+ if (!fs_1.default.existsSync(filePath))
187
+ continue;
188
+ const content = fs_1.default.readFileSync(filePath, "utf-8");
189
+ // Check if file has spacing containers
190
+ const hasSpacingContainer = SPACING_CONTAINER_PATTERNS.some((pattern) => pattern.test(content));
191
+ if (!hasSpacingContainer)
192
+ continue;
193
+ filesWithSpacingContainers++;
194
+ // Find potential empty wrapper issues
195
+ for (const pattern of EMPTY_WRAPPER_PATTERNS) {
196
+ let match;
197
+ const regex = new RegExp(pattern.source, "g");
198
+ while ((match = regex.exec(content)) !== null) {
199
+ const position = match.index;
200
+ const lineNum = getLineNumber(content, position);
201
+ // Get context around the match for false positive detection
202
+ const contextAfter = content.substring(position, Math.min(content.length, position + 500));
203
+ // Check for false positive patterns (map, ternary, etc.)
204
+ const isFalsePositive = FALSE_POSITIVE_PATTERNS.some((fp) => fp.test(contextAfter));
205
+ if (isFalsePositive) {
206
+ continue;
207
+ }
208
+ // Check if this is already inside a conditional wrapper (good pattern)
209
+ if (isInsideConditionalWrapper(content, position)) {
210
+ continue;
211
+ }
212
+ // Check if there's a preflight-ignore comment nearby (JS or JSX style)
213
+ const nearbyContent = content.substring(Math.max(0, position - 200), position);
214
+ if (/preflight-ignore/.test(nearbyContent)) {
215
+ continue;
216
+ }
217
+ // Also check for JSX comments on the line with the element
218
+ const lineStart = content.lastIndexOf('\n', position) + 1;
219
+ const lineContent = content.substring(lineStart, position + 200);
220
+ if (/\{\/\*\s*preflight-ignore/.test(lineContent) || /\/\/\s*preflight-ignore/.test(lineContent)) {
221
+ continue;
222
+ }
223
+ // Check if the wrapper itself is conditionally rendered
224
+ const before50 = content.substring(Math.max(0, position - 50), position);
225
+ if (/\{\s*\w+\s*&&\s*\(?\s*$/.test(before50)) {
226
+ continue; // Good - wrapper is conditional
227
+ }
228
+ // This is a potential issue
229
+ potentialIssuesFound++;
230
+ issues.push({
231
+ file: relativePath,
232
+ line: lineNum,
233
+ description: "Wrapper element with only conditional children may cause empty spacing",
234
+ code: getCodeContext(content, position),
235
+ severity: "warning",
236
+ });
237
+ }
238
+ }
239
+ }
240
+ // Summary
241
+ console.log(`\n${console_chars_1.emoji.chart} Summary:`);
242
+ console.log(` Files with spacing containers: ${filesWithSpacingContainers}`);
243
+ console.log(` Potential issues found: ${potentialIssuesFound}`);
244
+ if (issues.length === 0) {
245
+ console.log(`\n${console_chars_1.emoji.success} EMPTY WRAPPER SPACING VALIDATION PASSED`);
246
+ console.log(`\nNo potential empty wrapper spacing issues detected.`);
247
+ return { success: true, errors: 0, warnings: 0 };
248
+ }
249
+ // Group issues by file
250
+ const issuesByFile = new Map();
251
+ for (const issue of issues) {
252
+ const fileIssues = issuesByFile.get(issue.file) || [];
253
+ fileIssues.push(issue);
254
+ issuesByFile.set(issue.file, fileIssues);
255
+ }
256
+ console.log(`\n${console_chars_1.emoji.warning} Potential issues found:`);
257
+ for (const [file, fileIssues] of issuesByFile) {
258
+ console.log(`\n ${file}:`);
259
+ for (const issue of fileIssues) {
260
+ console.log(` Line ${issue.line}: ${issue.description}`);
261
+ console.log(` ${"-".repeat(50)}`);
262
+ const indentedCode = issue.code
263
+ .split("\n")
264
+ .map((line) => ` ${line}`)
265
+ .join("\n");
266
+ console.log(indentedCode);
267
+ }
268
+ }
269
+ console.log(`\n${console_chars_1.emoji.info} To fix empty wrapper spacing issues:`);
270
+ console.log(` 1. Move the condition to wrap the entire element:`);
271
+ console.log(` BAD: <Box className="...">\\{condition && <Button />}\\</Box>`);
272
+ console.log(` GOOD: {condition && <Box className="..."><Button /></Box>}`);
273
+ console.log(` 2. Or use a smaller spacing value (e.g., space-y-3 instead of space-y-6)`);
274
+ console.log(` 3. Or add a /* preflight-ignore */ comment if the spacing is intentional`);
275
+ console.log(`\n${console_chars_1.emoji.warning} EMPTY WRAPPER SPACING VALIDATION COMPLETED WITH WARNINGS`);
276
+ return { success: true, errors: 0, warnings: issues.length }; // Non-blocking - requires manual review
277
+ }
278
+ // Allow direct execution
279
+ if (require.main === module) {
280
+ run()
281
+ .then((result) => {
282
+ process.exit(result.success ? 0 : 1);
283
+ })
284
+ .catch((err) => {
285
+ console.error(`${console_chars_1.emoji.error} Preflight failed:`, err);
286
+ process.exit(1);
287
+ });
288
+ }
289
+ //# sourceMappingURL=empty-wrapper-spacing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-wrapper-spacing.js","sourceRoot":"","sources":["../../../src/checks/ui/empty-wrapper-spacing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Select Option Styling Preflight
4
+ *
5
+ * Detects native <option> elements inside Select components which break
6
+ * the shared component styling. MUI/custom Select components should use
7
+ * MenuItem instead of native option elements for consistent styling.
8
+ *
9
+ * @blocking false - Advisory check for UI consistency
10
+ */
11
+ export declare const id = "ui/select-option-styling";
12
+ export declare const name = "Select Option Styling";
13
+ export declare const category = "ui";
14
+ export declare const blocking = false;
15
+ export declare const description = "Detects native <option> elements inside Select components that should use MenuItem for proper styling";
16
+ export declare const tags: string[];
17
+ //# sourceMappingURL=select-option-styling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select-option-styling.d.ts","sourceRoot":"","sources":["../../../src/checks/ui/select-option-styling.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAQH,eAAO,MAAM,EAAE,6BAA6B,CAAC;AAC7C,eAAO,MAAM,IAAI,0BAA0B,CAAC;AAC5C,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,WAAW,0GACiF,CAAC;AAC1G,eAAO,MAAM,IAAI,UAAsC,CAAC"}
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env tsx
2
+ "use strict";
3
+ /**
4
+ * Select Option Styling Preflight
5
+ *
6
+ * Detects native <option> elements inside Select components which break
7
+ * the shared component styling. MUI/custom Select components should use
8
+ * MenuItem instead of native option elements for consistent styling.
9
+ *
10
+ * @blocking false - Advisory check for UI consistency
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.tags = exports.description = exports.blocking = exports.category = exports.name = exports.id = void 0;
47
+ const fs = __importStar(require("fs"));
48
+ const glob_1 = require("glob");
49
+ const path = __importStar(require("path"));
50
+ const console_chars_1 = require("../../utils/console-chars");
51
+ // Check metadata
52
+ exports.id = "ui/select-option-styling";
53
+ exports.name = "Select Option Styling";
54
+ exports.category = "ui";
55
+ exports.blocking = false;
56
+ exports.description = "Detects native <option> elements inside Select components that should use MenuItem for proper styling";
57
+ exports.tags = ["ui", "consistency", "components"];
58
+ const ROOT_DIR = process.cwd();
59
+ function getFiles(patterns) {
60
+ const allFiles = [];
61
+ for (const pattern of patterns) {
62
+ const matches = glob_1.glob.sync(pattern, {
63
+ cwd: ROOT_DIR,
64
+ absolute: true,
65
+ ignore: ["**/node_modules/**", "**/.next/**", "**/dist/**"],
66
+ });
67
+ allFiles.push(...matches);
68
+ }
69
+ return [...new Set(allFiles)];
70
+ }
71
+ function readFile(filePath) {
72
+ try {
73
+ return fs.readFileSync(filePath, "utf-8");
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
79
+ async function main() {
80
+ console.log(`\n${console_chars_1.emoji.palette} Select Option Styling Check\n`);
81
+ const issues = [];
82
+ // Find all TSX files that might have Select components
83
+ const files = getFiles(["components/**/*.tsx", "app/**/*.tsx"]);
84
+ // Pattern to detect <option> within context of Select usage
85
+ // We look for files that:
86
+ // 1. Import Select from @/components/ui
87
+ // 2. Contain <option> elements
88
+ for (const filePath of files) {
89
+ const content = readFile(filePath);
90
+ if (!content)
91
+ continue;
92
+ const relativePath = path.relative(ROOT_DIR, filePath);
93
+ // Check if file imports Select from @/components/ui
94
+ const hasSelectImport = content.includes("Select") &&
95
+ (content.includes("@/components/ui") || content.includes("components/ui"));
96
+ if (!hasSelectImport)
97
+ continue;
98
+ // Check for native <option> elements
99
+ const lines = content.split("\n");
100
+ for (let i = 0; i < lines.length; i++) {
101
+ const line = lines[i];
102
+ if (line && /<option\s+value=/.test(line)) {
103
+ issues.push({
104
+ file: relativePath,
105
+ line: i + 1,
106
+ content: line.trim().substring(0, 80),
107
+ });
108
+ }
109
+ }
110
+ }
111
+ // Report findings
112
+ if (issues.length === 0) {
113
+ console.log(`${console_chars_1.emoji.success} No native <option> elements found in Select components!\n`);
114
+ console.log("All dropdowns are using MenuItem for consistent shared styling.\n");
115
+ process.exit(0);
116
+ }
117
+ console.log(`${console_chars_1.emoji.warning} Found ${issues.length} native <option> elements that should use MenuItem\n`);
118
+ console.log("Native <option> elements don't receive shared component styling.");
119
+ console.log("Replace with MenuItem from @/components/ui for consistent UI.\n");
120
+ console.log("─".repeat(70));
121
+ console.log("");
122
+ // Group by file
123
+ const byFile = issues.reduce((acc, issue) => {
124
+ acc[issue.file] ||= [];
125
+ acc[issue.file].push(issue);
126
+ return acc;
127
+ }, {});
128
+ for (const [file, fileIssues] of Object.entries(byFile)) {
129
+ console.log(`${console_chars_1.emoji.file} ${file}`);
130
+ for (const issue of fileIssues) {
131
+ console.log(` Line ${issue.line}: ${issue.content}`);
132
+ }
133
+ console.log("");
134
+ }
135
+ console.log("─".repeat(70));
136
+ console.log("");
137
+ console.log(`${console_chars_1.emoji.info} Fix: Replace <option value="...">...</option>`);
138
+ console.log(` With: <MenuItem value="...">...</MenuItem>`);
139
+ console.log("");
140
+ console.log(` Import: import { MenuItem } from "@/components/ui";`);
141
+ console.log("");
142
+ // Non-blocking, just advisory
143
+ process.exit(0);
144
+ }
145
+ main().catch((err) => {
146
+ console.error(`${console_chars_1.emoji.error} Select option styling check crashed:`, err);
147
+ process.exit(1);
148
+ });
149
+ //# sourceMappingURL=select-option-styling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select-option-styling.js","sourceRoot":"","sources":["../../../src/checks/ui/select-option-styling.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empline/preflight",
3
- "version": "1.1.28",
3
+ "version": "1.1.30",
4
4
  "description": "Distributable preflight validation system with app-specific plugin support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",