@empline/preflight 1.1.14 → 1.1.15
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/checks/auth/session-provider-wrapper.d.ts +47 -0
- package/dist/checks/auth/session-provider-wrapper.d.ts.map +1 -0
- package/dist/checks/auth/session-provider-wrapper.js +286 -0
- package/dist/checks/auth/session-provider-wrapper.js.map +1 -0
- package/dist/checks/database/prisma-upsert-safety.d.ts +39 -0
- package/dist/checks/database/prisma-upsert-safety.d.ts.map +1 -0
- package/dist/checks/database/prisma-upsert-safety.js +220 -0
- package/dist/checks/database/prisma-upsert-safety.js.map +1 -0
- package/dist/checks/dependencies/dependency-health-monitor.d.ts +49 -0
- package/dist/checks/dependencies/dependency-health-monitor.d.ts.map +1 -0
- package/dist/checks/dependencies/dependency-health-monitor.js +323 -0
- package/dist/checks/dependencies/dependency-health-monitor.js.map +1 -0
- package/dist/checks/file-hygiene-validation.d.ts +31 -0
- package/dist/checks/file-hygiene-validation.d.ts.map +1 -0
- package/dist/checks/file-hygiene-validation.js +934 -0
- package/dist/checks/file-hygiene-validation.js.map +1 -0
- package/dist/checks/organization/file-cleanup-validation.d.ts +22 -0
- package/dist/checks/organization/file-cleanup-validation.d.ts.map +1 -0
- package/dist/checks/organization/file-cleanup-validation.js +1121 -0
- package/dist/checks/organization/file-cleanup-validation.js.map +1 -0
- package/dist/checks/runtime/tailwind-runtime-check.d.ts +36 -0
- package/dist/checks/runtime/tailwind-runtime-check.d.ts.map +1 -0
- package/dist/checks/runtime/tailwind-runtime-check.js +264 -0
- package/dist/checks/runtime/tailwind-runtime-check.js.map +1 -0
- package/dist/checks/shipping-integration-validation.d.ts +28 -0
- package/dist/checks/shipping-integration-validation.d.ts.map +1 -0
- package/dist/checks/shipping-integration-validation.js +409 -0
- package/dist/checks/shipping-integration-validation.js.map +1 -0
- package/dist/checks/system/layout-constants-sync.d.ts +36 -0
- package/dist/checks/system/layout-constants-sync.d.ts.map +1 -0
- package/dist/checks/system/layout-constants-sync.js +642 -0
- package/dist/checks/system/layout-constants-sync.js.map +1 -0
- package/dist/checks/system/preflight-circular-dependency-detector.d.ts +26 -0
- package/dist/checks/system/preflight-circular-dependency-detector.d.ts.map +1 -0
- package/dist/checks/system/preflight-circular-dependency-detector.js +310 -0
- package/dist/checks/system/preflight-circular-dependency-detector.js.map +1 -0
- package/dist/checks/system/preflight-execution-benchmarks.d.ts +24 -0
- package/dist/checks/system/preflight-execution-benchmarks.d.ts.map +1 -0
- package/dist/checks/system/preflight-execution-benchmarks.js +282 -0
- package/dist/checks/system/preflight-execution-benchmarks.js.map +1 -0
- package/dist/checks/system/preflight-tag-taxonomy-validator.d.ts +27 -0
- package/dist/checks/system/preflight-tag-taxonomy-validator.d.ts.map +1 -0
- package/dist/checks/system/preflight-tag-taxonomy-validator.js +361 -0
- package/dist/checks/system/preflight-tag-taxonomy-validator.js.map +1 -0
- package/dist/utils/console-chars.d.ts +16 -0
- package/dist/utils/console-chars.d.ts.map +1 -1
- package/dist/utils/console-chars.js +10 -0
- package/dist/utils/console-chars.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Layout Constants Sync Preflight - IRON-CLAD ENFORCEMENT
|
|
5
|
+
*
|
|
6
|
+
* Ensures consistency between:
|
|
7
|
+
* 1. Shared layout constants (single source of truth)
|
|
8
|
+
* 2. Actual layout component implementations
|
|
9
|
+
* 3. Preflights that reference layout components
|
|
10
|
+
* 4. Identifies opportunities for consolidation (shown as warnings)
|
|
11
|
+
*
|
|
12
|
+
* This prevents drift where constants say one thing but implementations do another.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* pnpm preflight:layout-constants-sync # All checks including opportunities
|
|
16
|
+
* pnpm preflight:layout-constants-sync --verbose # Show all details
|
|
17
|
+
*
|
|
18
|
+
* Checks performed:
|
|
19
|
+
* 1. Layout components implement expected gap values (BLOCKING)
|
|
20
|
+
* 2. Preflights import from shared constants (not hardcoded)
|
|
21
|
+
* 3. All registered components exist (BLOCKING)
|
|
22
|
+
* 4. Unregistered gap layouts are flagged
|
|
23
|
+
* 5. Hardcoded spacing that could use constants (WARNING)
|
|
24
|
+
* 6. Components that could benefit from shared constants (WARNING)
|
|
25
|
+
*/
|
|
26
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
29
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
30
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
31
|
+
}
|
|
32
|
+
Object.defineProperty(o, k2, desc);
|
|
33
|
+
}) : (function(o, m, k, k2) {
|
|
34
|
+
if (k2 === undefined) k2 = k;
|
|
35
|
+
o[k2] = m[k];
|
|
36
|
+
}));
|
|
37
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
38
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
39
|
+
}) : function(o, v) {
|
|
40
|
+
o["default"] = v;
|
|
41
|
+
});
|
|
42
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
43
|
+
var ownKeys = function(o) {
|
|
44
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
45
|
+
var ar = [];
|
|
46
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
47
|
+
return ar;
|
|
48
|
+
};
|
|
49
|
+
return ownKeys(o);
|
|
50
|
+
};
|
|
51
|
+
return function (mod) {
|
|
52
|
+
if (mod && mod.__esModule) return mod;
|
|
53
|
+
var result = {};
|
|
54
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
55
|
+
__setModuleDefault(result, mod);
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
})();
|
|
59
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
60
|
+
exports.requires = exports.tags = exports.description = exports.blocking = exports.category = exports.name = exports.id = void 0;
|
|
61
|
+
exports.run = run;
|
|
62
|
+
const fs = __importStar(require("fs"));
|
|
63
|
+
const console_chars_1 = require("../../utils/console-chars");
|
|
64
|
+
const file_cache_1 = require("../../shared/file-cache");
|
|
65
|
+
const glob_patterns_1 = require("../../shared/glob-patterns");
|
|
66
|
+
const universal_progress_reporter_1 = require("./universal-progress-reporter");
|
|
67
|
+
// Check metadata
|
|
68
|
+
exports.id = "system/layout-constants-sync";
|
|
69
|
+
exports.name = "Layout Constants Sync";
|
|
70
|
+
exports.category = "system";
|
|
71
|
+
exports.blocking = true;
|
|
72
|
+
exports.description = "Layout Constants Sync Preflight - IRON-CLAD ENFORCEMENT";
|
|
73
|
+
exports.tags = ["system"];
|
|
74
|
+
exports.requires = ["trading-card-system"];
|
|
75
|
+
// Dynamic imports for project-specific modules
|
|
76
|
+
let GAP_LAYOUT_COMPONENTS = [];
|
|
77
|
+
let LAYOUT_GAP_SPACING = 6;
|
|
78
|
+
let LAYOUT_PADDING_TOP_SPACING = 4;
|
|
79
|
+
let TOAST_CONTAINER_GAP_SPACING = 3;
|
|
80
|
+
async function loadProjectDependencies() {
|
|
81
|
+
try {
|
|
82
|
+
const layoutConstantsPath = "../../shared/layout-constants";
|
|
83
|
+
const mod = await Promise.resolve(`${layoutConstantsPath}`).then(s => __importStar(require(s)));
|
|
84
|
+
GAP_LAYOUT_COMPONENTS = mod.GAP_LAYOUT_COMPONENTS;
|
|
85
|
+
LAYOUT_GAP_SPACING = mod.LAYOUT_GAP_SPACING;
|
|
86
|
+
LAYOUT_PADDING_TOP_SPACING = mod.LAYOUT_PADDING_TOP_SPACING;
|
|
87
|
+
TOAST_CONTAINER_GAP_SPACING = mod.TOAST_CONTAINER_GAP_SPACING;
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// CACHED FILE LISTS - Scan once, use everywhere
|
|
95
|
+
let _cachedPreflightFiles = null;
|
|
96
|
+
async function getPreflightFiles() {
|
|
97
|
+
if (!_cachedPreflightFiles) {
|
|
98
|
+
_cachedPreflightFiles = await file_cache_1.fileCache.getFiles("scripts/active/preflights/**/*.ts", {
|
|
99
|
+
ignore: glob_patterns_1.STANDARD_EXCLUDES,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return _cachedPreflightFiles;
|
|
103
|
+
}
|
|
104
|
+
let _cachedAppComponentsTsxFiles = null;
|
|
105
|
+
async function getAppComponentsTsxFiles() {
|
|
106
|
+
if (!_cachedAppComponentsTsxFiles) {
|
|
107
|
+
_cachedAppComponentsTsxFiles = await file_cache_1.fileCache.getAppAndComponentsTSX();
|
|
108
|
+
}
|
|
109
|
+
return _cachedAppComponentsTsxFiles;
|
|
110
|
+
}
|
|
111
|
+
let _cachedLayoutFiles = null;
|
|
112
|
+
async function getLayoutComponentFiles() {
|
|
113
|
+
if (!_cachedLayoutFiles) {
|
|
114
|
+
_cachedLayoutFiles = await file_cache_1.fileCache.getFiles("components/**/*Layout*.tsx", {
|
|
115
|
+
ignore: glob_patterns_1.STANDARD_EXCLUDES,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return _cachedLayoutFiles;
|
|
119
|
+
}
|
|
120
|
+
const EXCLUDED = [...glob_patterns_1.STANDARD_EXCLUDES];
|
|
121
|
+
/**
|
|
122
|
+
* Check 1: Verify layout components actually use the expected gap value
|
|
123
|
+
*
|
|
124
|
+
* Note: Some layout components are thin wrappers around DashboardPageLayout.
|
|
125
|
+
* These wrappers don't define gap themselves - they delegate to DashboardPageLayout.
|
|
126
|
+
* We only check components that actually define their own gap.
|
|
127
|
+
*/
|
|
128
|
+
async function checkLayoutComponentsImplementGap() {
|
|
129
|
+
const issues = [];
|
|
130
|
+
// Map component names to their file paths
|
|
131
|
+
const componentFiles = {
|
|
132
|
+
DashboardPageLayout: "components/shared/DashboardPageLayout.tsx",
|
|
133
|
+
AdminPageLayout: "components/shared/AdminPageLayout.tsx",
|
|
134
|
+
SellerPageLayout: "components/shared/SellerPageLayout.tsx",
|
|
135
|
+
AccountPageLayout: "components/shared/AccountPageLayout.tsx",
|
|
136
|
+
};
|
|
137
|
+
// Components that are thin wrappers - they use DashboardPageLayout internally
|
|
138
|
+
// These don't need their own gap definition
|
|
139
|
+
const wrapperComponents = new Set([
|
|
140
|
+
"AdminPageLayout",
|
|
141
|
+
"SellerPageLayout",
|
|
142
|
+
"AccountPageLayout",
|
|
143
|
+
"TemplatesLayout",
|
|
144
|
+
]);
|
|
145
|
+
for (const component of GAP_LAYOUT_COMPONENTS) {
|
|
146
|
+
const filePath = componentFiles[component];
|
|
147
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
148
|
+
// Skip wrapper components like TemplatesLayout - they use the base layouts
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
152
|
+
// Check if this is a wrapper that delegates to DashboardPageLayout
|
|
153
|
+
const isDelegatingWrapper = wrapperComponents.has(component) && content.includes("DashboardPageLayout");
|
|
154
|
+
if (isDelegatingWrapper) {
|
|
155
|
+
// Verify it actually uses DashboardPageLayout
|
|
156
|
+
if (!/<DashboardPageLayout/.test(content)) {
|
|
157
|
+
issues.push({
|
|
158
|
+
file: filePath,
|
|
159
|
+
type: "wrapper-not-delegating",
|
|
160
|
+
severity: "error",
|
|
161
|
+
message: `${component} is expected to wrap DashboardPageLayout but doesn't`,
|
|
162
|
+
suggestion: `Use <DashboardPageLayout variant="..."> inside ${component}`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// Skip gap/padding checks for wrappers - DashboardPageLayout handles it
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// Check for gap: var(--spacing-X) where X should match LAYOUT_GAP_SPACING
|
|
169
|
+
const gapMatches = content.matchAll(/gap:\s*["']var\(--spacing-(\d+)\)["']/g);
|
|
170
|
+
let foundCorrectGap = false;
|
|
171
|
+
for (const match of gapMatches) {
|
|
172
|
+
const gapValue = parseInt(match[1]);
|
|
173
|
+
if (gapValue === LAYOUT_GAP_SPACING) {
|
|
174
|
+
foundCorrectGap = true;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const lineNumber = content.substring(0, match.index).split("\n").length;
|
|
178
|
+
issues.push({
|
|
179
|
+
file: filePath,
|
|
180
|
+
line: lineNumber,
|
|
181
|
+
type: "layout-wrong-gap",
|
|
182
|
+
severity: "error",
|
|
183
|
+
message: `${component} uses gap: spacing-${gapValue} but shared constant expects spacing-${LAYOUT_GAP_SPACING}`,
|
|
184
|
+
suggestion: `Change to gap: "var(--spacing-${LAYOUT_GAP_SPACING})" or update LAYOUT_GAP_SPACING in layout-constants.ts`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Check for paddingTop: var(--spacing-X) where X should match LAYOUT_PADDING_TOP_SPACING
|
|
189
|
+
const paddingMatches = content.matchAll(/paddingTop:\s*["']var\(--spacing-(\d+)\)["']/g);
|
|
190
|
+
for (const match of paddingMatches) {
|
|
191
|
+
const paddingValue = parseInt(match[1]);
|
|
192
|
+
if (paddingValue !== LAYOUT_PADDING_TOP_SPACING) {
|
|
193
|
+
const lineNumber = content.substring(0, match.index).split("\n").length;
|
|
194
|
+
issues.push({
|
|
195
|
+
file: filePath,
|
|
196
|
+
line: lineNumber,
|
|
197
|
+
type: "layout-wrong-padding-top",
|
|
198
|
+
severity: "error",
|
|
199
|
+
message: `${component} uses paddingTop: spacing-${paddingValue} but shared constant expects spacing-${LAYOUT_PADDING_TOP_SPACING}`,
|
|
200
|
+
suggestion: `Change to paddingTop: "var(--spacing-${LAYOUT_PADDING_TOP_SPACING})" or update LAYOUT_PADDING_TOP_SPACING in layout-constants.ts`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Warn if no gap found at all (only for non-wrapper components)
|
|
205
|
+
if (!foundCorrectGap && !content.includes(`gap:`)) {
|
|
206
|
+
issues.push({
|
|
207
|
+
file: filePath,
|
|
208
|
+
type: "layout-missing-gap",
|
|
209
|
+
severity: "warning",
|
|
210
|
+
message: `${component} is in GAP_LAYOUT_COMPONENTS but doesn't appear to use gap spacing`,
|
|
211
|
+
suggestion: `Add gap: "var(--spacing-${LAYOUT_GAP_SPACING})" to the content area style`,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return issues;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Check 2: Verify preflights import from shared constants (not hardcoded)
|
|
219
|
+
*/
|
|
220
|
+
async function checkPreflightsUseSharedConstants() {
|
|
221
|
+
const issues = [];
|
|
222
|
+
// Find all preflight files that might reference layout components
|
|
223
|
+
const preflightFiles = await getPreflightFiles();
|
|
224
|
+
// Skip the shared constants file itself
|
|
225
|
+
const sharedConstantsPath = "scripts/active/preflights/shared/layout-constants.ts";
|
|
226
|
+
for (const file of preflightFiles) {
|
|
227
|
+
if (file.replace(/\\/g, "/").includes("shared/layout-constants"))
|
|
228
|
+
continue;
|
|
229
|
+
const content = fs.readFileSync(file, "utf8");
|
|
230
|
+
const lines = content.split("\n");
|
|
231
|
+
// Check for hardcoded layout component arrays
|
|
232
|
+
const hardcodedPatterns = [
|
|
233
|
+
/const\s+\w*[Ll]ayout[Cc]omponents?\w*\s*=\s*\[/,
|
|
234
|
+
/const\s+GAP_LAYOUTS\s*=\s*\[(?!\.\.\.GAP_LAYOUT_COMPONENTS)/,
|
|
235
|
+
/const\s+LAYOUT_COMPONENTS_WITH_GAP\s*=\s*\[(?!\.\.\.GAP_LAYOUT_COMPONENTS)/,
|
|
236
|
+
];
|
|
237
|
+
for (let i = 0; i < lines.length; i++) {
|
|
238
|
+
const line = lines[i];
|
|
239
|
+
for (const pattern of hardcodedPatterns) {
|
|
240
|
+
if (pattern.test(line)) {
|
|
241
|
+
// Check if this file imports from shared constants
|
|
242
|
+
const hasSharedImport = content.includes("from '../shared/layout-constants'") ||
|
|
243
|
+
content.includes("from './shared/layout-constants'") ||
|
|
244
|
+
content.includes("from '../../shared/layout-constants'");
|
|
245
|
+
if (!hasSharedImport) {
|
|
246
|
+
issues.push({
|
|
247
|
+
file,
|
|
248
|
+
line: i + 1,
|
|
249
|
+
type: "hardcoded-layout-list",
|
|
250
|
+
severity: "warning",
|
|
251
|
+
message: "Preflight has hardcoded layout component list instead of using shared constants",
|
|
252
|
+
suggestion: "Import GAP_LAYOUT_COMPONENTS from '../shared/layout-constants' and use it",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Check for hardcoded spacing values that should use constants
|
|
259
|
+
const spacingPatterns = [
|
|
260
|
+
{ pattern: /DASHBOARD_LAYOUT_PADDING_TOP:\s*(\d+)/, name: "DASHBOARD_LAYOUT_PADDING_TOP" },
|
|
261
|
+
{ pattern: /LAYOUT_GAP_SPACING\s*=\s*(\d+)/, name: "LAYOUT_GAP_SPACING" },
|
|
262
|
+
];
|
|
263
|
+
for (const { pattern, name } of spacingPatterns) {
|
|
264
|
+
const match = content.match(pattern);
|
|
265
|
+
if (match && !file.includes("layout-constants")) {
|
|
266
|
+
const hasSharedImport = content.includes("from '../shared/layout-constants'") ||
|
|
267
|
+
content.includes("from './shared/layout-constants'");
|
|
268
|
+
if (!hasSharedImport) {
|
|
269
|
+
issues.push({
|
|
270
|
+
file,
|
|
271
|
+
type: "hardcoded-spacing-value",
|
|
272
|
+
severity: "warning",
|
|
273
|
+
message: `Preflight defines ${name} locally instead of importing from shared constants`,
|
|
274
|
+
suggestion: `Import ${name} from layout-constants.ts`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return issues;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Check 3: Verify all layout components in GAP_LAYOUT_COMPONENTS actually exist
|
|
284
|
+
*/
|
|
285
|
+
async function checkLayoutComponentsExist() {
|
|
286
|
+
const issues = [];
|
|
287
|
+
// Find all component files
|
|
288
|
+
const componentFiles = await getAppComponentsTsxFiles();
|
|
289
|
+
const componentNames = new Set();
|
|
290
|
+
for (const file of componentFiles) {
|
|
291
|
+
const content = fs.readFileSync(file, "utf8");
|
|
292
|
+
// Extract exported component names
|
|
293
|
+
const exportMatches = content.matchAll(/export\s+(?:default\s+)?function\s+(\w+)/g);
|
|
294
|
+
for (const match of exportMatches) {
|
|
295
|
+
componentNames.add(match[1]);
|
|
296
|
+
}
|
|
297
|
+
// Also check for const exports
|
|
298
|
+
const constExportMatches = content.matchAll(/export\s+(?:const|let)\s+(\w+)/g);
|
|
299
|
+
for (const match of constExportMatches) {
|
|
300
|
+
componentNames.add(match[1]);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Check each component in GAP_LAYOUT_COMPONENTS exists
|
|
304
|
+
for (const component of GAP_LAYOUT_COMPONENTS) {
|
|
305
|
+
if (!componentNames.has(component)) {
|
|
306
|
+
issues.push({
|
|
307
|
+
file: "scripts/active/preflights/shared/layout-constants.ts",
|
|
308
|
+
type: "missing-layout-component",
|
|
309
|
+
severity: "error",
|
|
310
|
+
message: `GAP_LAYOUT_COMPONENTS includes "${component}" but this component doesn't exist`,
|
|
311
|
+
suggestion: `Remove "${component}" from GAP_LAYOUT_COMPONENTS or create the component`,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return issues;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Check 4: Find layout components that use gap but aren't in GAP_LAYOUT_COMPONENTS
|
|
319
|
+
*/
|
|
320
|
+
async function checkUnregisteredGapLayouts() {
|
|
321
|
+
const issues = [];
|
|
322
|
+
// Find layout component files
|
|
323
|
+
const layoutFiles = await getLayoutComponentFiles();
|
|
324
|
+
for (const file of layoutFiles) {
|
|
325
|
+
const content = fs.readFileSync(file, "utf8");
|
|
326
|
+
// Check if this file defines a gap-based layout
|
|
327
|
+
const hasGap = /gap:\s*["']var\(--spacing-\d+\)["']/.test(content);
|
|
328
|
+
const hasFlexColumn = /flexDirection:\s*["']column["']/.test(content) || /flex-col/.test(content);
|
|
329
|
+
if (hasGap && hasFlexColumn) {
|
|
330
|
+
// Extract component name from file
|
|
331
|
+
const componentMatch = content.match(/export\s+(?:default\s+)?function\s+(\w*Layout\w*)/);
|
|
332
|
+
if (componentMatch) {
|
|
333
|
+
const componentName = componentMatch[1];
|
|
334
|
+
// Check if it's in GAP_LAYOUT_COMPONENTS
|
|
335
|
+
if (!GAP_LAYOUT_COMPONENTS.includes(componentName)) {
|
|
336
|
+
issues.push({
|
|
337
|
+
file,
|
|
338
|
+
type: "unregistered-gap-layout",
|
|
339
|
+
severity: "warning",
|
|
340
|
+
message: `${componentName} uses gap spacing but isn't in GAP_LAYOUT_COMPONENTS`,
|
|
341
|
+
suggestion: `Add "${componentName}" to GAP_LAYOUT_COMPONENTS in layout-constants.ts if children shouldn't have margin`,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return issues;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Check 4b: Verify toast/notification containers have proper gap spacing
|
|
351
|
+
*
|
|
352
|
+
* Toast containers should use gap: var(--spacing-3) to ensure consistent
|
|
353
|
+
* spacing between stacked notifications in the upper right corner.
|
|
354
|
+
*/
|
|
355
|
+
async function checkToastContainerGaps() {
|
|
356
|
+
const issues = [];
|
|
357
|
+
// Files that contain toast container implementations
|
|
358
|
+
const toastContainerFiles = [
|
|
359
|
+
"components/shared/status/StatusProvider.tsx",
|
|
360
|
+
"components/ToastProvider.tsx",
|
|
361
|
+
"components/shared/notifications/Toast.tsx",
|
|
362
|
+
"components/shared/notifications/NotificationProvider.tsx",
|
|
363
|
+
"packages/ui/src/components/feedback/Toast.tsx",
|
|
364
|
+
];
|
|
365
|
+
for (const filePath of toastContainerFiles) {
|
|
366
|
+
if (!fs.existsSync(filePath))
|
|
367
|
+
continue;
|
|
368
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
369
|
+
const lines = content.split("\n");
|
|
370
|
+
// Check for flex-col containers that render multiple toasts/notifications
|
|
371
|
+
// These should have gap spacing
|
|
372
|
+
const flexColContainerPatterns = [
|
|
373
|
+
/flexDirection:\s*["']column["']/,
|
|
374
|
+
/flex-col/,
|
|
375
|
+
/flex\s+flex-col/,
|
|
376
|
+
];
|
|
377
|
+
const hasFlexColumn = flexColContainerPatterns.some((p) => p.test(content));
|
|
378
|
+
// Check if file renders multiple toast items (map over toasts/notifications)
|
|
379
|
+
const rendersMultipleToasts = /\.map\s*\(\s*\(?\s*(?:toast|notification|item)/i.test(content) ||
|
|
380
|
+
/toasts\.map|notifications\.map/i.test(content);
|
|
381
|
+
if (hasFlexColumn && rendersMultipleToasts) {
|
|
382
|
+
// Check for gap in the container
|
|
383
|
+
const hasGapStyle = /gap:\s*["']var\(--spacing-(\d+)\)["']/.test(content);
|
|
384
|
+
const hasGapClass = /gap-(\d+)/.test(content);
|
|
385
|
+
if (!hasGapStyle && !hasGapClass) {
|
|
386
|
+
issues.push({
|
|
387
|
+
file: filePath,
|
|
388
|
+
type: "toast-container-missing-gap",
|
|
389
|
+
severity: "error",
|
|
390
|
+
message: `Toast container missing gap spacing between stacked items`,
|
|
391
|
+
suggestion: `Add gap: "var(--spacing-${TOAST_CONTAINER_GAP_SPACING})" or className="gap-${TOAST_CONTAINER_GAP_SPACING}" to the flex-col container`,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
// Verify correct gap value
|
|
396
|
+
const styleGapMatch = content.match(/gap:\s*["']var\(--spacing-(\d+)\)["']/);
|
|
397
|
+
const classGapMatch = content.match(/gap-(\d+)/);
|
|
398
|
+
const gapValue = styleGapMatch
|
|
399
|
+
? parseInt(styleGapMatch[1])
|
|
400
|
+
: classGapMatch
|
|
401
|
+
? parseInt(classGapMatch[1])
|
|
402
|
+
: null;
|
|
403
|
+
if (gapValue !== null && gapValue !== TOAST_CONTAINER_GAP_SPACING) {
|
|
404
|
+
// Find line number
|
|
405
|
+
let lineNumber = 0;
|
|
406
|
+
for (let i = 0; i < lines.length; i++) {
|
|
407
|
+
if (/gap/.test(lines[i]) &&
|
|
408
|
+
/toast|notification/i.test(content.slice(0, content.indexOf(lines[i])))) {
|
|
409
|
+
lineNumber = i + 1;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
issues.push({
|
|
414
|
+
file: filePath,
|
|
415
|
+
line: lineNumber || undefined,
|
|
416
|
+
type: "toast-container-wrong-gap",
|
|
417
|
+
severity: "warning",
|
|
418
|
+
message: `Toast container uses gap-${gapValue} but expected gap-${TOAST_CONTAINER_GAP_SPACING}`,
|
|
419
|
+
suggestion: `Change to gap: "var(--spacing-${TOAST_CONTAINER_GAP_SPACING})" or className="gap-${TOAST_CONTAINER_GAP_SPACING}"`,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return issues;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Check 5: Find opportunities where hardcoded spacing values could use shared constants
|
|
429
|
+
*
|
|
430
|
+
* Scans components for patterns like:
|
|
431
|
+
* - gap: "var(--spacing-X)" that could use SPACING constants
|
|
432
|
+
* - padding: "var(--spacing-X)" that could use SPACING constants
|
|
433
|
+
* - Hardcoded layout component name arrays
|
|
434
|
+
*
|
|
435
|
+
* These are shown as WARNINGS to encourage consolidation.
|
|
436
|
+
*/
|
|
437
|
+
async function checkSharedConstantOpportunities() {
|
|
438
|
+
const issues = [];
|
|
439
|
+
// Scan component files (not preflights - those are checked separately)
|
|
440
|
+
const componentFiles = await getAppComponentsTsxFiles();
|
|
441
|
+
// Spacing patterns to detect with their suggested constants
|
|
442
|
+
const spacingOpportunities = [
|
|
443
|
+
{ value: 2, type: "gap", constant: "SPACING.COMPACT_GAP", minOccurrences: 2 },
|
|
444
|
+
{ value: 3, type: "gap", constant: "SPACING.CARD_CONTAINER_GAP", minOccurrences: 2 },
|
|
445
|
+
{ value: 6, type: "gap", constant: "SPACING.SECTION_GAP", minOccurrences: 2 },
|
|
446
|
+
{ value: 4, type: "padding", constant: "SPACING.CONTENT_PADDING", minOccurrences: 3 },
|
|
447
|
+
{ value: 6, type: "padding", constant: "SPACING.CARD_PADDING", minOccurrences: 2 },
|
|
448
|
+
];
|
|
449
|
+
for (const file of componentFiles) {
|
|
450
|
+
// Skip layout files themselves - they're the source of truth
|
|
451
|
+
if (file.includes("Layout") && file.includes("components/shared"))
|
|
452
|
+
continue;
|
|
453
|
+
// Skip DashboardPageLayout specifically - it's the canonical source
|
|
454
|
+
if (file.includes("DashboardPageLayout"))
|
|
455
|
+
continue;
|
|
456
|
+
const content = fs.readFileSync(file, "utf8");
|
|
457
|
+
// Skip if file already imports SPACING from lib/constants
|
|
458
|
+
if (content.includes("from '@/lib/constants/spacing'") ||
|
|
459
|
+
content.includes('from "@/lib/constants/spacing"') ||
|
|
460
|
+
content.includes("from '@/lib/constants'") ||
|
|
461
|
+
content.includes('from "@/lib/constants"')) {
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
// Check each spacing pattern
|
|
465
|
+
for (const { value, type, constant, minOccurrences } of spacingOpportunities) {
|
|
466
|
+
const pattern = new RegExp(`${type}:\\s*["']var\\(--spacing-${value}\\)["']`, "g");
|
|
467
|
+
const matches = [...content.matchAll(pattern)];
|
|
468
|
+
if (matches.length >= minOccurrences) {
|
|
469
|
+
issues.push({
|
|
470
|
+
file,
|
|
471
|
+
type: `hardcoded-${type}-spacing-${value}`,
|
|
472
|
+
severity: "warning",
|
|
473
|
+
message: `${matches.length}x ${type}: "var(--spacing-${value})" ${console_chars_1.chars.arrow} use ${constant}`,
|
|
474
|
+
suggestion: `Import { SPACING } from '@/lib/constants/spacing'`,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// Check for files that define their own SPACING constants
|
|
479
|
+
if (/const\s+SPACING\s*=\s*\{/.test(content) && !file.includes("lib/constants")) {
|
|
480
|
+
issues.push({
|
|
481
|
+
file,
|
|
482
|
+
type: "duplicate-spacing-constant",
|
|
483
|
+
severity: "warning",
|
|
484
|
+
message: "Local SPACING constant - import from lib/constants/spacing instead",
|
|
485
|
+
suggestion: "Import { SPACING } from '@/lib/constants/spacing'",
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
// Check for hardcoded layout component arrays
|
|
489
|
+
if (/const\s+\w*(?:layouts?|pageLayouts?)\w*\s*=\s*\[\s*["'](?:Admin|Seller|Account|Dashboard)PageLayout["']/i.test(content)) {
|
|
490
|
+
issues.push({
|
|
491
|
+
file,
|
|
492
|
+
type: "hardcoded-layout-array",
|
|
493
|
+
severity: "warning",
|
|
494
|
+
message: "Hardcoded layout array - import GAP_LAYOUT_COMPONENTS",
|
|
495
|
+
suggestion: "Import from scripts/active/preflights/shared/layout-constants",
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return issues;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Check 6: Find components using layout components that might benefit from shared constants
|
|
503
|
+
*/
|
|
504
|
+
async function checkLayoutConsumerOpportunities() {
|
|
505
|
+
const issues = [];
|
|
506
|
+
const componentFiles = await getAppComponentsTsxFiles();
|
|
507
|
+
// Skip wrapper layout files - they legitimately use DashboardPageLayout
|
|
508
|
+
const wrapperFiles = ["AdminPageLayout", "SellerPageLayout", "AccountPageLayout"];
|
|
509
|
+
for (const file of componentFiles) {
|
|
510
|
+
const content = fs.readFileSync(file, "utf8");
|
|
511
|
+
// Skip if file already imports from layout-constants or lib/constants
|
|
512
|
+
if (content.includes("layout-constants") || content.includes("from '@/lib/constants"))
|
|
513
|
+
continue;
|
|
514
|
+
// Skip wrapper layout files
|
|
515
|
+
if (wrapperFiles.some((wrapper) => file.includes(wrapper)))
|
|
516
|
+
continue;
|
|
517
|
+
// Check if file uses multiple layout components (excluding wrapper patterns)
|
|
518
|
+
const layoutUsageCount = GAP_LAYOUT_COMPONENTS.filter((comp) => new RegExp(`<${comp}[\\s>]`).test(content)).length;
|
|
519
|
+
// Only flag if using 3+ different layouts (2 is common for wrapper pattern)
|
|
520
|
+
if (layoutUsageCount >= 3) {
|
|
521
|
+
issues.push({
|
|
522
|
+
file,
|
|
523
|
+
type: "multiple-layout-usage",
|
|
524
|
+
severity: "warning",
|
|
525
|
+
message: `Uses ${layoutUsageCount} different layout components - might benefit from shared constants`,
|
|
526
|
+
suggestion: "If checking layout types, consider importing GAP_LAYOUT_COMPONENTS",
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
// Check for switch/if statements checking layout component names
|
|
530
|
+
const layoutSwitchPattern = /(?:switch|if)\s*\([^)]*(?:Admin|Seller|Account|Dashboard)PageLayout/;
|
|
531
|
+
if (layoutSwitchPattern.test(content)) {
|
|
532
|
+
issues.push({
|
|
533
|
+
file,
|
|
534
|
+
type: "layout-type-checking",
|
|
535
|
+
severity: "warning",
|
|
536
|
+
message: "Contains layout type checking logic - could use GAP_LAYOUT_COMPONENTS",
|
|
537
|
+
suggestion: "Import GAP_LAYOUT_COMPONENTS for type-safe layout checking",
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return issues;
|
|
542
|
+
}
|
|
543
|
+
async function run() {
|
|
544
|
+
const reporter = (0, universal_progress_reporter_1.createUniversalProgressReporter)(exports.name);
|
|
545
|
+
// Load project-specific dependencies
|
|
546
|
+
const loaded = await loadProjectDependencies();
|
|
547
|
+
if (!loaded) {
|
|
548
|
+
console.log(`${console_chars_1.emoji.skip} Skipping: project-specific modules not available`);
|
|
549
|
+
return { passed: true, skipped: true };
|
|
550
|
+
}
|
|
551
|
+
const args = process.argv.slice(2);
|
|
552
|
+
const verbose = args.includes("--verbose") || args.includes("-v");
|
|
553
|
+
console.log(`\n${console_chars_1.emoji.refresh} LAYOUT CONSTANTS SYNC PREFLIGHT`);
|
|
554
|
+
console.log(`${console_chars_1.box.doubleHorizontal}`.repeat(70));
|
|
555
|
+
console.log(`Shared Constants: GAP_LAYOUT_COMPONENTS has ${GAP_LAYOUT_COMPONENTS.length} components`);
|
|
556
|
+
console.log(`Expected Gap: spacing-${LAYOUT_GAP_SPACING} (${LAYOUT_GAP_SPACING * 4}px)`);
|
|
557
|
+
console.log(`Expected PaddingTop: spacing-${LAYOUT_PADDING_TOP_SPACING} (${LAYOUT_PADDING_TOP_SPACING * 4}px)`);
|
|
558
|
+
console.log(`${console_chars_1.box.doubleHorizontal}`.repeat(70));
|
|
559
|
+
const allIssues = [];
|
|
560
|
+
// Run core checks (always run)
|
|
561
|
+
console.log(`\n${console_chars_1.emoji.search} Checking layout component implementations...`);
|
|
562
|
+
const implIssues = await checkLayoutComponentsImplementGap();
|
|
563
|
+
allIssues.push(...implIssues);
|
|
564
|
+
console.log(` Found ${implIssues.length} issue(s)`);
|
|
565
|
+
console.log(`${console_chars_1.emoji.search} Checking preflights use shared constants...`);
|
|
566
|
+
const preflightIssues = await checkPreflightsUseSharedConstants();
|
|
567
|
+
allIssues.push(...preflightIssues);
|
|
568
|
+
console.log(` Found ${preflightIssues.length} issue(s)`);
|
|
569
|
+
console.log(`${console_chars_1.emoji.search} Checking all registered components exist...`);
|
|
570
|
+
const existIssues = await checkLayoutComponentsExist();
|
|
571
|
+
allIssues.push(...existIssues);
|
|
572
|
+
console.log(` Found ${existIssues.length} issue(s)`);
|
|
573
|
+
console.log(`${console_chars_1.emoji.search} Checking for unregistered gap layouts...`);
|
|
574
|
+
const unregisteredIssues = await checkUnregisteredGapLayouts();
|
|
575
|
+
allIssues.push(...unregisteredIssues);
|
|
576
|
+
console.log(` Found ${unregisteredIssues.length} issue(s)`);
|
|
577
|
+
console.log(`${console_chars_1.emoji.search} Checking toast container gap spacing...`);
|
|
578
|
+
const toastGapIssues = await checkToastContainerGaps();
|
|
579
|
+
allIssues.push(...toastGapIssues);
|
|
580
|
+
console.log(` Found ${toastGapIssues.length} issue(s)`);
|
|
581
|
+
// Run opportunity checks (always run - shown as warnings)
|
|
582
|
+
console.log(`${console_chars_1.emoji.search} Scanning for consolidation opportunities...`);
|
|
583
|
+
const constantOpportunities = await checkSharedConstantOpportunities();
|
|
584
|
+
const consumerOpportunities = await checkLayoutConsumerOpportunities();
|
|
585
|
+
allIssues.push(...constantOpportunities, ...consumerOpportunities);
|
|
586
|
+
console.log(` Found ${constantOpportunities.length + consumerOpportunities.length} opportunity warning(s)`);
|
|
587
|
+
// Report results
|
|
588
|
+
const errors = allIssues.filter((i) => i.severity === "error");
|
|
589
|
+
const warnings = allIssues.filter((i) => i.severity === "warning");
|
|
590
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
591
|
+
console.log(`\n${console_chars_1.emoji.success} Layout constants are in sync!`);
|
|
592
|
+
console.log(`${console_chars_1.box.doubleHorizontal}`.repeat(70));
|
|
593
|
+
process.exit(0);
|
|
594
|
+
}
|
|
595
|
+
// Print issues grouped by severity
|
|
596
|
+
if (errors.length > 0 || warnings.length > 0) {
|
|
597
|
+
console.log(`\n${console_chars_1.emoji.error} Found ${errors.length} error(s), ${warnings.length} warning(s):\n`);
|
|
598
|
+
}
|
|
599
|
+
// Group by file
|
|
600
|
+
const byFile = new Map();
|
|
601
|
+
for (const issue of allIssues) {
|
|
602
|
+
const existing = byFile.get(issue.file) || [];
|
|
603
|
+
existing.push(issue);
|
|
604
|
+
byFile.set(issue.file, existing);
|
|
605
|
+
}
|
|
606
|
+
for (const [file, fileIssues] of byFile) {
|
|
607
|
+
const fileErrors = fileIssues.filter((i) => i.severity === "error");
|
|
608
|
+
const fileWarnings = fileIssues.filter((i) => i.severity === "warning");
|
|
609
|
+
if (fileErrors.length > 0 || fileWarnings.length > 0) {
|
|
610
|
+
console.log(`${console_chars_1.emoji.folder} ${file}`);
|
|
611
|
+
for (const issue of [...fileErrors, ...fileWarnings]) {
|
|
612
|
+
const icon = issue.severity === "error" ? console_chars_1.emoji.error : console_chars_1.emoji.warning;
|
|
613
|
+
console.log(` ${icon} ${issue.line ? `Line ${issue.line}: ` : ""}${issue.message}`);
|
|
614
|
+
if (issue.suggestion && verbose) {
|
|
615
|
+
console.log(` ${console_chars_1.box.bottomLeft}${console_chars_1.box.horizontal} ${issue.suggestion}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
console.log();
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
console.log(`${console_chars_1.box.doubleHorizontal}`.repeat(70));
|
|
622
|
+
if (errors.length > 0) {
|
|
623
|
+
console.log(`${console_chars_1.emoji.error} FAILED: ${errors.length} error(s) must be fixed`);
|
|
624
|
+
process.exit(1);
|
|
625
|
+
}
|
|
626
|
+
else if (warnings.length > 0) {
|
|
627
|
+
console.log(`${console_chars_1.emoji.warning} PASSED with ${warnings.length} warning(s) - consider using shared constants`);
|
|
628
|
+
if (!verbose) {
|
|
629
|
+
console.log(`${console_chars_1.emoji.hint} Run with --verbose to see suggestions`);
|
|
630
|
+
}
|
|
631
|
+
process.exit(0);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
console.log(`${console_chars_1.emoji.success} Layout constants are in sync!`);
|
|
635
|
+
process.exit(0);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
// Allow direct execution
|
|
639
|
+
if (require.main === module) {
|
|
640
|
+
run().catch(console.error);
|
|
641
|
+
}
|
|
642
|
+
//# sourceMappingURL=layout-constants-sync.js.map
|