@l4yercak3/cli 1.2.15 → 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.
- package/docs/INTEGRATION_PATHS_ARCHITECTURE.md +1543 -0
- package/package.json +1 -1
- package/src/commands/spread.js +101 -6
- package/src/detectors/database-detector.js +245 -0
- package/src/detectors/index.js +17 -4
- package/src/generators/api-only/client.js +683 -0
- package/src/generators/api-only/index.js +96 -0
- package/src/generators/api-only/types.js +618 -0
- package/src/generators/api-only/webhooks.js +377 -0
- package/src/generators/index.js +88 -2
- package/src/generators/mcp-guide-generator.js +256 -0
- package/src/generators/quickstart/components/index.js +1699 -0
- package/src/generators/quickstart/database/convex.js +1257 -0
- package/src/generators/quickstart/database/index.js +34 -0
- package/src/generators/quickstart/database/supabase.js +1132 -0
- package/src/generators/quickstart/hooks/index.js +1047 -0
- package/src/generators/quickstart/index.js +151 -0
- package/src/generators/quickstart/pages/index.js +1466 -0
- package/src/mcp/registry/domains/applications.js +4 -4
- package/src/mcp/registry/domains/benefits.js +798 -0
- package/src/mcp/registry/domains/crm.js +11 -11
- package/src/mcp/registry/domains/events.js +12 -12
- package/src/mcp/registry/domains/forms.js +12 -12
- package/src/mcp/registry/index.js +2 -0
- package/tests/database-detector.test.js +221 -0
- package/tests/generators-index.test.js +215 -3
|
@@ -49,7 +49,7 @@ Returns contacts with their details including name, email, phone, and custom fie
|
|
|
49
49
|
},
|
|
50
50
|
},
|
|
51
51
|
requiresAuth: true,
|
|
52
|
-
requiredPermissions: ['
|
|
52
|
+
requiredPermissions: ['contacts:read'],
|
|
53
53
|
handler: async (params, authContext) => {
|
|
54
54
|
// Build query params
|
|
55
55
|
const queryParams = new URLSearchParams();
|
|
@@ -130,7 +130,7 @@ Use this to add a customer, lead, or prospect to the user's CRM.`,
|
|
|
130
130
|
required: ['firstName', 'lastName', 'email'],
|
|
131
131
|
},
|
|
132
132
|
requiresAuth: true,
|
|
133
|
-
requiredPermissions: ['
|
|
133
|
+
requiredPermissions: ['contacts:write'],
|
|
134
134
|
handler: async (params, authContext) => {
|
|
135
135
|
const response = await backendClient.request('POST', '/api/v1/crm/contacts', {
|
|
136
136
|
organizationId: authContext.organizationId,
|
|
@@ -177,7 +177,7 @@ Use this to retrieve full details including activities and notes.`,
|
|
|
177
177
|
required: ['contactId'],
|
|
178
178
|
},
|
|
179
179
|
requiresAuth: true,
|
|
180
|
-
requiredPermissions: ['
|
|
180
|
+
requiredPermissions: ['contacts:read'],
|
|
181
181
|
handler: async (params, authContext) => {
|
|
182
182
|
const queryParams = new URLSearchParams();
|
|
183
183
|
if (params.includeActivities) queryParams.set('includeActivities', 'true');
|
|
@@ -242,7 +242,7 @@ Use this to modify contact information.`,
|
|
|
242
242
|
required: ['contactId'],
|
|
243
243
|
},
|
|
244
244
|
requiresAuth: true,
|
|
245
|
-
requiredPermissions: ['
|
|
245
|
+
requiredPermissions: ['contacts:write'],
|
|
246
246
|
handler: async (params, authContext) => {
|
|
247
247
|
const { contactId, ...updates } = params;
|
|
248
248
|
|
|
@@ -273,7 +273,7 @@ This performs a soft delete - the contact can be restored.`,
|
|
|
273
273
|
required: ['contactId'],
|
|
274
274
|
},
|
|
275
275
|
requiresAuth: true,
|
|
276
|
-
requiredPermissions: ['
|
|
276
|
+
requiredPermissions: ['contacts:write'],
|
|
277
277
|
handler: async (params, authContext) => {
|
|
278
278
|
await backendClient.request('DELETE', `/api/v1/crm/contacts/${params.contactId}`);
|
|
279
279
|
|
|
@@ -311,7 +311,7 @@ These are customer companies, not L4YERCAK3 platform organizations.`,
|
|
|
311
311
|
},
|
|
312
312
|
},
|
|
313
313
|
requiresAuth: true,
|
|
314
|
-
requiredPermissions: ['
|
|
314
|
+
requiredPermissions: ['contacts:read'],
|
|
315
315
|
handler: async (params, authContext) => {
|
|
316
316
|
const queryParams = new URLSearchParams();
|
|
317
317
|
queryParams.set('organizationId', authContext.organizationId);
|
|
@@ -386,7 +386,7 @@ Use this to track a customer company in the CRM.`,
|
|
|
386
386
|
required: ['name'],
|
|
387
387
|
},
|
|
388
388
|
requiresAuth: true,
|
|
389
|
-
requiredPermissions: ['
|
|
389
|
+
requiredPermissions: ['contacts:write'],
|
|
390
390
|
handler: async (params, authContext) => {
|
|
391
391
|
const response = await backendClient.request('POST', '/api/v1/crm/organizations', {
|
|
392
392
|
organizationId: authContext.organizationId,
|
|
@@ -426,7 +426,7 @@ Use this to track a customer company in the CRM.`,
|
|
|
426
426
|
required: ['crmOrganizationId'],
|
|
427
427
|
},
|
|
428
428
|
requiresAuth: true,
|
|
429
|
-
requiredPermissions: ['
|
|
429
|
+
requiredPermissions: ['contacts:read'],
|
|
430
430
|
handler: async (params, authContext) => {
|
|
431
431
|
const queryParams = new URLSearchParams();
|
|
432
432
|
if (params.includeContacts) queryParams.set('includeContacts', 'true');
|
|
@@ -488,7 +488,7 @@ Use this to associate a contact with a company they work for.`,
|
|
|
488
488
|
required: ['contactId', 'crmOrganizationId'],
|
|
489
489
|
},
|
|
490
490
|
requiresAuth: true,
|
|
491
|
-
requiredPermissions: ['
|
|
491
|
+
requiredPermissions: ['contacts:write'],
|
|
492
492
|
handler: async (params, authContext) => {
|
|
493
493
|
await backendClient.request('POST', '/api/v1/crm/contact-organization-links', {
|
|
494
494
|
contactId: params.contactId,
|
|
@@ -527,7 +527,7 @@ Use this to record information or interactions with a contact.`,
|
|
|
527
527
|
required: ['contactId', 'content'],
|
|
528
528
|
},
|
|
529
529
|
requiresAuth: true,
|
|
530
|
-
requiredPermissions: ['
|
|
530
|
+
requiredPermissions: ['contacts:write'],
|
|
531
531
|
handler: async (params, authContext) => {
|
|
532
532
|
await backendClient.request('POST', `/api/v1/crm/contacts/${params.contactId}/notes`, {
|
|
533
533
|
content: params.content,
|
|
@@ -572,7 +572,7 @@ Use this to track interactions with contacts.`,
|
|
|
572
572
|
required: ['contactId', 'type', 'summary'],
|
|
573
573
|
},
|
|
574
574
|
requiresAuth: true,
|
|
575
|
-
requiredPermissions: ['
|
|
575
|
+
requiredPermissions: ['contacts:write'],
|
|
576
576
|
handler: async (params, authContext) => {
|
|
577
577
|
await backendClient.request('POST', `/api/v1/crm/contacts/${params.contactId}/activities`, {
|
|
578
578
|
type: params.type,
|
|
@@ -51,7 +51,7 @@ Returns events with their basic details, status, and dates.`,
|
|
|
51
51
|
},
|
|
52
52
|
},
|
|
53
53
|
requiresAuth: true,
|
|
54
|
-
requiredPermissions: ['
|
|
54
|
+
requiredPermissions: ['events:read'],
|
|
55
55
|
handler: async (params, authContext) => {
|
|
56
56
|
const queryParams = new URLSearchParams();
|
|
57
57
|
queryParams.set('organizationId', authContext.organizationId);
|
|
@@ -128,7 +128,7 @@ Events start in 'draft' status and can be published when ready.`,
|
|
|
128
128
|
required: ['name', 'startDate', 'endDate', 'location'],
|
|
129
129
|
},
|
|
130
130
|
requiresAuth: true,
|
|
131
|
-
requiredPermissions: ['
|
|
131
|
+
requiredPermissions: ['events:write'],
|
|
132
132
|
handler: async (params, authContext) => {
|
|
133
133
|
// Convert ISO dates to timestamps
|
|
134
134
|
const startDate = new Date(params.startDate).getTime();
|
|
@@ -184,7 +184,7 @@ Includes agenda, products, and optionally sponsors.`,
|
|
|
184
184
|
required: ['eventId'],
|
|
185
185
|
},
|
|
186
186
|
requiresAuth: true,
|
|
187
|
-
requiredPermissions: ['
|
|
187
|
+
requiredPermissions: ['events:read'],
|
|
188
188
|
handler: async (params, authContext) => {
|
|
189
189
|
const queryParams = new URLSearchParams();
|
|
190
190
|
if (params.includeProducts !== false) queryParams.set('includeProducts', 'true');
|
|
@@ -245,7 +245,7 @@ Can update name, dates, location, and other properties.`,
|
|
|
245
245
|
required: ['eventId'],
|
|
246
246
|
},
|
|
247
247
|
requiresAuth: true,
|
|
248
|
-
requiredPermissions: ['
|
|
248
|
+
requiredPermissions: ['events:write'],
|
|
249
249
|
handler: async (params, authContext) => {
|
|
250
250
|
const { eventId, startDate, endDate, ...updates } = params;
|
|
251
251
|
|
|
@@ -278,7 +278,7 @@ Changes status from 'draft' to 'published'.`,
|
|
|
278
278
|
required: ['eventId'],
|
|
279
279
|
},
|
|
280
280
|
requiresAuth: true,
|
|
281
|
-
requiredPermissions: ['
|
|
281
|
+
requiredPermissions: ['events:write'],
|
|
282
282
|
handler: async (params, authContext) => {
|
|
283
283
|
await backendClient.request('POST', `/api/v1/events/${params.eventId}/publish`);
|
|
284
284
|
|
|
@@ -306,7 +306,7 @@ Sets status to 'cancelled'. This is a soft delete.`,
|
|
|
306
306
|
required: ['eventId'],
|
|
307
307
|
},
|
|
308
308
|
requiresAuth: true,
|
|
309
|
-
requiredPermissions: ['
|
|
309
|
+
requiredPermissions: ['events:write'],
|
|
310
310
|
handler: async (params, authContext) => {
|
|
311
311
|
await backendClient.request('POST', `/api/v1/events/${params.eventId}/cancel`);
|
|
312
312
|
|
|
@@ -371,7 +371,7 @@ Replace the entire agenda with a new list of agenda items.`,
|
|
|
371
371
|
required: ['eventId', 'agenda'],
|
|
372
372
|
},
|
|
373
373
|
requiresAuth: true,
|
|
374
|
-
requiredPermissions: ['
|
|
374
|
+
requiredPermissions: ['events:write'],
|
|
375
375
|
handler: async (params, authContext) => {
|
|
376
376
|
await backendClient.request('PATCH', `/api/v1/events/${params.eventId}/agenda`, {
|
|
377
377
|
agenda: params.agenda,
|
|
@@ -403,7 +403,7 @@ Replace the entire agenda with a new list of agenda items.`,
|
|
|
403
403
|
required: ['eventId'],
|
|
404
404
|
},
|
|
405
405
|
requiresAuth: true,
|
|
406
|
-
requiredPermissions: ['
|
|
406
|
+
requiredPermissions: ['events:read'],
|
|
407
407
|
handler: async (params, authContext) => {
|
|
408
408
|
const response = await backendClient.request(
|
|
409
409
|
'GET',
|
|
@@ -470,7 +470,7 @@ Products can be tickets, merchandise, or add-ons.`,
|
|
|
470
470
|
required: ['eventId', 'name', 'priceInCents'],
|
|
471
471
|
},
|
|
472
472
|
requiresAuth: true,
|
|
473
|
-
requiredPermissions: ['
|
|
473
|
+
requiredPermissions: ['events:write'],
|
|
474
474
|
handler: async (params, authContext) => {
|
|
475
475
|
const response = await backendClient.request('POST', '/api/v1/products', {
|
|
476
476
|
organizationId: authContext.organizationId,
|
|
@@ -519,7 +519,7 @@ Returns people who have purchased tickets.`,
|
|
|
519
519
|
required: ['eventId'],
|
|
520
520
|
},
|
|
521
521
|
requiresAuth: true,
|
|
522
|
-
requiredPermissions: ['
|
|
522
|
+
requiredPermissions: ['events:read'],
|
|
523
523
|
handler: async (params, authContext) => {
|
|
524
524
|
const queryParams = new URLSearchParams();
|
|
525
525
|
if (params.status) queryParams.set('status', params.status);
|
|
@@ -571,7 +571,7 @@ Sponsors are CRM organizations linked to the event.`,
|
|
|
571
571
|
required: ['eventId'],
|
|
572
572
|
},
|
|
573
573
|
requiresAuth: true,
|
|
574
|
-
requiredPermissions: ['
|
|
574
|
+
requiredPermissions: ['events:read'],
|
|
575
575
|
handler: async (params, authContext) => {
|
|
576
576
|
const queryParams = new URLSearchParams();
|
|
577
577
|
if (params.sponsorLevel) queryParams.set('sponsorLevel', params.sponsorLevel);
|
|
@@ -629,7 +629,7 @@ Sponsors are CRM organizations linked to the event.`,
|
|
|
629
629
|
required: ['eventId', 'crmOrganizationId'],
|
|
630
630
|
},
|
|
631
631
|
requiresAuth: true,
|
|
632
|
-
requiredPermissions: ['
|
|
632
|
+
requiredPermissions: ['events:write'],
|
|
633
633
|
handler: async (params, authContext) => {
|
|
634
634
|
await backendClient.request('POST', `/api/v1/events/${params.eventId}/sponsors`, {
|
|
635
635
|
crmOrganizationId: params.crmOrganizationId,
|
|
@@ -43,7 +43,7 @@ Returns forms with their type, status, and submission counts.`,
|
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
requiresAuth: true,
|
|
46
|
-
requiredPermissions: ['
|
|
46
|
+
requiredPermissions: ['forms:read'],
|
|
47
47
|
handler: async (params, authContext) => {
|
|
48
48
|
const queryParams = new URLSearchParams();
|
|
49
49
|
queryParams.set('organizationId', authContext.organizationId);
|
|
@@ -197,7 +197,7 @@ Start with basic info, then add fields.`,
|
|
|
197
197
|
required: ['name'],
|
|
198
198
|
},
|
|
199
199
|
requiresAuth: true,
|
|
200
|
-
requiredPermissions: ['
|
|
200
|
+
requiredPermissions: ['forms:write'],
|
|
201
201
|
handler: async (params, authContext) => {
|
|
202
202
|
// Build form schema
|
|
203
203
|
const formSchema = {
|
|
@@ -259,7 +259,7 @@ Start with basic info, then add fields.`,
|
|
|
259
259
|
required: ['formId'],
|
|
260
260
|
},
|
|
261
261
|
requiresAuth: true,
|
|
262
|
-
requiredPermissions: ['
|
|
262
|
+
requiredPermissions: ['forms:read'],
|
|
263
263
|
handler: async (params, authContext) => {
|
|
264
264
|
const response = await backendClient.request('GET', `/api/v1/forms/${params.formId}`);
|
|
265
265
|
|
|
@@ -312,7 +312,7 @@ To update fields, use l4yercak3_forms_add_field or l4yercak3_forms_update_fields
|
|
|
312
312
|
required: ['formId'],
|
|
313
313
|
},
|
|
314
314
|
requiresAuth: true,
|
|
315
|
-
requiredPermissions: ['
|
|
315
|
+
requiredPermissions: ['forms:write'],
|
|
316
316
|
handler: async (params, authContext) => {
|
|
317
317
|
const { formId, ...updates } = params;
|
|
318
318
|
|
|
@@ -395,7 +395,7 @@ To update fields, use l4yercak3_forms_add_field or l4yercak3_forms_update_fields
|
|
|
395
395
|
required: ['formId', 'field'],
|
|
396
396
|
},
|
|
397
397
|
requiresAuth: true,
|
|
398
|
-
requiredPermissions: ['
|
|
398
|
+
requiredPermissions: ['forms:write'],
|
|
399
399
|
handler: async (params, authContext) => {
|
|
400
400
|
// Get current form to add field
|
|
401
401
|
const formResponse = await backendClient.request('GET', `/api/v1/forms/${params.formId}`);
|
|
@@ -439,7 +439,7 @@ To update fields, use l4yercak3_forms_add_field or l4yercak3_forms_update_fields
|
|
|
439
439
|
required: ['formId'],
|
|
440
440
|
},
|
|
441
441
|
requiresAuth: true,
|
|
442
|
-
requiredPermissions: ['
|
|
442
|
+
requiredPermissions: ['forms:write'],
|
|
443
443
|
handler: async (params, authContext) => {
|
|
444
444
|
await backendClient.request('POST', `/api/v1/forms/${params.formId}/publish`);
|
|
445
445
|
|
|
@@ -466,7 +466,7 @@ To update fields, use l4yercak3_forms_add_field or l4yercak3_forms_update_fields
|
|
|
466
466
|
required: ['formId'],
|
|
467
467
|
},
|
|
468
468
|
requiresAuth: true,
|
|
469
|
-
requiredPermissions: ['
|
|
469
|
+
requiredPermissions: ['forms:write'],
|
|
470
470
|
handler: async (params, authContext) => {
|
|
471
471
|
await backendClient.request('POST', `/api/v1/forms/${params.formId}/unpublish`);
|
|
472
472
|
|
|
@@ -493,7 +493,7 @@ To update fields, use l4yercak3_forms_add_field or l4yercak3_forms_update_fields
|
|
|
493
493
|
required: ['formId'],
|
|
494
494
|
},
|
|
495
495
|
requiresAuth: true,
|
|
496
|
-
requiredPermissions: ['
|
|
496
|
+
requiredPermissions: ['forms:write'],
|
|
497
497
|
handler: async (params, authContext) => {
|
|
498
498
|
await backendClient.request('DELETE', `/api/v1/forms/${params.formId}`);
|
|
499
499
|
|
|
@@ -518,7 +518,7 @@ To update fields, use l4yercak3_forms_add_field or l4yercak3_forms_update_fields
|
|
|
518
518
|
required: ['formId'],
|
|
519
519
|
},
|
|
520
520
|
requiresAuth: true,
|
|
521
|
-
requiredPermissions: ['
|
|
521
|
+
requiredPermissions: ['forms:write'],
|
|
522
522
|
handler: async (params, authContext) => {
|
|
523
523
|
const response = await backendClient.request(
|
|
524
524
|
'POST',
|
|
@@ -563,7 +563,7 @@ To update fields, use l4yercak3_forms_add_field or l4yercak3_forms_update_fields
|
|
|
563
563
|
required: ['formId'],
|
|
564
564
|
},
|
|
565
565
|
requiresAuth: true,
|
|
566
|
-
requiredPermissions: ['
|
|
566
|
+
requiredPermissions: ['forms:read'],
|
|
567
567
|
handler: async (params, authContext) => {
|
|
568
568
|
const queryParams = new URLSearchParams();
|
|
569
569
|
if (params.status) queryParams.set('status', params.status);
|
|
@@ -602,7 +602,7 @@ To update fields, use l4yercak3_forms_add_field or l4yercak3_forms_update_fields
|
|
|
602
602
|
required: ['responseId'],
|
|
603
603
|
},
|
|
604
604
|
requiresAuth: true,
|
|
605
|
-
requiredPermissions: ['
|
|
605
|
+
requiredPermissions: ['forms:read'],
|
|
606
606
|
handler: async (params, authContext) => {
|
|
607
607
|
const response = await backendClient.request(
|
|
608
608
|
'GET',
|
|
@@ -646,7 +646,7 @@ To update fields, use l4yercak3_forms_add_field or l4yercak3_forms_update_fields
|
|
|
646
646
|
required: ['formId'],
|
|
647
647
|
},
|
|
648
648
|
requiresAuth: true,
|
|
649
|
-
requiredPermissions: ['
|
|
649
|
+
requiredPermissions: ['forms:read'],
|
|
650
650
|
handler: async (params, authContext) => {
|
|
651
651
|
const format = params.format || 'json';
|
|
652
652
|
|
|
@@ -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
|
+
});
|