@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.
Files changed (49) hide show
  1. package/dist/checks/auth/session-provider-wrapper.d.ts +47 -0
  2. package/dist/checks/auth/session-provider-wrapper.d.ts.map +1 -0
  3. package/dist/checks/auth/session-provider-wrapper.js +286 -0
  4. package/dist/checks/auth/session-provider-wrapper.js.map +1 -0
  5. package/dist/checks/database/prisma-upsert-safety.d.ts +39 -0
  6. package/dist/checks/database/prisma-upsert-safety.d.ts.map +1 -0
  7. package/dist/checks/database/prisma-upsert-safety.js +220 -0
  8. package/dist/checks/database/prisma-upsert-safety.js.map +1 -0
  9. package/dist/checks/dependencies/dependency-health-monitor.d.ts +49 -0
  10. package/dist/checks/dependencies/dependency-health-monitor.d.ts.map +1 -0
  11. package/dist/checks/dependencies/dependency-health-monitor.js +323 -0
  12. package/dist/checks/dependencies/dependency-health-monitor.js.map +1 -0
  13. package/dist/checks/file-hygiene-validation.d.ts +31 -0
  14. package/dist/checks/file-hygiene-validation.d.ts.map +1 -0
  15. package/dist/checks/file-hygiene-validation.js +934 -0
  16. package/dist/checks/file-hygiene-validation.js.map +1 -0
  17. package/dist/checks/organization/file-cleanup-validation.d.ts +22 -0
  18. package/dist/checks/organization/file-cleanup-validation.d.ts.map +1 -0
  19. package/dist/checks/organization/file-cleanup-validation.js +1121 -0
  20. package/dist/checks/organization/file-cleanup-validation.js.map +1 -0
  21. package/dist/checks/runtime/tailwind-runtime-check.d.ts +36 -0
  22. package/dist/checks/runtime/tailwind-runtime-check.d.ts.map +1 -0
  23. package/dist/checks/runtime/tailwind-runtime-check.js +264 -0
  24. package/dist/checks/runtime/tailwind-runtime-check.js.map +1 -0
  25. package/dist/checks/shipping-integration-validation.d.ts +28 -0
  26. package/dist/checks/shipping-integration-validation.d.ts.map +1 -0
  27. package/dist/checks/shipping-integration-validation.js +409 -0
  28. package/dist/checks/shipping-integration-validation.js.map +1 -0
  29. package/dist/checks/system/layout-constants-sync.d.ts +36 -0
  30. package/dist/checks/system/layout-constants-sync.d.ts.map +1 -0
  31. package/dist/checks/system/layout-constants-sync.js +642 -0
  32. package/dist/checks/system/layout-constants-sync.js.map +1 -0
  33. package/dist/checks/system/preflight-circular-dependency-detector.d.ts +26 -0
  34. package/dist/checks/system/preflight-circular-dependency-detector.d.ts.map +1 -0
  35. package/dist/checks/system/preflight-circular-dependency-detector.js +310 -0
  36. package/dist/checks/system/preflight-circular-dependency-detector.js.map +1 -0
  37. package/dist/checks/system/preflight-execution-benchmarks.d.ts +24 -0
  38. package/dist/checks/system/preflight-execution-benchmarks.d.ts.map +1 -0
  39. package/dist/checks/system/preflight-execution-benchmarks.js +282 -0
  40. package/dist/checks/system/preflight-execution-benchmarks.js.map +1 -0
  41. package/dist/checks/system/preflight-tag-taxonomy-validator.d.ts +27 -0
  42. package/dist/checks/system/preflight-tag-taxonomy-validator.d.ts.map +1 -0
  43. package/dist/checks/system/preflight-tag-taxonomy-validator.js +361 -0
  44. package/dist/checks/system/preflight-tag-taxonomy-validator.js.map +1 -0
  45. package/dist/utils/console-chars.d.ts +16 -0
  46. package/dist/utils/console-chars.d.ts.map +1 -1
  47. package/dist/utils/console-chars.js +10 -0
  48. package/dist/utils/console-chars.js.map +1 -1
  49. package/package.json +1 -1
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Preflight: Session Provider Wrapper Check
3
+ *
4
+ * Detects useSession() calls in components that may not be wrapped in SessionProvider.
5
+ * This catches the common error: "[next-auth]: useSession must be wrapped in a <SessionProvider />"
6
+ *
7
+ * Checks:
8
+ * 1. Files using useSession() must have "use client" directive
9
+ * 2. Files using useSession() should not be in locations that bypass the provider tree
10
+ * 3. Warns about useSession() in error boundaries or fallback components
11
+ * 4. Detects deprecated getSession() usage (should use auth() server-side)
12
+ * 5. Detects conditional SessionProvider wrapping patterns
13
+ * 6. Detects useSession in server components (async function exports without "use client")
14
+ *
15
+ * Usage:
16
+ * pnpm preflight:session-provider-wrapper
17
+ * pnpm preflight:session-provider-wrapper --fix # Auto-fix missing "use client"
18
+ *
19
+ * @blocking - This check blocks builds because missing SessionProvider causes runtime crashes
20
+ */
21
+ export declare const id = "auth/session-provider-wrapper";
22
+ export declare const name = "Session Provider Wrapper";
23
+ export declare const category = "auth";
24
+ export declare const blocking = true;
25
+ export declare const description = "Preflight: Session Provider Wrapper Check";
26
+ export declare const tags: string[];
27
+ interface Issue {
28
+ file: string;
29
+ line: number;
30
+ type: "error" | "warning";
31
+ code: string;
32
+ message: string;
33
+ suggestion?: string;
34
+ fixable?: boolean;
35
+ }
36
+ interface CheckResult {
37
+ issues: Issue[];
38
+ fixApplied?: boolean;
39
+ }
40
+ declare function checkFile(filePath: string, autoFix?: boolean): CheckResult;
41
+ export declare function run(): Promise<{
42
+ errors: number;
43
+ warnings: number;
44
+ fixed: number;
45
+ }>;
46
+ export { checkFile };
47
+ //# sourceMappingURL=session-provider-wrapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-provider-wrapper.d.ts","sourceRoot":"","sources":["../../../src/checks/auth/session-provider-wrapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAWH,eAAO,MAAM,EAAE,kCAAkC,CAAC;AAClD,eAAO,MAAM,IAAI,6BAA6B,CAAC;AAC/C,eAAO,MAAM,QAAQ,SAAS,CAAC;AAC/B,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,WAAW,8CAA8C,CAAC;AACvE,eAAO,MAAM,IAAI,UAAW,CAAC;AA2C7B,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,WAAW;IACnB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAkBD,iBAAS,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,OAAe,GAAG,WAAW,CA2H1E;AAED,wBAAsB,GAAG,IAAI,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CA0ExF;AAYD,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ /**
3
+ * Preflight: Session Provider Wrapper Check
4
+ *
5
+ * Detects useSession() calls in components that may not be wrapped in SessionProvider.
6
+ * This catches the common error: "[next-auth]: useSession must be wrapped in a <SessionProvider />"
7
+ *
8
+ * Checks:
9
+ * 1. Files using useSession() must have "use client" directive
10
+ * 2. Files using useSession() should not be in locations that bypass the provider tree
11
+ * 3. Warns about useSession() in error boundaries or fallback components
12
+ * 4. Detects deprecated getSession() usage (should use auth() server-side)
13
+ * 5. Detects conditional SessionProvider wrapping patterns
14
+ * 6. Detects useSession in server components (async function exports without "use client")
15
+ *
16
+ * Usage:
17
+ * pnpm preflight:session-provider-wrapper
18
+ * pnpm preflight:session-provider-wrapper --fix # Auto-fix missing "use client"
19
+ *
20
+ * @blocking - This check blocks builds because missing SessionProvider causes runtime crashes
21
+ */
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.tags = exports.description = exports.blocking = exports.category = exports.name = exports.id = void 0;
27
+ exports.run = run;
28
+ exports.checkFile = checkFile;
29
+ const fs_1 = __importDefault(require("fs"));
30
+ const path_1 = __importDefault(require("path"));
31
+ const console_chars_1 = require("../../utils/console-chars");
32
+ const glob_1 = require("glob");
33
+ const universal_progress_reporter_1 = require("../system/universal-progress-reporter");
34
+ // Check metadata
35
+ exports.id = "auth/session-provider-wrapper";
36
+ exports.name = "Session Provider Wrapper";
37
+ exports.category = "auth";
38
+ exports.blocking = true;
39
+ exports.description = "Preflight: Session Provider Wrapper Check";
40
+ exports.tags = ["auth"];
41
+ const SCAN_PATTERNS = [
42
+ "app/**/*.tsx",
43
+ "app/**/*.ts",
44
+ "components/**/*.tsx",
45
+ "components/**/*.ts",
46
+ "contexts/**/*.tsx",
47
+ "hooks/**/*.tsx",
48
+ "hooks/**/*.ts",
49
+ "lib/**/*.tsx",
50
+ "lib/**/*.ts",
51
+ ];
52
+ const EXCLUDE_PATTERNS = [
53
+ "**/node_modules/**",
54
+ "**/.next/**",
55
+ "**/dist/**",
56
+ "**/coverage/**",
57
+ "**/*.test.tsx",
58
+ "**/*.test.ts",
59
+ "**/*.spec.tsx",
60
+ "**/*.spec.ts",
61
+ "**/__tests__/**",
62
+ "**/__mocks__/**",
63
+ ];
64
+ const UNSAFE_LOCATIONS = [
65
+ { pattern: /error\.tsx$/, reason: "Error boundaries render outside the normal component tree" },
66
+ { pattern: /not-found\.tsx$/, reason: "Not-found pages may render before providers are mounted" },
67
+ {
68
+ pattern: /loading\.tsx$/,
69
+ reason: "Loading states render during Suspense, before providers may be ready",
70
+ },
71
+ { pattern: /global-error\.tsx$/, reason: "Global error boundary renders at the root level" },
72
+ ];
73
+ const CONDITIONAL_PROVIDER_PATTERNS = [
74
+ /\{[^}]*&&\s*<SessionProvider/,
75
+ /\{[^}]*\?\s*<SessionProvider/,
76
+ /<SessionProvider[^>]*>\s*\{[^}]*&&/,
77
+ ];
78
+ function findLineNumber(content, searchStr) {
79
+ const lines = content.split("\n");
80
+ for (let i = 0; i < lines.length; i++) {
81
+ if (lines[i].includes(searchStr))
82
+ return i + 1;
83
+ }
84
+ return 1;
85
+ }
86
+ function isServerComponent(content) {
87
+ const hasUseClient = /^["']use client["'];?\s*$/m.test(content);
88
+ if (hasUseClient)
89
+ return false;
90
+ const hasAsyncExport = /export\s+(default\s+)?async\s+function/.test(content);
91
+ const hasDefaultExport = /export\s+default/.test(content);
92
+ return hasAsyncExport || hasDefaultExport;
93
+ }
94
+ function checkFile(filePath, autoFix = false) {
95
+ const issues = [];
96
+ let content = fs_1.default.readFileSync(filePath, "utf-8");
97
+ let fixApplied = false;
98
+ const normalizedPath = filePath.replace(/\\/g, "/");
99
+ const useSessionMatch = content.match(/useSession\s*\(/);
100
+ if (useSessionMatch) {
101
+ const useSessionLine = findLineNumber(content, "useSession");
102
+ const hasUseClient = /^["']use client["'];?\s*$/m.test(content);
103
+ if (!hasUseClient) {
104
+ if (isServerComponent(content)) {
105
+ issues.push({
106
+ file: filePath,
107
+ line: useSessionLine,
108
+ type: "error",
109
+ code: "USE_SESSION_IN_SERVER_COMPONENT",
110
+ message: "useSession() called in what appears to be a Server Component",
111
+ suggestion: "Use server-side auth() from @/lib/auth instead of useSession()",
112
+ });
113
+ }
114
+ else {
115
+ issues.push({
116
+ file: filePath,
117
+ line: useSessionLine,
118
+ type: "error",
119
+ code: "MISSING_USE_CLIENT",
120
+ message: 'useSession() requires "use client" directive',
121
+ suggestion: 'Add "use client"; at the top of the file',
122
+ fixable: true,
123
+ });
124
+ if (autoFix) {
125
+ content = '"use client";\n\n' + content;
126
+ fs_1.default.writeFileSync(filePath, content, "utf-8");
127
+ fixApplied = true;
128
+ console.log(" " + console_chars_1.emoji.wrench + ' Fixed: Added "use client" to ' + filePath);
129
+ }
130
+ }
131
+ }
132
+ for (const unsafe of UNSAFE_LOCATIONS) {
133
+ if (unsafe.pattern.test(normalizedPath)) {
134
+ issues.push({
135
+ file: filePath,
136
+ line: useSessionLine,
137
+ type: "error",
138
+ code: "USE_SESSION_UNSAFE_LOCATION",
139
+ message: "useSession() used in " + path_1.default.basename(filePath) + " - " + unsafe.reason,
140
+ suggestion: "Use server-side auth() from @/lib/auth instead, or pass session as a prop",
141
+ });
142
+ }
143
+ }
144
+ const providesSession = /<SessionProvider[\s>]/.test(content);
145
+ if (providesSession) {
146
+ issues.push({
147
+ file: filePath,
148
+ line: useSessionLine,
149
+ type: "warning",
150
+ code: "PROVIDER_CONSUMER_SAME_FILE",
151
+ message: "Component both provides SessionProvider and uses useSession()",
152
+ suggestion: "Split into separate provider and consumer components",
153
+ });
154
+ }
155
+ }
156
+ const getSessionImport = content.match(/import\s*\{[^}]*\bgetSession\b[^}]*\}\s*from\s*["']next-auth\/react["']/);
157
+ const getSessionUsage = content.match(/\bgetSession\s*\(/);
158
+ if (getSessionImport || getSessionUsage) {
159
+ const line = findLineNumber(content, "getSession");
160
+ issues.push({
161
+ file: filePath,
162
+ line,
163
+ type: "error",
164
+ code: "DEPRECATED_GET_SESSION",
165
+ message: "getSession() from next-auth/react is deprecated",
166
+ suggestion: "Use auth() from @/lib/auth for server-side, or useSession() for client-side",
167
+ });
168
+ }
169
+ for (const pattern of CONDITIONAL_PROVIDER_PATTERNS) {
170
+ if (pattern.test(content)) {
171
+ const line = findLineNumber(content, "SessionProvider");
172
+ issues.push({
173
+ file: filePath,
174
+ line,
175
+ type: "error",
176
+ code: "CONDITIONAL_SESSION_PROVIDER",
177
+ message: "SessionProvider is conditionally rendered - this causes useSession errors",
178
+ suggestion: "Always render SessionProvider unconditionally at the app root",
179
+ });
180
+ break;
181
+ }
182
+ }
183
+ const authActionsMatch = content.match(/\b(signIn|signOut)\s*\(/);
184
+ if (authActionsMatch) {
185
+ const hasUseClient = /^["']use client["'];?\s*$/m.test(content);
186
+ const fromReact = content.includes('from "next-auth/react"') || content.includes("from 'next-auth/react'");
187
+ if (fromReact && !hasUseClient) {
188
+ const line = findLineNumber(content, authActionsMatch[1]);
189
+ issues.push({
190
+ file: filePath,
191
+ line,
192
+ type: "error",
193
+ code: "AUTH_ACTION_NO_USE_CLIENT",
194
+ message: authActionsMatch[1] + '() from next-auth/react requires "use client" directive',
195
+ suggestion: 'Add "use client"; at the top of the file, or use server actions',
196
+ fixable: true,
197
+ });
198
+ if (autoFix && !fixApplied) {
199
+ content = '"use client";\n\n' + content;
200
+ fs_1.default.writeFileSync(filePath, content, "utf-8");
201
+ fixApplied = true;
202
+ console.log(" " + console_chars_1.emoji.wrench + ' Fixed: Added "use client" to ' + filePath);
203
+ }
204
+ }
205
+ }
206
+ return { issues, fixApplied };
207
+ }
208
+ async function run() {
209
+ const reporter = (0, universal_progress_reporter_1.createUniversalProgressReporter)(exports.name);
210
+ const args = process.argv.slice(2);
211
+ const autoFix = args.includes("--fix");
212
+ console.log("\n" + console_chars_1.emoji.search + " Checking SessionProvider wrapper requirements...");
213
+ if (autoFix)
214
+ console.log(console_chars_1.emoji.wrench + " Auto-fix mode enabled\n");
215
+ else
216
+ console.log("");
217
+ const files = [];
218
+ for (const pattern of SCAN_PATTERNS) {
219
+ const matches = glob_1.glob.sync(pattern, { ignore: EXCLUDE_PATTERNS, nodir: true });
220
+ files.push(...matches);
221
+ }
222
+ const allIssues = [];
223
+ let totalFixed = 0;
224
+ for (const file of files) {
225
+ const result = checkFile(file, autoFix);
226
+ allIssues.push(...result.issues);
227
+ if (result.fixApplied)
228
+ totalFixed++;
229
+ }
230
+ const errors = allIssues.filter((i) => i.type === "error");
231
+ const warnings = allIssues.filter((i) => i.type === "warning");
232
+ const fixableCount = allIssues.filter((i) => i.fixable).length;
233
+ if (allIssues.length === 0) {
234
+ console.log(console_chars_1.emoji.success + " All session/auth patterns are correct\n");
235
+ console.log(" Scanned " + files.length + " files\n");
236
+ return { errors: 0, warnings: 0, fixed: totalFixed };
237
+ }
238
+ if (errors.length > 0) {
239
+ console.log(console_chars_1.emoji.error + " Found " + errors.length + " error(s):\n");
240
+ for (const issue of errors) {
241
+ console.log(" " + issue.file + ":" + issue.line);
242
+ console.log(" " + console_chars_1.emoji.error + " [" + issue.code + "] " + issue.message);
243
+ if (issue.suggestion)
244
+ console.log(" " + console_chars_1.emoji.hint + " " + issue.suggestion);
245
+ if (issue.fixable && !autoFix)
246
+ console.log(" " + console_chars_1.emoji.wrench + " Fixable with --fix");
247
+ console.log("");
248
+ }
249
+ }
250
+ if (warnings.length > 0) {
251
+ console.log(console_chars_1.emoji.warning + " Found " + warnings.length + " warning(s):\n");
252
+ for (const issue of warnings) {
253
+ console.log(" " + issue.file + ":" + issue.line);
254
+ console.log(" " + console_chars_1.emoji.warning + " [" + issue.code + "] " + issue.message);
255
+ if (issue.suggestion)
256
+ console.log(" " + console_chars_1.emoji.hint + " " + issue.suggestion);
257
+ console.log("");
258
+ }
259
+ }
260
+ console.log((0, console_chars_1.createDivider)(60));
261
+ if (totalFixed > 0)
262
+ console.log("\n" + console_chars_1.emoji.success + " Auto-fixed " + totalFixed + " file(s)");
263
+ if (fixableCount > 0 && !autoFix) {
264
+ console.log("\n" +
265
+ console_chars_1.emoji.hint +
266
+ " " +
267
+ fixableCount +
268
+ " issue(s) can be auto-fixed with: pnpm preflight:session-provider-wrapper --fix");
269
+ }
270
+ console.log("\n" + console_chars_1.emoji.info + " Why this matters:");
271
+ console.log(" - useSession() must be called within a <SessionProvider> tree");
272
+ console.log(" - Components in error.tsx, not-found.tsx render outside providers");
273
+ console.log(" - getSession() is deprecated - use auth() server-side");
274
+ console.log(" - Conditional SessionProvider causes hydration mismatches\n");
275
+ return { errors: errors.length, warnings: warnings.length, fixed: totalFixed };
276
+ }
277
+ // Allow direct execution
278
+ if (require.main === module) {
279
+ run()
280
+ .then((result) => process.exit(result.errors > 0 ? 1 : 0))
281
+ .catch((err) => {
282
+ console.error("Error:", err);
283
+ process.exit(1);
284
+ });
285
+ }
286
+ //# sourceMappingURL=session-provider-wrapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-provider-wrapper.js","sourceRoot":"","sources":["../../../src/checks/auth/session-provider-wrapper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Prisma Upsert Safety Preflight
4
+ *
5
+ * Detects unsafe upsert patterns that can cause duplicate records:
6
+ * 1. Upserts using non-unique fields in the where clause
7
+ * 2. Upserts with dynamic/generated IDs that won't match existing records
8
+ * 3. Missing unique constraints on fields used for upsert lookups
9
+ *
10
+ * The root cause: Using `upsert` with a `where` clause that doesn't match
11
+ * a unique constraint will always create new records instead of updating.
12
+ *
13
+ * Example of BAD pattern:
14
+ * await tx.model.upsert({
15
+ * where: { id: card.id || `temp_${card.clientPairId}` }, // temp_ never matches!
16
+ * create: { ... },
17
+ * update: { ... },
18
+ * });
19
+ *
20
+ * Example of GOOD pattern:
21
+ * const existing = await tx.model.findFirst({ where: { clientPairId } });
22
+ * if (existing) {
23
+ * await tx.model.update({ where: { id: existing.id }, data: { ... } });
24
+ * } else {
25
+ * await tx.model.create({ data: { ... } });
26
+ * }
27
+ *
28
+ * Usage:
29
+ * pnpm preflight:prisma-upsert-safety
30
+ * pnpm preflight:prisma-upsert-safety --verbose
31
+ */
32
+ export declare const id = "database/prisma-upsert-safety";
33
+ export declare const name = "Prisma Upsert Safety";
34
+ export declare const category = "database";
35
+ export declare const blocking = true;
36
+ export declare const description = "Prisma Upsert Safety Preflight";
37
+ export declare const tags: string[];
38
+ export declare function run(): Promise<void>;
39
+ //# sourceMappingURL=prisma-upsert-safety.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-upsert-safety.d.ts","sourceRoot":"","sources":["../../../src/checks/database/prisma-upsert-safety.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAWH,eAAO,MAAM,EAAE,kCAAkC,CAAC;AAClD,eAAO,MAAM,IAAI,yBAAyB,CAAC;AAC3C,eAAO,MAAM,QAAQ,aAAa,CAAC;AACnC,eAAO,MAAM,QAAQ,OAAO,CAAC;AAC7B,eAAO,MAAM,WAAW,mCAAmC,CAAC;AAC5D,eAAO,MAAM,IAAI,UAAe,CAAC;AAkDjC,wBAAsB,GAAG,kBAyHxB"}
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env tsx
2
+ "use strict";
3
+ /**
4
+ * Prisma Upsert Safety Preflight
5
+ *
6
+ * Detects unsafe upsert patterns that can cause duplicate records:
7
+ * 1. Upserts using non-unique fields in the where clause
8
+ * 2. Upserts with dynamic/generated IDs that won't match existing records
9
+ * 3. Missing unique constraints on fields used for upsert lookups
10
+ *
11
+ * The root cause: Using `upsert` with a `where` clause that doesn't match
12
+ * a unique constraint will always create new records instead of updating.
13
+ *
14
+ * Example of BAD pattern:
15
+ * await tx.model.upsert({
16
+ * where: { id: card.id || `temp_${card.clientPairId}` }, // temp_ never matches!
17
+ * create: { ... },
18
+ * update: { ... },
19
+ * });
20
+ *
21
+ * Example of GOOD pattern:
22
+ * const existing = await tx.model.findFirst({ where: { clientPairId } });
23
+ * if (existing) {
24
+ * await tx.model.update({ where: { id: existing.id }, data: { ... } });
25
+ * } else {
26
+ * await tx.model.create({ data: { ... } });
27
+ * }
28
+ *
29
+ * Usage:
30
+ * pnpm preflight:prisma-upsert-safety
31
+ * pnpm preflight:prisma-upsert-safety --verbose
32
+ */
33
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
34
+ if (k2 === undefined) k2 = k;
35
+ var desc = Object.getOwnPropertyDescriptor(m, k);
36
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
37
+ desc = { enumerable: true, get: function() { return m[k]; } };
38
+ }
39
+ Object.defineProperty(o, k2, desc);
40
+ }) : (function(o, m, k, k2) {
41
+ if (k2 === undefined) k2 = k;
42
+ o[k2] = m[k];
43
+ }));
44
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
45
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
46
+ }) : function(o, v) {
47
+ o["default"] = v;
48
+ });
49
+ var __importStar = (this && this.__importStar) || (function () {
50
+ var ownKeys = function(o) {
51
+ ownKeys = Object.getOwnPropertyNames || function (o) {
52
+ var ar = [];
53
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
54
+ return ar;
55
+ };
56
+ return ownKeys(o);
57
+ };
58
+ return function (mod) {
59
+ if (mod && mod.__esModule) return mod;
60
+ var result = {};
61
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
62
+ __setModuleDefault(result, mod);
63
+ return result;
64
+ };
65
+ })();
66
+ Object.defineProperty(exports, "__esModule", { value: true });
67
+ exports.tags = exports.description = exports.blocking = exports.category = exports.name = exports.id = void 0;
68
+ exports.run = run;
69
+ const fs = __importStar(require("fs"));
70
+ const path = __importStar(require("path"));
71
+ const console_chars_1 = require("../../utils/console-chars");
72
+ const universal_progress_reporter_1 = require("../system/universal-progress-reporter");
73
+ const glob_1 = require("glob");
74
+ // Check metadata
75
+ exports.id = "database/prisma-upsert-safety";
76
+ exports.name = "Prisma Upsert Safety";
77
+ exports.category = "database";
78
+ exports.blocking = true;
79
+ exports.description = "Prisma Upsert Safety Preflight";
80
+ exports.tags = ["database"];
81
+ function isVerbose() {
82
+ return process.argv.includes("--verbose");
83
+ }
84
+ // Patterns that indicate unsafe upsert usage
85
+ const UNSAFE_PATTERNS = [
86
+ {
87
+ // Upsert with fallback ID pattern: id: value || `temp_${...}` or id: value || generateId()
88
+ // This is the most dangerous pattern - temp IDs will never match
89
+ pattern: /\.upsert\s*\(\s*\{[\s\S]*?where\s*:\s*\{[\s\S]*?id\s*:\s*[^,}]+\|\|[^,}]+/g,
90
+ type: "error",
91
+ message: "Upsert with fallback ID pattern - temp IDs will never match existing records",
92
+ suggestion: "Use findFirst + update/create pattern instead of upsert with fallback IDs",
93
+ },
94
+ {
95
+ // Upsert with template literal in where clause for ID field specifically
96
+ // But NOT for composite keys like storeId_date which are safe
97
+ pattern: /\.upsert\s*\(\s*\{[\s\S]*?where\s*:\s*\{\s*id\s*:\s*`[^`]*\$\{/g,
98
+ type: "error",
99
+ message: "Upsert with dynamic template literal ID - may not match existing records",
100
+ suggestion: "Use findFirst to check existence before create/update",
101
+ },
102
+ {
103
+ // Upsert with generateId/generatePrefixedId directly in where clause for id field
104
+ // This is always wrong - generated IDs won't match existing records
105
+ pattern: /\.upsert\s*\(\s*\{[\s\S]*?where\s*:\s*\{\s*id\s*:\s*[^,}]*generate(?:Prefixed)?Id/g,
106
+ type: "error",
107
+ message: "Upsert with generated ID in where clause - will always create new records",
108
+ suggestion: "Generate ID only in create block, use existing field for where clause",
109
+ },
110
+ ];
111
+ // Patterns that are acceptable
112
+ const SAFE_PATTERNS = [
113
+ // Upsert with @@unique composite key lookup
114
+ /\.upsert\s*\(\s*\{[\s\S]*?where\s*:\s*\{[\s\S]*?_[a-zA-Z]+_[a-zA-Z]+\s*:/,
115
+ // Upsert preceded by findFirst check
116
+ /findFirst[\s\S]{0,200}\.upsert/,
117
+ ];
118
+ async function run() {
119
+ const reporter = (0, universal_progress_reporter_1.createUniversalProgressReporter)(exports.name);
120
+ console.log(`${console_chars_1.emoji.database} Prisma Upsert Safety Preflight`);
121
+ console.log((0, console_chars_1.createDivider)(60, "heavy"));
122
+ const issues = [];
123
+ // Find all TypeScript files in app/ and lib/ directories
124
+ const files = await (0, glob_1.glob)(["app/**/*.ts", "app/**/*.tsx", "lib/**/*.ts"], {
125
+ ignore: ["**/node_modules/**", "**/*.d.ts", "**/*.test.ts", "**/*.spec.ts"],
126
+ cwd: process.cwd(),
127
+ });
128
+ console.log(`\n${console_chars_1.emoji.search} Scanning ${files.length} files for unsafe upsert patterns...\n`);
129
+ let filesChecked = 0;
130
+ let upsertsFound = 0;
131
+ for (const file of files) {
132
+ const filePath = path.join(process.cwd(), file);
133
+ const content = fs.readFileSync(filePath, "utf-8");
134
+ // Skip files without upsert
135
+ if (!content.includes(".upsert")) {
136
+ continue;
137
+ }
138
+ filesChecked++;
139
+ const lines = content.split("\n");
140
+ // Check if file has safe patterns that make upserts okay
141
+ const hasSafePattern = SAFE_PATTERNS.some((pattern) => pattern.test(content));
142
+ for (const check of UNSAFE_PATTERNS) {
143
+ const matches = content.matchAll(check.pattern);
144
+ for (const match of matches) {
145
+ upsertsFound++;
146
+ // Find line number
147
+ const beforeMatch = content.substring(0, match.index);
148
+ const lineNum = beforeMatch.split("\n").length;
149
+ // Skip if file has compensating safe patterns (only for non-critical issues)
150
+ if (hasSafePattern && check.type === "warning") {
151
+ if (isVerbose()) {
152
+ console.log(` ${console_chars_1.emoji.info} ${file}:${lineNum} - Safe pattern detected, skipping`);
153
+ }
154
+ continue;
155
+ }
156
+ const codeLine = lines[lineNum - 1]?.trim() || "";
157
+ issues.push({
158
+ file,
159
+ line: lineNum,
160
+ type: check.type,
161
+ message: check.message,
162
+ code: codeLine.substring(0, 80),
163
+ });
164
+ }
165
+ }
166
+ if (isVerbose() && filesChecked % 50 === 0) {
167
+ console.log(` Checked ${filesChecked} files with upserts...`);
168
+ }
169
+ }
170
+ console.log(`${console_chars_1.emoji.chart} Scanned ${filesChecked} files containing upsert operations`);
171
+ console.log(` Found ${upsertsFound} upsert patterns to analyze\n`);
172
+ // Report issues
173
+ if (issues.length === 0) {
174
+ console.log(`${console_chars_1.emoji.success} No unsafe upsert patterns detected`);
175
+ process.exit(0);
176
+ }
177
+ console.log((0, console_chars_1.createDivider)(60, "heavy"));
178
+ console.log(`${console_chars_1.emoji.clipboard} Unsafe Upsert Patterns Found:`);
179
+ console.log((0, console_chars_1.createDivider)(60, "heavy"));
180
+ let errors = 0;
181
+ let warnings = 0;
182
+ for (const issue of issues) {
183
+ const icon = issue.type === "error" ? `${console_chars_1.emoji.error}` : `${console_chars_1.emoji.warning}`;
184
+ console.log(`\n${icon} ${issue.file}:${issue.line}`);
185
+ console.log(` ${issue.message}`);
186
+ console.log(` Code: ${issue.code}...`);
187
+ if (issue.type === "error") {
188
+ errors++;
189
+ }
190
+ else {
191
+ warnings++;
192
+ }
193
+ }
194
+ console.log("\n" + (0, console_chars_1.createDivider)(60, "heavy"));
195
+ console.log(`${console_chars_1.emoji.chart} Summary:`);
196
+ console.log(` Errors: ${errors}`);
197
+ console.log(` Warnings: ${warnings}`);
198
+ if (errors > 0) {
199
+ console.log(`\n${console_chars_1.emoji.error} FAILED: Unsafe upsert patterns detected`);
200
+ console.log("\n Fix by using findFirst + update/create pattern:");
201
+ console.log(" ```typescript");
202
+ console.log(" const existing = await tx.model.findFirst({");
203
+ console.log(" where: { uniqueField: value },");
204
+ console.log(" });");
205
+ console.log(" if (existing) {");
206
+ console.log(" await tx.model.update({ where: { id: existing.id }, data });");
207
+ console.log(" } else {");
208
+ console.log(" await tx.model.create({ data: { id: generateId(), ...data } });");
209
+ console.log(" }");
210
+ console.log(" ```");
211
+ process.exit(1);
212
+ }
213
+ console.log(`\n${console_chars_1.emoji.success} Passed with ${warnings} warnings`);
214
+ process.exit(0);
215
+ }
216
+ // Allow direct execution
217
+ if (require.main === module) {
218
+ run().catch(console.error);
219
+ }
220
+ //# sourceMappingURL=prisma-upsert-safety.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-upsert-safety.js","sourceRoot":"","sources":["../../../src/checks/database/prisma-upsert-safety.ts"],"names":[],"mappings":"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"}