@l4yercak3/cli 1.2.16 → 1.2.18

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.
@@ -16,6 +16,7 @@ const eventsDomain = require('./domains/events');
16
16
  const formsDomain = require('./domains/forms');
17
17
  const codegenDomain = require('./domains/codegen');
18
18
  const applicationsDomain = require('./domains/applications');
19
+ const benefitsDomain = require('./domains/benefits');
19
20
 
20
21
  /**
21
22
  * @typedef {Object} ToolDefinition
@@ -44,6 +45,7 @@ const toolDomains = [
44
45
  crmDomain,
45
46
  eventsDomain,
46
47
  formsDomain,
48
+ benefitsDomain,
47
49
  codegenDomain,
48
50
  ];
49
51
 
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Tests for Database Detector
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ jest.mock('fs');
9
+
10
+ const databaseDetector = require('../src/detectors/database-detector');
11
+
12
+ describe('DatabaseDetector', () => {
13
+ const mockProjectPath = '/test/project';
14
+
15
+ beforeEach(() => {
16
+ jest.clearAllMocks();
17
+ fs.existsSync.mockReturnValue(false);
18
+ });
19
+
20
+ describe('detect', () => {
21
+ it('returns hasDatabase false when no database detected', () => {
22
+ fs.existsSync.mockReturnValue(false);
23
+
24
+ const result = databaseDetector.detect(mockProjectPath);
25
+
26
+ expect(result.hasDatabase).toBe(false);
27
+ expect(result.detections).toHaveLength(0);
28
+ expect(result.primary).toBeNull();
29
+ });
30
+
31
+ it('detects Convex from directory', () => {
32
+ fs.existsSync.mockImplementation((filePath) => {
33
+ return filePath === path.join(mockProjectPath, 'convex') ||
34
+ filePath === path.join(mockProjectPath, 'convex', 'schema.ts');
35
+ });
36
+
37
+ const result = databaseDetector.detect(mockProjectPath);
38
+
39
+ expect(result.hasDatabase).toBe(true);
40
+ expect(result.primary.type).toBe('convex');
41
+ expect(result.primary.confidence).toBe('high');
42
+ expect(result.primary.hasSchema).toBe(true);
43
+ });
44
+
45
+ it('detects Supabase from directory', () => {
46
+ fs.existsSync.mockImplementation((filePath) => {
47
+ return filePath === path.join(mockProjectPath, 'supabase') ||
48
+ filePath === path.join(mockProjectPath, 'supabase', 'migrations');
49
+ });
50
+
51
+ const result = databaseDetector.detect(mockProjectPath);
52
+
53
+ expect(result.hasDatabase).toBe(true);
54
+ expect(result.primary.type).toBe('supabase');
55
+ expect(result.primary.confidence).toBe('high');
56
+ expect(result.primary.hasMigrations).toBe(true);
57
+ });
58
+
59
+ it('detects Prisma from directory', () => {
60
+ fs.existsSync.mockImplementation((filePath) => {
61
+ return filePath === path.join(mockProjectPath, 'prisma') ||
62
+ filePath === path.join(mockProjectPath, 'prisma', 'schema.prisma');
63
+ });
64
+
65
+ const result = databaseDetector.detect(mockProjectPath);
66
+
67
+ expect(result.hasDatabase).toBe(true);
68
+ expect(result.primary.type).toBe('prisma');
69
+ expect(result.primary.confidence).toBe('high');
70
+ expect(result.primary.hasSchema).toBe(true);
71
+ });
72
+
73
+ it('detects Drizzle from config file', () => {
74
+ fs.existsSync.mockImplementation((filePath) => {
75
+ return filePath === path.join(mockProjectPath, 'drizzle.config.ts');
76
+ });
77
+
78
+ const result = databaseDetector.detect(mockProjectPath);
79
+
80
+ expect(result.hasDatabase).toBe(true);
81
+ expect(result.primary.type).toBe('drizzle');
82
+ expect(result.primary.confidence).toBe('high');
83
+ });
84
+
85
+ it('detects database from package.json dependencies', () => {
86
+ fs.existsSync.mockImplementation((filePath) => {
87
+ return filePath === path.join(mockProjectPath, 'package.json');
88
+ });
89
+
90
+ fs.readFileSync.mockReturnValue(JSON.stringify({
91
+ dependencies: {
92
+ 'convex': '^1.0.0',
93
+ },
94
+ }));
95
+
96
+ const result = databaseDetector.detect(mockProjectPath);
97
+
98
+ expect(result.hasDatabase).toBe(true);
99
+ expect(result.primary.type).toBe('convex');
100
+ expect(result.primary.confidence).toBe('medium');
101
+ expect(result.primary.source).toBe('package.json');
102
+ });
103
+
104
+ it('detects Supabase from package.json', () => {
105
+ fs.existsSync.mockImplementation((filePath) => {
106
+ return filePath === path.join(mockProjectPath, 'package.json');
107
+ });
108
+
109
+ fs.readFileSync.mockReturnValue(JSON.stringify({
110
+ dependencies: {
111
+ '@supabase/supabase-js': '^2.0.0',
112
+ },
113
+ }));
114
+
115
+ const result = databaseDetector.detect(mockProjectPath);
116
+
117
+ expect(result.hasDatabase).toBe(true);
118
+ expect(result.primary.type).toBe('supabase');
119
+ });
120
+
121
+ it('detects MongoDB/Mongoose from package.json', () => {
122
+ fs.existsSync.mockImplementation((filePath) => {
123
+ return filePath === path.join(mockProjectPath, 'package.json');
124
+ });
125
+
126
+ fs.readFileSync.mockReturnValue(JSON.stringify({
127
+ dependencies: {
128
+ 'mongoose': '^7.0.0',
129
+ },
130
+ }));
131
+
132
+ const result = databaseDetector.detect(mockProjectPath);
133
+
134
+ expect(result.hasDatabase).toBe(true);
135
+ expect(result.primary.type).toBe('mongodb');
136
+ expect(result.primary.details.client).toBe('mongoose');
137
+ });
138
+
139
+ it('detects Firebase from package.json', () => {
140
+ fs.existsSync.mockImplementation((filePath) => {
141
+ return filePath === path.join(mockProjectPath, 'package.json');
142
+ });
143
+
144
+ fs.readFileSync.mockReturnValue(JSON.stringify({
145
+ dependencies: {
146
+ 'firebase': '^9.0.0',
147
+ },
148
+ }));
149
+
150
+ const result = databaseDetector.detect(mockProjectPath);
151
+
152
+ expect(result.hasDatabase).toBe(true);
153
+ expect(result.primary.type).toBe('firebase');
154
+ });
155
+
156
+ it('prioritizes high confidence detections', () => {
157
+ // Both directory and package.json detected
158
+ fs.existsSync.mockImplementation((filePath) => {
159
+ return filePath === path.join(mockProjectPath, 'convex') ||
160
+ filePath === path.join(mockProjectPath, 'package.json');
161
+ });
162
+
163
+ fs.readFileSync.mockReturnValue(JSON.stringify({
164
+ dependencies: {
165
+ 'mongoose': '^7.0.0',
166
+ },
167
+ }));
168
+
169
+ const result = databaseDetector.detect(mockProjectPath);
170
+
171
+ expect(result.hasDatabase).toBe(true);
172
+ // Convex directory detection should be primary (high confidence)
173
+ expect(result.primary.type).toBe('convex');
174
+ expect(result.primary.confidence).toBe('high');
175
+ // MongoDB should also be in detections
176
+ expect(result.detections.some(d => d.type === 'mongodb')).toBe(true);
177
+ });
178
+
179
+ it('returns multiple detections when multiple databases found', () => {
180
+ fs.existsSync.mockImplementation((filePath) => {
181
+ return filePath === path.join(mockProjectPath, 'package.json');
182
+ });
183
+
184
+ fs.readFileSync.mockReturnValue(JSON.stringify({
185
+ dependencies: {
186
+ 'convex': '^1.0.0',
187
+ 'mongoose': '^7.0.0',
188
+ },
189
+ }));
190
+
191
+ const result = databaseDetector.detect(mockProjectPath);
192
+
193
+ expect(result.hasDatabase).toBe(true);
194
+ expect(result.detections.length).toBeGreaterThan(1);
195
+ });
196
+
197
+ it('handles malformed package.json gracefully', () => {
198
+ fs.existsSync.mockImplementation((filePath) => {
199
+ return filePath === path.join(mockProjectPath, 'package.json');
200
+ });
201
+
202
+ fs.readFileSync.mockReturnValue('not valid json');
203
+
204
+ const result = databaseDetector.detect(mockProjectPath);
205
+
206
+ expect(result.hasDatabase).toBe(false);
207
+ });
208
+
209
+ it('uses current directory as default', () => {
210
+ const originalCwd = process.cwd;
211
+ process.cwd = jest.fn().mockReturnValue(mockProjectPath);
212
+
213
+ fs.existsSync.mockReturnValue(false);
214
+
215
+ const result = databaseDetector.detect();
216
+
217
+ expect(result.hasDatabase).toBe(false);
218
+ process.cwd = originalCwd;
219
+ });
220
+ });
221
+ });
@@ -27,7 +27,7 @@ describe('FileGenerator', () => {
27
27
  fs.readFileSync.mockReturnValue('');
28
28
  });
29
29
 
30
- describe('generate', () => {
30
+ describe('legacy generate (backward compatibility)', () => {
31
31
  it('returns results object with expected structure', async () => {
32
32
  const options = {
33
33
  projectPath: mockProjectPath,
@@ -38,6 +38,7 @@ describe('FileGenerator', () => {
38
38
  oauthProviders: [],
39
39
  isTypeScript: false,
40
40
  routerType: 'app',
41
+ integrationPath: 'legacy', // Use legacy path for backward compatibility tests
41
42
  };
42
43
 
43
44
  const result = await FileGenerator.generate(options);
@@ -58,6 +59,7 @@ describe('FileGenerator', () => {
58
59
  features: ['crm'],
59
60
  oauthProviders: [],
60
61
  isTypeScript: false,
62
+ integrationPath: 'legacy',
61
63
  };
62
64
 
63
65
  const result = await FileGenerator.generate(options);
@@ -75,6 +77,7 @@ describe('FileGenerator', () => {
75
77
  features: [],
76
78
  oauthProviders: [],
77
79
  isTypeScript: false,
80
+ integrationPath: 'legacy',
78
81
  };
79
82
 
80
83
  const result = await FileGenerator.generate(options);
@@ -90,6 +93,7 @@ describe('FileGenerator', () => {
90
93
  organizationId: 'org-123',
91
94
  features: [],
92
95
  oauthProviders: [],
96
+ integrationPath: 'legacy',
93
97
  };
94
98
 
95
99
  const result = await FileGenerator.generate(options);
@@ -108,7 +112,8 @@ describe('FileGenerator', () => {
108
112
  oauthProviders: ['google'],
109
113
  isTypeScript: false,
110
114
  routerType: 'app',
111
- frameworkType: 'nextjs', // NextAuth is Next.js only
115
+ frameworkType: 'nextjs',
116
+ integrationPath: 'legacy',
112
117
  };
113
118
 
114
119
  const result = await FileGenerator.generate(options);
@@ -126,6 +131,7 @@ describe('FileGenerator', () => {
126
131
  oauthProviders: ['google'],
127
132
  isTypeScript: true,
128
133
  frameworkType: 'expo',
134
+ integrationPath: 'legacy',
129
135
  };
130
136
 
131
137
  const result = await FileGenerator.generate(options);
@@ -143,6 +149,7 @@ describe('FileGenerator', () => {
143
149
  oauthProviders: ['google'],
144
150
  isTypeScript: false,
145
151
  routerType: 'app',
152
+ integrationPath: 'legacy',
146
153
  };
147
154
 
148
155
  const result = await FileGenerator.generate(options);
@@ -160,6 +167,7 @@ describe('FileGenerator', () => {
160
167
  oauthProviders: null,
161
168
  isTypeScript: false,
162
169
  routerType: 'app',
170
+ integrationPath: 'legacy',
163
171
  };
164
172
 
165
173
  const result = await FileGenerator.generate(options);
@@ -177,6 +185,7 @@ describe('FileGenerator', () => {
177
185
  oauthProviders: ['google'],
178
186
  productionDomain: 'example.com',
179
187
  appName: 'Test App',
188
+ integrationPath: 'legacy',
180
189
  };
181
190
 
182
191
  const result = await FileGenerator.generate(options);
@@ -193,6 +202,7 @@ describe('FileGenerator', () => {
193
202
  organizationId: 'org-123',
194
203
  features: ['crm'],
195
204
  oauthProviders: [],
205
+ integrationPath: 'legacy',
196
206
  };
197
207
 
198
208
  const result = await FileGenerator.generate(options);
@@ -208,11 +218,11 @@ describe('FileGenerator', () => {
208
218
  organizationId: 'org-123',
209
219
  features: [],
210
220
  oauthProviders: [],
221
+ integrationPath: 'legacy',
211
222
  };
212
223
 
213
224
  const result = await FileGenerator.generate(options);
214
225
 
215
- // gitignore generator returns path or null depending on if update needed
216
226
  expect(result).toHaveProperty('gitignore');
217
227
  });
218
228
 
@@ -229,6 +239,7 @@ describe('FileGenerator', () => {
229
239
  productionDomain: 'example.com',
230
240
  appName: 'Full App',
231
241
  frameworkType: 'nextjs',
242
+ integrationPath: 'legacy',
232
243
  };
233
244
 
234
245
  const result = await FileGenerator.generate(options);
@@ -239,4 +250,205 @@ describe('FileGenerator', () => {
239
250
  expect(result.oauthGuide).not.toBeNull();
240
251
  });
241
252
  });
253
+
254
+ describe('api-only integration path', () => {
255
+ it('returns results object with expected structure', async () => {
256
+ const options = {
257
+ projectPath: mockProjectPath,
258
+ apiKey: 'test-key',
259
+ backendUrl: 'https://backend.test.com',
260
+ organizationId: 'org-123',
261
+ features: ['crm'],
262
+ oauthProviders: [],
263
+ isTypeScript: true,
264
+ integrationPath: 'api-only',
265
+ };
266
+
267
+ const result = await FileGenerator.generate(options);
268
+
269
+ expect(result).toHaveProperty('apiClient');
270
+ expect(result).toHaveProperty('types');
271
+ expect(result).toHaveProperty('webhooks');
272
+ expect(result).toHaveProperty('index');
273
+ expect(result).toHaveProperty('envFile');
274
+ expect(result).toHaveProperty('gitignore');
275
+ });
276
+
277
+ it('generates typed client file', async () => {
278
+ const options = {
279
+ projectPath: mockProjectPath,
280
+ apiKey: 'test-key',
281
+ backendUrl: 'https://backend.test.com',
282
+ organizationId: 'org-123',
283
+ features: ['crm'],
284
+ oauthProviders: [],
285
+ isTypeScript: true,
286
+ integrationPath: 'api-only',
287
+ };
288
+
289
+ const result = await FileGenerator.generate(options);
290
+
291
+ expect(result.apiClient).not.toBeNull();
292
+ expect(result.apiClient).toContain('client.ts');
293
+ });
294
+
295
+ it('generates types file for TypeScript projects', async () => {
296
+ const options = {
297
+ projectPath: mockProjectPath,
298
+ apiKey: 'test-key',
299
+ backendUrl: 'https://backend.test.com',
300
+ organizationId: 'org-123',
301
+ features: ['crm'],
302
+ oauthProviders: [],
303
+ isTypeScript: true,
304
+ integrationPath: 'api-only',
305
+ };
306
+
307
+ const result = await FileGenerator.generate(options);
308
+
309
+ expect(result.types).not.toBeNull();
310
+ expect(result.types).toContain('types.ts');
311
+ });
312
+
313
+ it('does not generate types file for JavaScript projects', async () => {
314
+ const options = {
315
+ projectPath: mockProjectPath,
316
+ apiKey: 'test-key',
317
+ backendUrl: 'https://backend.test.com',
318
+ organizationId: 'org-123',
319
+ features: ['crm'],
320
+ oauthProviders: [],
321
+ isTypeScript: false,
322
+ integrationPath: 'api-only',
323
+ };
324
+
325
+ const result = await FileGenerator.generate(options);
326
+
327
+ expect(result.types).toBeNull();
328
+ });
329
+
330
+ it('generates webhooks utility file', async () => {
331
+ const options = {
332
+ projectPath: mockProjectPath,
333
+ apiKey: 'test-key',
334
+ backendUrl: 'https://backend.test.com',
335
+ organizationId: 'org-123',
336
+ features: ['crm'],
337
+ oauthProviders: [],
338
+ isTypeScript: true,
339
+ integrationPath: 'api-only',
340
+ };
341
+
342
+ const result = await FileGenerator.generate(options);
343
+
344
+ expect(result.webhooks).not.toBeNull();
345
+ expect(result.webhooks).toContain('webhooks.ts');
346
+ });
347
+ });
348
+
349
+ describe('quickstart integration path', () => {
350
+ it('returns results object with expected structure', async () => {
351
+ const options = {
352
+ projectPath: mockProjectPath,
353
+ apiKey: 'test-key',
354
+ backendUrl: 'https://backend.test.com',
355
+ organizationId: 'org-123',
356
+ features: ['crm', 'oauth'],
357
+ oauthProviders: ['google'],
358
+ isTypeScript: true,
359
+ frameworkType: 'nextjs',
360
+ integrationPath: 'quickstart',
361
+ };
362
+
363
+ const result = await FileGenerator.generate(options);
364
+
365
+ expect(result).toHaveProperty('apiClient');
366
+ expect(result).toHaveProperty('types');
367
+ expect(result).toHaveProperty('webhooks');
368
+ expect(result).toHaveProperty('envFile');
369
+ expect(result).toHaveProperty('nextauth');
370
+ expect(result).toHaveProperty('oauthGuide');
371
+ expect(result).toHaveProperty('gitignore');
372
+ });
373
+
374
+ it('generates NextAuth for Next.js with oauth feature', async () => {
375
+ const options = {
376
+ projectPath: mockProjectPath,
377
+ apiKey: 'test-key',
378
+ backendUrl: 'https://backend.test.com',
379
+ organizationId: 'org-123',
380
+ features: ['crm', 'oauth'],
381
+ oauthProviders: ['google'],
382
+ isTypeScript: true,
383
+ frameworkType: 'nextjs',
384
+ integrationPath: 'quickstart',
385
+ };
386
+
387
+ const result = await FileGenerator.generate(options);
388
+
389
+ expect(result.nextauth).not.toBeNull();
390
+ expect(result.oauthGuide).not.toBeNull();
391
+ });
392
+ });
393
+
394
+ describe('mcp-assisted integration path', () => {
395
+ it('returns results object with expected structure', async () => {
396
+ const options = {
397
+ projectPath: mockProjectPath,
398
+ apiKey: 'test-key',
399
+ backendUrl: 'https://backend.test.com',
400
+ organizationId: 'org-123',
401
+ organizationName: 'Test Org',
402
+ features: ['crm'],
403
+ oauthProviders: [],
404
+ isTypeScript: true,
405
+ integrationPath: 'mcp-assisted',
406
+ };
407
+
408
+ const result = await FileGenerator.generate(options);
409
+
410
+ expect(result).toHaveProperty('mcpConfig');
411
+ expect(result).toHaveProperty('mcpGuide');
412
+ expect(result).toHaveProperty('envFile');
413
+ expect(result).toHaveProperty('gitignore');
414
+ });
415
+
416
+ it('generates MCP config file', async () => {
417
+ const options = {
418
+ projectPath: mockProjectPath,
419
+ apiKey: 'test-key',
420
+ backendUrl: 'https://backend.test.com',
421
+ organizationId: 'org-123',
422
+ organizationName: 'Test Org',
423
+ features: ['crm'],
424
+ oauthProviders: [],
425
+ isTypeScript: true,
426
+ integrationPath: 'mcp-assisted',
427
+ };
428
+
429
+ const result = await FileGenerator.generate(options);
430
+
431
+ expect(result.mcpConfig).not.toBeNull();
432
+ expect(result.mcpConfig).toContain('mcp.json');
433
+ });
434
+
435
+ it('generates MCP guide file', async () => {
436
+ const options = {
437
+ projectPath: mockProjectPath,
438
+ apiKey: 'test-key',
439
+ backendUrl: 'https://backend.test.com',
440
+ organizationId: 'org-123',
441
+ organizationName: 'Test Org',
442
+ features: ['crm'],
443
+ oauthProviders: [],
444
+ isTypeScript: true,
445
+ integrationPath: 'mcp-assisted',
446
+ };
447
+
448
+ const result = await FileGenerator.generate(options);
449
+
450
+ expect(result.mcpGuide).not.toBeNull();
451
+ expect(result.mcpGuide).toContain('L4YERCAK3_MCP_GUIDE.md');
452
+ });
453
+ });
242
454
  });