wiggum-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +341 -0
  3. package/bin/ralph.js +8 -0
  4. package/dist/ai/enhancer.d.ts +100 -0
  5. package/dist/ai/enhancer.d.ts.map +1 -0
  6. package/dist/ai/enhancer.js +233 -0
  7. package/dist/ai/enhancer.js.map +1 -0
  8. package/dist/ai/index.d.ts +8 -0
  9. package/dist/ai/index.d.ts.map +1 -0
  10. package/dist/ai/index.js +11 -0
  11. package/dist/ai/index.js.map +1 -0
  12. package/dist/ai/prompts.d.ts +26 -0
  13. package/dist/ai/prompts.d.ts.map +1 -0
  14. package/dist/ai/prompts.js +201 -0
  15. package/dist/ai/prompts.js.map +1 -0
  16. package/dist/ai/providers.d.ts +35 -0
  17. package/dist/ai/providers.d.ts.map +1 -0
  18. package/dist/ai/providers.js +104 -0
  19. package/dist/ai/providers.js.map +1 -0
  20. package/dist/cli.d.ts +6 -0
  21. package/dist/cli.d.ts.map +1 -0
  22. package/dist/cli.js +196 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/commands/init.d.ts +16 -0
  25. package/dist/commands/init.d.ts.map +1 -0
  26. package/dist/commands/init.js +124 -0
  27. package/dist/commands/init.js.map +1 -0
  28. package/dist/commands/monitor.d.ts +17 -0
  29. package/dist/commands/monitor.d.ts.map +1 -0
  30. package/dist/commands/monitor.js +342 -0
  31. package/dist/commands/monitor.js.map +1 -0
  32. package/dist/commands/new.d.ts +19 -0
  33. package/dist/commands/new.d.ts.map +1 -0
  34. package/dist/commands/new.js +272 -0
  35. package/dist/commands/new.js.map +1 -0
  36. package/dist/commands/run.d.ts +16 -0
  37. package/dist/commands/run.d.ts.map +1 -0
  38. package/dist/commands/run.js +175 -0
  39. package/dist/commands/run.js.map +1 -0
  40. package/dist/generator/config.d.ts +59 -0
  41. package/dist/generator/config.d.ts.map +1 -0
  42. package/dist/generator/config.js +68 -0
  43. package/dist/generator/config.js.map +1 -0
  44. package/dist/generator/index.d.ts +64 -0
  45. package/dist/generator/index.d.ts.map +1 -0
  46. package/dist/generator/index.js +147 -0
  47. package/dist/generator/index.js.map +1 -0
  48. package/dist/generator/templates.d.ts +70 -0
  49. package/dist/generator/templates.d.ts.map +1 -0
  50. package/dist/generator/templates.js +296 -0
  51. package/dist/generator/templates.js.map +1 -0
  52. package/dist/generator/writer.d.ts +93 -0
  53. package/dist/generator/writer.d.ts.map +1 -0
  54. package/dist/generator/writer.js +213 -0
  55. package/dist/generator/writer.js.map +1 -0
  56. package/dist/index.d.ts +12 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +17 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/scanner/detectors/core/framework.d.ts +11 -0
  61. package/dist/scanner/detectors/core/framework.d.ts.map +1 -0
  62. package/dist/scanner/detectors/core/framework.js +275 -0
  63. package/dist/scanner/detectors/core/framework.js.map +1 -0
  64. package/dist/scanner/detectors/core/packageManager.d.ts +11 -0
  65. package/dist/scanner/detectors/core/packageManager.d.ts.map +1 -0
  66. package/dist/scanner/detectors/core/packageManager.js +74 -0
  67. package/dist/scanner/detectors/core/packageManager.js.map +1 -0
  68. package/dist/scanner/detectors/core/styling.d.ts +12 -0
  69. package/dist/scanner/detectors/core/styling.d.ts.map +1 -0
  70. package/dist/scanner/detectors/core/styling.js +230 -0
  71. package/dist/scanner/detectors/core/styling.js.map +1 -0
  72. package/dist/scanner/detectors/core/testing.d.ts +12 -0
  73. package/dist/scanner/detectors/core/testing.d.ts.map +1 -0
  74. package/dist/scanner/detectors/core/testing.js +190 -0
  75. package/dist/scanner/detectors/core/testing.js.map +1 -0
  76. package/dist/scanner/detectors/data/api.d.ts +12 -0
  77. package/dist/scanner/detectors/data/api.d.ts.map +1 -0
  78. package/dist/scanner/detectors/data/api.js +261 -0
  79. package/dist/scanner/detectors/data/api.js.map +1 -0
  80. package/dist/scanner/detectors/data/database.d.ts +12 -0
  81. package/dist/scanner/detectors/data/database.d.ts.map +1 -0
  82. package/dist/scanner/detectors/data/database.js +213 -0
  83. package/dist/scanner/detectors/data/database.js.map +1 -0
  84. package/dist/scanner/detectors/data/orm.d.ts +12 -0
  85. package/dist/scanner/detectors/data/orm.d.ts.map +1 -0
  86. package/dist/scanner/detectors/data/orm.js +160 -0
  87. package/dist/scanner/detectors/data/orm.js.map +1 -0
  88. package/dist/scanner/detectors/frontend/formHandling.d.ts +12 -0
  89. package/dist/scanner/detectors/frontend/formHandling.d.ts.map +1 -0
  90. package/dist/scanner/detectors/frontend/formHandling.js +211 -0
  91. package/dist/scanner/detectors/frontend/formHandling.js.map +1 -0
  92. package/dist/scanner/detectors/frontend/stateManagement.d.ts +12 -0
  93. package/dist/scanner/detectors/frontend/stateManagement.d.ts.map +1 -0
  94. package/dist/scanner/detectors/frontend/stateManagement.js +221 -0
  95. package/dist/scanner/detectors/frontend/stateManagement.js.map +1 -0
  96. package/dist/scanner/detectors/frontend/uiComponents.d.ts +12 -0
  97. package/dist/scanner/detectors/frontend/uiComponents.d.ts.map +1 -0
  98. package/dist/scanner/detectors/frontend/uiComponents.js +285 -0
  99. package/dist/scanner/detectors/frontend/uiComponents.js.map +1 -0
  100. package/dist/scanner/detectors/infra/deployment.d.ts +12 -0
  101. package/dist/scanner/detectors/infra/deployment.d.ts.map +1 -0
  102. package/dist/scanner/detectors/infra/deployment.js +301 -0
  103. package/dist/scanner/detectors/infra/deployment.js.map +1 -0
  104. package/dist/scanner/detectors/infra/monorepo.d.ts +12 -0
  105. package/dist/scanner/detectors/infra/monorepo.d.ts.map +1 -0
  106. package/dist/scanner/detectors/infra/monorepo.js +219 -0
  107. package/dist/scanner/detectors/infra/monorepo.js.map +1 -0
  108. package/dist/scanner/detectors/mcp/mcpProject.d.ts +12 -0
  109. package/dist/scanner/detectors/mcp/mcpProject.d.ts.map +1 -0
  110. package/dist/scanner/detectors/mcp/mcpProject.js +154 -0
  111. package/dist/scanner/detectors/mcp/mcpProject.js.map +1 -0
  112. package/dist/scanner/detectors/mcp/mcpServers.d.ts +17 -0
  113. package/dist/scanner/detectors/mcp/mcpServers.d.ts.map +1 -0
  114. package/dist/scanner/detectors/mcp/mcpServers.js +193 -0
  115. package/dist/scanner/detectors/mcp/mcpServers.js.map +1 -0
  116. package/dist/scanner/detectors/services/analytics.d.ts +12 -0
  117. package/dist/scanner/detectors/services/analytics.d.ts.map +1 -0
  118. package/dist/scanner/detectors/services/analytics.js +236 -0
  119. package/dist/scanner/detectors/services/analytics.js.map +1 -0
  120. package/dist/scanner/detectors/services/auth.d.ts +12 -0
  121. package/dist/scanner/detectors/services/auth.d.ts.map +1 -0
  122. package/dist/scanner/detectors/services/auth.js +217 -0
  123. package/dist/scanner/detectors/services/auth.js.map +1 -0
  124. package/dist/scanner/detectors/services/email.d.ts +12 -0
  125. package/dist/scanner/detectors/services/email.d.ts.map +1 -0
  126. package/dist/scanner/detectors/services/email.js +211 -0
  127. package/dist/scanner/detectors/services/email.js.map +1 -0
  128. package/dist/scanner/detectors/services/payments.d.ts +12 -0
  129. package/dist/scanner/detectors/services/payments.d.ts.map +1 -0
  130. package/dist/scanner/detectors/services/payments.js +185 -0
  131. package/dist/scanner/detectors/services/payments.js.map +1 -0
  132. package/dist/scanner/detectors/utils.d.ts +160 -0
  133. package/dist/scanner/detectors/utils.d.ts.map +1 -0
  134. package/dist/scanner/detectors/utils.js +222 -0
  135. package/dist/scanner/detectors/utils.js.map +1 -0
  136. package/dist/scanner/index.d.ts +42 -0
  137. package/dist/scanner/index.d.ts.map +1 -0
  138. package/dist/scanner/index.js +282 -0
  139. package/dist/scanner/index.js.map +1 -0
  140. package/dist/scanner/registry.d.ts +43 -0
  141. package/dist/scanner/registry.d.ts.map +1 -0
  142. package/dist/scanner/registry.js +243 -0
  143. package/dist/scanner/registry.js.map +1 -0
  144. package/dist/scanner/types.d.ts +112 -0
  145. package/dist/scanner/types.d.ts.map +1 -0
  146. package/dist/scanner/types.js +6 -0
  147. package/dist/scanner/types.js.map +1 -0
  148. package/dist/templates/config/ralph.config.js.tmpl +38 -0
  149. package/dist/templates/guides/AGENTS.md.tmpl +100 -0
  150. package/dist/templates/guides/FRONTEND.md.tmpl +523 -0
  151. package/dist/templates/guides/PERFORMANCE.md.tmpl +264 -0
  152. package/dist/templates/guides/SECURITY.md.tmpl +100 -0
  153. package/dist/templates/prompts/PROMPT.md.tmpl +77 -0
  154. package/dist/templates/prompts/PROMPT_e2e.md.tmpl +234 -0
  155. package/dist/templates/prompts/PROMPT_feature.md.tmpl +83 -0
  156. package/dist/templates/prompts/PROMPT_review.md.tmpl +167 -0
  157. package/dist/templates/prompts/PROMPT_verify.md.tmpl +72 -0
  158. package/dist/templates/root/.gitignore.tmpl +5 -0
  159. package/dist/templates/root/LEARNINGS.md.tmpl +24 -0
  160. package/dist/templates/root/README.md.tmpl +61 -0
  161. package/dist/templates/scripts/feature-loop.sh.tmpl +267 -0
  162. package/dist/templates/scripts/loop.sh.tmpl +59 -0
  163. package/dist/templates/scripts/ralph-monitor.sh.tmpl +244 -0
  164. package/dist/templates/specs/README.md.tmpl +57 -0
  165. package/dist/templates/specs/_example.md.tmpl +71 -0
  166. package/dist/utils/config.d.ts +95 -0
  167. package/dist/utils/config.d.ts.map +1 -0
  168. package/dist/utils/config.js +148 -0
  169. package/dist/utils/config.js.map +1 -0
  170. package/dist/utils/header.d.ts +5 -0
  171. package/dist/utils/header.d.ts.map +1 -0
  172. package/dist/utils/header.js +15 -0
  173. package/dist/utils/header.js.map +1 -0
  174. package/dist/utils/logger.d.ts +11 -0
  175. package/dist/utils/logger.d.ts.map +1 -0
  176. package/dist/utils/logger.js +24 -0
  177. package/dist/utils/logger.js.map +1 -0
  178. package/package.json +44 -0
  179. package/src/ai/enhancer.ts +350 -0
  180. package/src/ai/index.ts +38 -0
  181. package/src/ai/prompts.ts +217 -0
  182. package/src/ai/providers.ts +136 -0
  183. package/src/cli.ts +255 -0
  184. package/src/commands/init.ts +149 -0
  185. package/src/commands/monitor.ts +412 -0
  186. package/src/commands/new.ts +312 -0
  187. package/src/commands/run.ts +214 -0
  188. package/src/generator/config.ts +116 -0
  189. package/src/generator/index.ts +227 -0
  190. package/src/generator/templates.ts +412 -0
  191. package/src/generator/writer.ts +293 -0
  192. package/src/index.ts +41 -0
  193. package/src/scanner/detectors/core/framework.ts +332 -0
  194. package/src/scanner/detectors/core/packageManager.ts +91 -0
  195. package/src/scanner/detectors/core/styling.ts +261 -0
  196. package/src/scanner/detectors/core/testing.ts +221 -0
  197. package/src/scanner/detectors/data/api.ts +303 -0
  198. package/src/scanner/detectors/data/database.ts +245 -0
  199. package/src/scanner/detectors/data/orm.ts +180 -0
  200. package/src/scanner/detectors/frontend/formHandling.ts +244 -0
  201. package/src/scanner/detectors/frontend/stateManagement.ts +261 -0
  202. package/src/scanner/detectors/frontend/uiComponents.ts +328 -0
  203. package/src/scanner/detectors/infra/deployment.ts +343 -0
  204. package/src/scanner/detectors/infra/monorepo.ts +251 -0
  205. package/src/scanner/detectors/mcp/mcpProject.ts +176 -0
  206. package/src/scanner/detectors/mcp/mcpServers.ts +237 -0
  207. package/src/scanner/detectors/services/analytics.ts +273 -0
  208. package/src/scanner/detectors/services/auth.ts +254 -0
  209. package/src/scanner/detectors/services/email.ts +244 -0
  210. package/src/scanner/detectors/services/payments.ts +213 -0
  211. package/src/scanner/detectors/utils.ts +251 -0
  212. package/src/scanner/index.ts +354 -0
  213. package/src/scanner/registry.ts +301 -0
  214. package/src/scanner/types.ts +152 -0
  215. package/src/templates/config/ralph.config.js.tmpl +38 -0
  216. package/src/templates/guides/AGENTS.md.tmpl +100 -0
  217. package/src/templates/guides/FRONTEND.md.tmpl +523 -0
  218. package/src/templates/guides/PERFORMANCE.md.tmpl +264 -0
  219. package/src/templates/guides/SECURITY.md.tmpl +100 -0
  220. package/src/templates/prompts/PROMPT.md.tmpl +77 -0
  221. package/src/templates/prompts/PROMPT_e2e.md.tmpl +234 -0
  222. package/src/templates/prompts/PROMPT_feature.md.tmpl +83 -0
  223. package/src/templates/prompts/PROMPT_review.md.tmpl +167 -0
  224. package/src/templates/prompts/PROMPT_verify.md.tmpl +72 -0
  225. package/src/templates/root/.gitignore.tmpl +5 -0
  226. package/src/templates/root/LEARNINGS.md.tmpl +24 -0
  227. package/src/templates/root/README.md.tmpl +61 -0
  228. package/src/templates/scripts/feature-loop.sh.tmpl +267 -0
  229. package/src/templates/scripts/loop.sh.tmpl +59 -0
  230. package/src/templates/scripts/ralph-monitor.sh.tmpl +244 -0
  231. package/src/templates/specs/README.md.tmpl +57 -0
  232. package/src/templates/specs/_example.md.tmpl +71 -0
  233. package/src/utils/config.ts +221 -0
  234. package/src/utils/header.ts +15 -0
  235. package/src/utils/logger.ts +28 -0
  236. package/tsconfig.json +19 -0
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Email Provider Detector
3
+ * Detects: Resend, SendGrid, Nodemailer
4
+ */
5
+
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import type { Detector, DetectionResult } from '../../types.js';
9
+
10
+ /**
11
+ * Read and parse package.json from a directory
12
+ */
13
+ function readPackageJson(projectRoot: string): Record<string, unknown> | null {
14
+ const packageJsonPath = join(projectRoot, 'package.json');
15
+ if (!existsSync(packageJsonPath)) {
16
+ return null;
17
+ }
18
+ try {
19
+ const content = readFileSync(packageJsonPath, 'utf-8');
20
+ return JSON.parse(content);
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Get all dependencies from package.json (deps + devDeps)
28
+ */
29
+ function getDependencies(pkg: Record<string, unknown>): Record<string, string> {
30
+ const deps = (pkg.dependencies as Record<string, string>) || {};
31
+ const devDeps = (pkg.devDependencies as Record<string, string>) || {};
32
+ return { ...deps, ...devDeps };
33
+ }
34
+
35
+ /**
36
+ * Find all matching dependencies by pattern
37
+ */
38
+ function findMatchingDeps(deps: Record<string, string>, pattern: string): string[] {
39
+ return Object.keys(deps).filter(name => name.startsWith(pattern));
40
+ }
41
+
42
+ /**
43
+ * Detect Resend
44
+ */
45
+ function detectResend(deps: Record<string, string>): DetectionResult | null {
46
+ const evidence: string[] = [];
47
+ let confidence = 0;
48
+
49
+ if (deps.resend) {
50
+ evidence.push(`resend@${deps.resend} in dependencies`);
51
+ confidence += 80;
52
+ }
53
+
54
+ // Check for react-email (often used with Resend)
55
+ if (deps['react-email'] || deps['@react-email/components']) {
56
+ evidence.push('React Email detected (commonly paired with Resend)');
57
+ confidence += 10;
58
+ }
59
+
60
+ if (confidence === 0) return null;
61
+
62
+ return {
63
+ name: 'Resend',
64
+ version: deps.resend,
65
+ confidence: Math.min(confidence, 100),
66
+ evidence,
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Detect SendGrid
72
+ */
73
+ function detectSendGrid(deps: Record<string, string>): DetectionResult | null {
74
+ const evidence: string[] = [];
75
+ let confidence = 0;
76
+
77
+ // Check for @sendgrid/mail
78
+ if (deps['@sendgrid/mail']) {
79
+ evidence.push(`@sendgrid/mail@${deps['@sendgrid/mail']} in dependencies`);
80
+ confidence += 80;
81
+ }
82
+
83
+ // Check for other SendGrid packages
84
+ const sendgridPackages = findMatchingDeps(deps, '@sendgrid/');
85
+ if (sendgridPackages.length > 1) {
86
+ evidence.push(`Multiple SendGrid packages found: ${sendgridPackages.join(', ')}`);
87
+ confidence += 10;
88
+ }
89
+
90
+ if (confidence === 0) return null;
91
+
92
+ return {
93
+ name: 'SendGrid',
94
+ version: deps['@sendgrid/mail'],
95
+ confidence: Math.min(confidence, 100),
96
+ evidence,
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Detect Nodemailer
102
+ */
103
+ function detectNodemailer(deps: Record<string, string>): DetectionResult | null {
104
+ const evidence: string[] = [];
105
+ let confidence = 0;
106
+
107
+ if (deps.nodemailer) {
108
+ evidence.push(`nodemailer@${deps.nodemailer} in dependencies`);
109
+ confidence += 80;
110
+ }
111
+
112
+ // Check for nodemailer types
113
+ if (deps['@types/nodemailer']) {
114
+ evidence.push('@types/nodemailer found');
115
+ confidence += 10;
116
+ }
117
+
118
+ if (confidence === 0) return null;
119
+
120
+ return {
121
+ name: 'Nodemailer',
122
+ version: deps.nodemailer,
123
+ confidence: Math.min(confidence, 100),
124
+ evidence,
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Detect Postmark
130
+ */
131
+ function detectPostmark(deps: Record<string, string>): DetectionResult | null {
132
+ const evidence: string[] = [];
133
+ let confidence = 0;
134
+
135
+ if (deps.postmark) {
136
+ evidence.push(`postmark@${deps.postmark} in dependencies`);
137
+ confidence += 80;
138
+ }
139
+
140
+ if (confidence === 0) return null;
141
+
142
+ return {
143
+ name: 'Postmark',
144
+ version: deps.postmark,
145
+ confidence: Math.min(confidence, 100),
146
+ evidence,
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Detect Mailgun
152
+ */
153
+ function detectMailgun(deps: Record<string, string>): DetectionResult | null {
154
+ const evidence: string[] = [];
155
+ let confidence = 0;
156
+
157
+ if (deps['mailgun.js'] || deps['mailgun-js']) {
158
+ const version = deps['mailgun.js'] || deps['mailgun-js'];
159
+ evidence.push(`Mailgun package@${version} in dependencies`);
160
+ confidence += 80;
161
+ }
162
+
163
+ if (confidence === 0) return null;
164
+
165
+ return {
166
+ name: 'Mailgun',
167
+ version: deps['mailgun.js'] || deps['mailgun-js'],
168
+ confidence: Math.min(confidence, 100),
169
+ evidence,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Detect AWS SES
175
+ */
176
+ function detectAWSSES(deps: Record<string, string>): DetectionResult | null {
177
+ const evidence: string[] = [];
178
+ let confidence = 0;
179
+
180
+ // Check for AWS SDK v3 SES client
181
+ if (deps['@aws-sdk/client-ses']) {
182
+ evidence.push(`@aws-sdk/client-ses@${deps['@aws-sdk/client-ses']} in dependencies`);
183
+ confidence += 80;
184
+ }
185
+
186
+ // Check for AWS SDK v2 (legacy, but still used)
187
+ if (deps['aws-sdk'] && !deps['@aws-sdk/client-ses']) {
188
+ evidence.push('aws-sdk found (may include SES)');
189
+ confidence += 40; // Lower confidence since we can't confirm SES usage
190
+ }
191
+
192
+ if (confidence === 0) return null;
193
+
194
+ return {
195
+ name: 'AWS SES',
196
+ version: deps['@aws-sdk/client-ses'],
197
+ confidence: Math.min(confidence, 100),
198
+ evidence,
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Email provider detector
204
+ * Returns the primary email provider detected
205
+ */
206
+ export const emailDetector: Detector = {
207
+ category: 'email',
208
+ name: 'Email Provider Detector',
209
+
210
+ async detect(projectRoot: string): Promise<DetectionResult | null> {
211
+ const pkg = readPackageJson(projectRoot);
212
+ if (!pkg) {
213
+ return null;
214
+ }
215
+
216
+ const deps = getDependencies(pkg);
217
+
218
+ // Priority order based on modern usage patterns
219
+ const detectors = [
220
+ () => detectResend(deps),
221
+ () => detectSendGrid(deps),
222
+ () => detectPostmark(deps),
223
+ () => detectMailgun(deps),
224
+ () => detectAWSSES(deps),
225
+ () => detectNodemailer(deps), // Last as it's often a transport, not a provider
226
+ ];
227
+
228
+ // Find the highest confidence result
229
+ let bestResult: DetectionResult | null = null;
230
+ let bestConfidence = 0;
231
+
232
+ for (const detector of detectors) {
233
+ const result = detector();
234
+ if (result && result.confidence > bestConfidence) {
235
+ bestResult = result;
236
+ bestConfidence = result.confidence;
237
+ }
238
+ }
239
+
240
+ return bestResult && bestResult.confidence >= 40 ? bestResult : null;
241
+ },
242
+ };
243
+
244
+ export default emailDetector;
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Payments Provider Detector
3
+ * Detects: Stripe, Lemon Squeezy, Paddle
4
+ */
5
+
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import type { Detector, DetectionResult } from '../../types.js';
9
+
10
+ /**
11
+ * Read and parse package.json from a directory
12
+ */
13
+ function readPackageJson(projectRoot: string): Record<string, unknown> | null {
14
+ const packageJsonPath = join(projectRoot, 'package.json');
15
+ if (!existsSync(packageJsonPath)) {
16
+ return null;
17
+ }
18
+ try {
19
+ const content = readFileSync(packageJsonPath, 'utf-8');
20
+ return JSON.parse(content);
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Get all dependencies from package.json (deps + devDeps)
28
+ */
29
+ function getDependencies(pkg: Record<string, unknown>): Record<string, string> {
30
+ const deps = (pkg.dependencies as Record<string, string>) || {};
31
+ const devDeps = (pkg.devDependencies as Record<string, string>) || {};
32
+ return { ...deps, ...devDeps };
33
+ }
34
+
35
+ /**
36
+ * Find all matching dependencies by pattern
37
+ */
38
+ function findMatchingDeps(deps: Record<string, string>, pattern: string): string[] {
39
+ return Object.keys(deps).filter(name => name.startsWith(pattern));
40
+ }
41
+
42
+ /**
43
+ * Detect Stripe
44
+ */
45
+ function detectStripe(deps: Record<string, string>): DetectionResult | null {
46
+ const evidence: string[] = [];
47
+ let confidence = 0;
48
+ let variant: string | undefined;
49
+
50
+ // Check for stripe (Node.js SDK)
51
+ if (deps.stripe) {
52
+ evidence.push(`stripe@${deps.stripe} in dependencies`);
53
+ confidence += 70;
54
+ variant = 'server';
55
+ }
56
+
57
+ // Check for @stripe/* packages
58
+ const stripePackages = findMatchingDeps(deps, '@stripe/');
59
+ if (stripePackages.length > 0) {
60
+ evidence.push(`Stripe packages found: ${stripePackages.join(', ')}`);
61
+ confidence += 20;
62
+
63
+ if (deps['@stripe/stripe-js']) {
64
+ variant = variant === 'server' ? 'full-stack' : 'client';
65
+ }
66
+ if (deps['@stripe/react-stripe-js']) {
67
+ evidence.push('React Stripe.js integration detected');
68
+ confidence += 10;
69
+ }
70
+ }
71
+
72
+ if (confidence === 0) return null;
73
+
74
+ return {
75
+ name: 'Stripe',
76
+ version: deps.stripe || deps['@stripe/stripe-js'],
77
+ variant,
78
+ confidence: Math.min(confidence, 100),
79
+ evidence,
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Detect Lemon Squeezy
85
+ */
86
+ function detectLemonSqueezy(deps: Record<string, string>): DetectionResult | null {
87
+ const evidence: string[] = [];
88
+ let confidence = 0;
89
+
90
+ // Check for @lemonsqueezy/* packages
91
+ const lsPackages = findMatchingDeps(deps, '@lemonsqueezy/');
92
+ if (lsPackages.length > 0) {
93
+ evidence.push(`Lemon Squeezy packages found: ${lsPackages.join(', ')}`);
94
+ confidence += 80;
95
+ }
96
+
97
+ // Check for lemonsqueezy.js
98
+ if (deps['lemonsqueezy.js']) {
99
+ evidence.push(`lemonsqueezy.js@${deps['lemonsqueezy.js']} in dependencies`);
100
+ confidence += 80;
101
+ }
102
+
103
+ if (confidence === 0) return null;
104
+
105
+ return {
106
+ name: 'Lemon Squeezy',
107
+ version: deps['@lemonsqueezy/lemonsqueezy.js'] || deps['lemonsqueezy.js'],
108
+ confidence: Math.min(confidence, 100),
109
+ evidence,
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Detect Paddle
115
+ */
116
+ function detectPaddle(deps: Record<string, string>): DetectionResult | null {
117
+ const evidence: string[] = [];
118
+ let confidence = 0;
119
+
120
+ // Check for @paddle/* packages
121
+ const paddlePackages = findMatchingDeps(deps, '@paddle/');
122
+ if (paddlePackages.length > 0) {
123
+ evidence.push(`Paddle packages found: ${paddlePackages.join(', ')}`);
124
+ confidence += 80;
125
+ }
126
+
127
+ // Check for paddle-sdk
128
+ if (deps['paddle-sdk']) {
129
+ evidence.push(`paddle-sdk@${deps['paddle-sdk']} in dependencies`);
130
+ confidence += 70;
131
+ }
132
+
133
+ if (confidence === 0) return null;
134
+
135
+ return {
136
+ name: 'Paddle',
137
+ version: deps['@paddle/paddle-js'] || deps['paddle-sdk'],
138
+ confidence: Math.min(confidence, 100),
139
+ evidence,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Detect PayPal
145
+ */
146
+ function detectPayPal(deps: Record<string, string>): DetectionResult | null {
147
+ const evidence: string[] = [];
148
+ let confidence = 0;
149
+
150
+ // Check for @paypal/* packages
151
+ const paypalPackages = findMatchingDeps(deps, '@paypal/');
152
+ if (paypalPackages.length > 0) {
153
+ evidence.push(`PayPal packages found: ${paypalPackages.join(', ')}`);
154
+ confidence += 80;
155
+ }
156
+
157
+ // Check for paypal-rest-sdk
158
+ if (deps['paypal-rest-sdk']) {
159
+ evidence.push(`paypal-rest-sdk@${deps['paypal-rest-sdk']} in dependencies`);
160
+ confidence += 70;
161
+ }
162
+
163
+ if (confidence === 0) return null;
164
+
165
+ return {
166
+ name: 'PayPal',
167
+ version: deps['@paypal/paypal-js'] || deps['paypal-rest-sdk'],
168
+ confidence: Math.min(confidence, 100),
169
+ evidence,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Payments provider detector
175
+ * Returns the primary payment provider detected
176
+ */
177
+ export const paymentsDetector: Detector = {
178
+ category: 'payments',
179
+ name: 'Payments Provider Detector',
180
+
181
+ async detect(projectRoot: string): Promise<DetectionResult | null> {
182
+ const pkg = readPackageJson(projectRoot);
183
+ if (!pkg) {
184
+ return null;
185
+ }
186
+
187
+ const deps = getDependencies(pkg);
188
+
189
+ // Priority order based on popularity
190
+ const detectors = [
191
+ () => detectStripe(deps),
192
+ () => detectLemonSqueezy(deps),
193
+ () => detectPaddle(deps),
194
+ () => detectPayPal(deps),
195
+ ];
196
+
197
+ // Find the highest confidence result
198
+ let bestResult: DetectionResult | null = null;
199
+ let bestConfidence = 0;
200
+
201
+ for (const detector of detectors) {
202
+ const result = detector();
203
+ if (result && result.confidence > bestConfidence) {
204
+ bestResult = result;
205
+ bestConfidence = result.confidence;
206
+ }
207
+ }
208
+
209
+ return bestResult && bestResult.confidence >= 40 ? bestResult : null;
210
+ },
211
+ };
212
+
213
+ export default paymentsDetector;
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Shared Utilities for Detectors
3
+ * Common functions used across multiple detector files
4
+ */
5
+
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+
9
+ /**
10
+ * Package.json type definition
11
+ */
12
+ export type PackageJson = Record<string, unknown>;
13
+
14
+ /**
15
+ * Dependencies map type
16
+ */
17
+ export type DependencyMap = Record<string, string>;
18
+
19
+ /**
20
+ * Read and parse package.json from a directory
21
+ *
22
+ * @param projectRoot - The root directory of the project
23
+ * @returns Parsed package.json object or null if not found/invalid
24
+ *
25
+ * @example
26
+ * const pkg = readPackageJson('/path/to/project');
27
+ * if (pkg) {
28
+ * console.log(pkg.name);
29
+ * }
30
+ */
31
+ export function readPackageJson(projectRoot: string): PackageJson | null {
32
+ const packageJsonPath = join(projectRoot, 'package.json');
33
+ if (!existsSync(packageJsonPath)) {
34
+ return null;
35
+ }
36
+ try {
37
+ const content = readFileSync(packageJsonPath, 'utf-8');
38
+ return JSON.parse(content);
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Get all dependencies from package.json (deps + devDeps combined)
46
+ *
47
+ * @param pkg - Parsed package.json object
48
+ * @returns Combined dependencies and devDependencies map
49
+ *
50
+ * @example
51
+ * const pkg = readPackageJson(projectRoot);
52
+ * const deps = getDependencies(pkg);
53
+ * if (deps.react) {
54
+ * console.log(`React version: ${deps.react}`);
55
+ * }
56
+ */
57
+ export function getDependencies(pkg: PackageJson | null): DependencyMap {
58
+ if (!pkg) return {};
59
+ const deps = (pkg.dependencies as DependencyMap) || {};
60
+ const devDeps = (pkg.devDependencies as DependencyMap) || {};
61
+ return { ...deps, ...devDeps };
62
+ }
63
+
64
+ /**
65
+ * Find a config file with various possible extensions
66
+ *
67
+ * @param projectRoot - The root directory of the project
68
+ * @param baseName - The base name of the config file (e.g., 'next.config')
69
+ * @param extensions - Array of extensions to try (e.g., ['.js', '.ts', '.mjs'])
70
+ * @returns The full filename if found, or null
71
+ *
72
+ * @example
73
+ * const configFile = findConfigFile(projectRoot, 'next.config', ['.js', '.mjs', '.ts']);
74
+ * if (configFile) {
75
+ * console.log(`Found: ${configFile}`);
76
+ * }
77
+ */
78
+ export function findConfigFile(
79
+ projectRoot: string,
80
+ baseName: string,
81
+ extensions: string[]
82
+ ): string | null {
83
+ for (const ext of extensions) {
84
+ const filePath = join(projectRoot, `${baseName}${ext}`);
85
+ if (existsSync(filePath)) {
86
+ return `${baseName}${ext}`;
87
+ }
88
+ }
89
+ return null;
90
+ }
91
+
92
+ /**
93
+ * Check if a directory exists
94
+ *
95
+ * @param projectRoot - The root directory of the project
96
+ * @param dirName - The directory name to check
97
+ * @returns True if the directory exists
98
+ *
99
+ * @example
100
+ * if (checkDirectoryExists(projectRoot, 'src')) {
101
+ * console.log('Source directory found');
102
+ * }
103
+ */
104
+ export function checkDirectoryExists(projectRoot: string, dirName: string): boolean {
105
+ return existsSync(join(projectRoot, dirName));
106
+ }
107
+
108
+ /**
109
+ * Check if a file exists
110
+ *
111
+ * @param projectRoot - The root directory of the project
112
+ * @param fileName - The file name to check
113
+ * @returns True if the file exists
114
+ *
115
+ * @example
116
+ * if (checkFileExists(projectRoot, 'vercel.json')) {
117
+ * console.log('Vercel config found');
118
+ * }
119
+ */
120
+ export function checkFileExists(projectRoot: string, fileName: string): boolean {
121
+ return existsSync(join(projectRoot, fileName));
122
+ }
123
+
124
+ /**
125
+ * Find all dependencies matching a prefix pattern
126
+ *
127
+ * @param deps - Dependencies map
128
+ * @param pattern - Prefix pattern to match (e.g., '@supabase/')
129
+ * @returns Array of matching dependency names
130
+ *
131
+ * @example
132
+ * const supabasePackages = findMatchingDeps(deps, '@supabase/');
133
+ * // Returns: ['@supabase/supabase-js', '@supabase/ssr', ...]
134
+ */
135
+ export function findMatchingDeps(deps: DependencyMap, pattern: string): string[] {
136
+ return Object.keys(deps).filter(
137
+ (name) => name.startsWith(pattern) || name === pattern
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Check if any dependency matches a pattern and return its version
143
+ *
144
+ * @param deps - Dependencies map
145
+ * @param pattern - Prefix pattern to match
146
+ * @returns Version string if found, undefined otherwise
147
+ *
148
+ * @example
149
+ * const version = hasDependencyPattern(deps, '@supabase/');
150
+ * if (version) {
151
+ * console.log(`Found supabase package: ${version}`);
152
+ * }
153
+ */
154
+ export function hasDependencyPattern(
155
+ deps: DependencyMap,
156
+ pattern: string
157
+ ): string | undefined {
158
+ for (const [name, version] of Object.entries(deps)) {
159
+ if (name.startsWith(pattern) || name === pattern) {
160
+ return version;
161
+ }
162
+ }
163
+ return undefined;
164
+ }
165
+
166
+ /**
167
+ * Read file content safely
168
+ *
169
+ * @param filePath - Full path to the file
170
+ * @returns File content as string, or null if read fails
171
+ *
172
+ * @example
173
+ * const content = readFileSafe('/path/to/config.json');
174
+ * if (content) {
175
+ * const config = JSON.parse(content);
176
+ * }
177
+ */
178
+ export function readFileSafe(filePath: string): string | null {
179
+ try {
180
+ if (!existsSync(filePath)) {
181
+ return null;
182
+ }
183
+ return readFileSync(filePath, 'utf-8');
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Parse JSON file safely
191
+ *
192
+ * @param projectRoot - The root directory of the project
193
+ * @param fileName - The JSON file name
194
+ * @returns Parsed JSON object or null if invalid
195
+ *
196
+ * @example
197
+ * const config = parseJsonFile(projectRoot, 'tsconfig.json');
198
+ * if (config?.compilerOptions) {
199
+ * console.log('TypeScript config found');
200
+ * }
201
+ */
202
+ export function parseJsonFile(
203
+ projectRoot: string,
204
+ fileName: string
205
+ ): Record<string, unknown> | null {
206
+ try {
207
+ const filePath = join(projectRoot, fileName);
208
+ if (!existsSync(filePath)) {
209
+ return null;
210
+ }
211
+ const content = readFileSync(filePath, 'utf-8');
212
+ return JSON.parse(content);
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Common config file extensions
220
+ */
221
+ export const CONFIG_EXTENSIONS = {
222
+ /** JavaScript/TypeScript config extensions */
223
+ JS_TS: ['.js', '.ts', '.mjs', '.cjs'],
224
+ /** JSON/YAML config extensions */
225
+ DATA: ['.json', '.yaml', '.yml', '.toml'],
226
+ /** All common config extensions */
227
+ ALL: ['.js', '.ts', '.mjs', '.cjs', '.json', '.yaml', '.yml', '.toml'],
228
+ };
229
+
230
+ /**
231
+ * Get npm scripts from package.json
232
+ *
233
+ * @param pkg - Parsed package.json object
234
+ * @returns Scripts object or empty object
235
+ */
236
+ export function getScripts(pkg: PackageJson | null): Record<string, string> {
237
+ if (!pkg) return {};
238
+ return (pkg.scripts as Record<string, string>) || {};
239
+ }
240
+
241
+ /**
242
+ * Check if a specific npm script exists
243
+ *
244
+ * @param pkg - Parsed package.json object
245
+ * @param scriptName - Name of the script to check
246
+ * @returns True if the script exists
247
+ */
248
+ export function hasScript(pkg: PackageJson | null, scriptName: string): boolean {
249
+ const scripts = getScripts(pkg);
250
+ return scriptName in scripts;
251
+ }