@l4yercak3/cli 1.2.16 → 1.2.19

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 (31) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/docs/CRM-PIPELINES-SEQUENCES-SPEC.md +429 -0
  3. package/docs/INTEGRATION_PATHS_ARCHITECTURE.md +1543 -0
  4. package/package.json +1 -1
  5. package/src/commands/login.js +26 -7
  6. package/src/commands/spread.js +251 -10
  7. package/src/detectors/database-detector.js +245 -0
  8. package/src/detectors/expo-detector.js +4 -4
  9. package/src/detectors/index.js +17 -4
  10. package/src/generators/api-only/client.js +683 -0
  11. package/src/generators/api-only/index.js +96 -0
  12. package/src/generators/api-only/types.js +618 -0
  13. package/src/generators/api-only/webhooks.js +377 -0
  14. package/src/generators/env-generator.js +23 -8
  15. package/src/generators/expo-auth-generator.js +1009 -0
  16. package/src/generators/index.js +88 -2
  17. package/src/generators/mcp-guide-generator.js +256 -0
  18. package/src/generators/quickstart/components/index.js +1699 -0
  19. package/src/generators/quickstart/components-mobile/index.js +1440 -0
  20. package/src/generators/quickstart/database/convex.js +1257 -0
  21. package/src/generators/quickstart/database/index.js +34 -0
  22. package/src/generators/quickstart/database/supabase.js +1132 -0
  23. package/src/generators/quickstart/hooks/index.js +1065 -0
  24. package/src/generators/quickstart/index.js +177 -0
  25. package/src/generators/quickstart/pages/index.js +1466 -0
  26. package/src/generators/quickstart/screens/index.js +1498 -0
  27. package/src/mcp/registry/domains/benefits.js +798 -0
  28. package/src/mcp/registry/index.js +2 -0
  29. package/tests/database-detector.test.js +221 -0
  30. package/tests/expo-detector.test.js +3 -4
  31. package/tests/generators-index.test.js +215 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@l4yercak3/cli",
3
- "version": "1.2.16",
3
+ "version": "1.2.19",
4
4
  "description": "Icing on the L4yercak3 - The sweet finishing touch for your Layer Cake integration",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -232,6 +232,18 @@ async function handleLogin() {
232
232
  }
233
233
  }
234
234
 
235
+ /**
236
+ * Get friendly framework name for display
237
+ */
238
+ function getFrameworkDisplayName(frameworkType) {
239
+ const names = {
240
+ 'nextjs': 'Next.js',
241
+ 'expo': 'Expo',
242
+ 'react-native': 'React Native',
243
+ };
244
+ return names[frameworkType] || frameworkType;
245
+ }
246
+
235
247
  /**
236
248
  * Prompt user to run the setup wizard after login
237
249
  */
@@ -243,7 +255,7 @@ async function promptSetupWizard() {
243
255
  const isInProject = detection.framework.type !== null;
244
256
 
245
257
  if (isInProject) {
246
- const frameworkName = detection.framework.type === 'nextjs' ? 'Next.js' : detection.framework.type;
258
+ const frameworkName = getFrameworkDisplayName(detection.framework.type);
247
259
  console.log(chalk.cyan(` 🔍 Detected ${frameworkName} project in current directory\n`));
248
260
 
249
261
  // Check if project is already configured
@@ -280,6 +292,10 @@ async function promptSetupWizard() {
280
292
 
281
293
  if (!runWizard) {
282
294
  console.log('');
295
+ console.log(chalk.yellow(` ℹ️ To generate ${frameworkName}-specific code later, run:\n`));
296
+ console.log(chalk.cyan(' l4yercak3 spread\n'));
297
+ console.log(chalk.gray(' This will create components, hooks, and screens'));
298
+ console.log(chalk.gray(` optimized for ${frameworkName}.\n`));
283
299
  const action = await showPostLoginMenu({ isInProject: true, hasExistingConfig: false });
284
300
  await executeMenuAction(action);
285
301
  return;
@@ -294,14 +310,17 @@ async function promptSetupWizard() {
294
310
  } else {
295
311
  // Not in a project directory
296
312
  console.log(chalk.cyan(' 📋 What\'s Next?\n'));
297
- console.log(chalk.gray(' To integrate L4YERCAK3 with your Next.js project:'));
313
+ console.log(chalk.gray(' To integrate L4YERCAK3 with your project:\n'));
298
314
  console.log(chalk.gray(' 1. Navigate to your project directory'));
299
315
  console.log(chalk.gray(' 2. Run: l4yercak3 spread\n'));
300
- console.log(chalk.gray(' This will set up:'));
301
- console.log(chalk.gray(' • API client for backend communication'));
302
- console.log(chalk.gray(' • Environment variables'));
303
- console.log(chalk.gray(' OAuth authentication (optional)'));
304
- console.log(chalk.gray(' • CRM, Projects, and Invoices integration\n'));
316
+ console.log(chalk.gray(' Supported frameworks:'));
317
+ console.log(chalk.gray(' • Next.js (App Router & Pages Router)'));
318
+ console.log(chalk.gray(' • Expo / React Native\n'));
319
+ console.log(chalk.gray(' The spread command will:'));
320
+ console.log(chalk.gray(' • Detect your framework automatically'));
321
+ console.log(chalk.gray(' • Generate platform-specific components'));
322
+ console.log(chalk.gray(' • Set up API client & environment variables'));
323
+ console.log(chalk.gray(' • Configure OAuth authentication (optional)\n'));
305
324
  }
306
325
  }
307
326
 
@@ -5,6 +5,7 @@
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
+ const { execSync } = require('child_process');
8
9
  const configManager = require('../config/config-manager');
9
10
  const backendClient = require('../api/backend-client');
10
11
  const projectDetector = require('../detectors');
@@ -54,6 +55,124 @@ async function generateNewApiKey(organizationId) {
54
55
  return apiKey;
55
56
  }
56
57
 
58
+ /**
59
+ * Check if the project is a git repository
60
+ */
61
+ function isGitRepo(projectPath) {
62
+ try {
63
+ execSync('git rev-parse --is-inside-work-tree', {
64
+ cwd: projectPath,
65
+ stdio: 'pipe',
66
+ });
67
+ return true;
68
+ } catch {
69
+ return false;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Get git status (uncommitted changes)
75
+ */
76
+ function getGitStatus(projectPath) {
77
+ try {
78
+ const status = execSync('git status --porcelain', {
79
+ cwd: projectPath,
80
+ encoding: 'utf8',
81
+ });
82
+ return status.trim();
83
+ } catch {
84
+ return '';
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Check for uncommitted changes and prompt user to commit first
90
+ * Returns true if we should proceed, false if user wants to abort
91
+ */
92
+ async function checkGitStatusBeforeGeneration(projectPath) {
93
+ // Skip if not a git repo
94
+ if (!isGitRepo(projectPath)) {
95
+ return true;
96
+ }
97
+
98
+ const status = getGitStatus(projectPath);
99
+
100
+ // No uncommitted changes - proceed
101
+ if (!status) {
102
+ return true;
103
+ }
104
+
105
+ // Count changes
106
+ const changes = status.split('\n').filter(line => line.trim());
107
+ const modifiedCount = changes.filter(line => line.startsWith(' M') || line.startsWith('M ')).length;
108
+ const untrackedCount = changes.filter(line => line.startsWith('??')).length;
109
+ const stagedCount = changes.filter(line => /^[MADRC]/.test(line)).length;
110
+
111
+ console.log(chalk.yellow(' ⚠️ Uncommitted changes detected\n'));
112
+
113
+ if (modifiedCount > 0) {
114
+ console.log(chalk.gray(` ${modifiedCount} modified file(s)`));
115
+ }
116
+ if (untrackedCount > 0) {
117
+ console.log(chalk.gray(` ${untrackedCount} untracked file(s)`));
118
+ }
119
+ if (stagedCount > 0) {
120
+ console.log(chalk.gray(` ${stagedCount} staged file(s)`));
121
+ }
122
+
123
+ console.log('');
124
+ console.log(chalk.gray(' We recommend committing your changes before generating'));
125
+ console.log(chalk.gray(' new files, so you can easily revert if needed.\n'));
126
+
127
+ const { action } = await inquirer.prompt([
128
+ {
129
+ type: 'list',
130
+ name: 'action',
131
+ message: 'How would you like to proceed?',
132
+ choices: [
133
+ {
134
+ name: 'Continue anyway - I\'ll handle it later',
135
+ value: 'continue',
136
+ },
137
+ {
138
+ name: 'Commit changes now - Create a checkpoint commit',
139
+ value: 'commit',
140
+ },
141
+ {
142
+ name: 'Abort - I\'ll commit manually first',
143
+ value: 'abort',
144
+ },
145
+ ],
146
+ },
147
+ ]);
148
+
149
+ if (action === 'abort') {
150
+ console.log(chalk.gray('\n No worries! Run "l4yercak3 spread" again after committing.\n'));
151
+ return false;
152
+ }
153
+
154
+ if (action === 'commit') {
155
+ try {
156
+ // Stage all changes
157
+ execSync('git add -A', { cwd: projectPath, stdio: 'pipe' });
158
+
159
+ // Create commit
160
+ const commitMessage = 'chore: checkpoint before L4YERCAK3 integration';
161
+ execSync(`git commit -m "${commitMessage}"`, { cwd: projectPath, stdio: 'pipe' });
162
+
163
+ console.log(chalk.green('\n ✅ Changes committed successfully'));
164
+ console.log(chalk.gray(` Message: "${commitMessage}"`));
165
+ console.log(chalk.gray(' You can revert with: git reset --soft HEAD~1\n'));
166
+ } catch (error) {
167
+ console.log(chalk.yellow('\n ⚠️ Could not create commit automatically'));
168
+ console.log(chalk.gray(` ${error.message}`));
169
+ console.log(chalk.gray(' Proceeding with file generation anyway...\n'));
170
+ }
171
+ }
172
+
173
+ return true;
174
+ }
175
+
57
176
  async function handleSpread() {
58
177
  // Check if logged in
59
178
  if (!configManager.isLoggedIn()) {
@@ -71,11 +190,17 @@ async function handleSpread() {
71
190
 
72
191
  // Display framework detection results
73
192
  if (detection.framework.type) {
74
- const frameworkName = detection.framework.type === 'nextjs' ? 'Next.js' : detection.framework.type;
193
+ const frameworkNames = {
194
+ 'nextjs': 'Next.js',
195
+ 'expo': 'Expo',
196
+ 'react-native': 'React Native',
197
+ };
198
+ const frameworkName = frameworkNames[detection.framework.type] || detection.framework.type;
75
199
  console.log(chalk.green(` ✅ Detected ${frameworkName} project`));
76
-
200
+
201
+ const meta = detection.framework.metadata || {};
202
+
77
203
  if (detection.framework.type === 'nextjs') {
78
- const meta = detection.framework.metadata;
79
204
  if (meta.version) {
80
205
  console.log(chalk.gray(` Version: ${meta.version}`));
81
206
  }
@@ -85,6 +210,21 @@ async function handleSpread() {
85
210
  if (meta.hasTypeScript) {
86
211
  console.log(chalk.gray(' TypeScript: Yes'));
87
212
  }
213
+ } else if (detection.framework.type === 'expo' || detection.framework.type === 'react-native') {
214
+ if (meta.expoVersion) {
215
+ console.log(chalk.gray(` Expo SDK: ${meta.expoVersion}`));
216
+ }
217
+ if (meta.reactNativeVersion) {
218
+ console.log(chalk.gray(` React Native: ${meta.reactNativeVersion}`));
219
+ }
220
+ if (meta.routerType) {
221
+ const routerName = meta.routerType === 'expo-router' ? 'Expo Router' :
222
+ meta.routerType === 'react-navigation' ? 'React Navigation' : 'Native';
223
+ console.log(chalk.gray(` Navigation: ${routerName}`));
224
+ }
225
+ if (meta.hasTypeScript) {
226
+ console.log(chalk.gray(' TypeScript: Yes'));
227
+ }
88
228
  }
89
229
 
90
230
  // Show supported features
@@ -405,17 +545,83 @@ async function handleSpread() {
405
545
  choices: [
406
546
  { name: 'CRM (contacts, organizations)', value: 'crm', checked: true },
407
547
  { name: 'Events (event management, registrations)', value: 'events', checked: false },
408
- { name: 'Products (product catalog)', value: 'products', checked: false },
409
- { name: 'Checkout (payment processing)', value: 'checkout', checked: false },
410
- { name: 'Tickets (ticket generation)', value: 'tickets', checked: false },
411
- { name: 'Invoicing (invoice creation)', value: 'invoicing', checked: false },
412
- { name: 'Forms (dynamic forms)', value: 'forms', checked: false },
413
- { name: 'Projects (project management)', value: 'projects', checked: false },
548
+ { name: 'Forms (form builder, submissions)', value: 'forms', checked: false },
549
+ { name: 'Products (product catalog, inventory)', value: 'products', checked: false },
550
+ { name: 'Checkout (cart, payments)', value: 'checkout', checked: false },
551
+ { name: 'Invoicing (B2B/B2C invoices)', value: 'invoicing', checked: false },
552
+ { name: 'Benefits (claims, commissions)', value: 'benefits', checked: false },
553
+ { name: 'Certificates (CME, attendance)', value: 'certificates', checked: false },
554
+ { name: 'Projects (task management)', value: 'projects', checked: false },
414
555
  { name: 'OAuth Authentication', value: 'oauth', checked: false },
415
556
  ],
416
557
  },
417
558
  ]);
418
559
 
560
+ // Step 4.5: Integration path selection
561
+ console.log(chalk.cyan('\n 🛤️ Integration Path\n'));
562
+ const { integrationPath } = await inquirer.prompt([
563
+ {
564
+ type: 'list',
565
+ name: 'integrationPath',
566
+ message: 'Choose your integration approach:',
567
+ choices: [
568
+ {
569
+ name: 'Quick Start (Recommended) - Full-stack with UI components & database',
570
+ value: 'quickstart',
571
+ },
572
+ {
573
+ name: 'API Only - Just the typed API client, you build the UI',
574
+ value: 'api-only',
575
+ },
576
+ {
577
+ name: 'MCP-Assisted - AI-powered custom generation with Claude Code',
578
+ value: 'mcp-assisted',
579
+ },
580
+ ],
581
+ },
582
+ ]);
583
+ console.log(chalk.green(` ✅ Path: ${integrationPath === 'quickstart' ? 'Quick Start' : integrationPath === 'api-only' ? 'API Only' : 'MCP-Assisted'}\n`));
584
+
585
+ // Step 4.6: Database selection (for Quick Start path when no DB detected)
586
+ let selectedDatabase = null;
587
+ if (integrationPath === 'quickstart') {
588
+ const dbDetection = detection.database || { hasDatabase: false };
589
+
590
+ if (!dbDetection.hasDatabase) {
591
+ console.log(chalk.yellow(' ℹ️ No database detected in your project.\n'));
592
+
593
+ const { database } = await inquirer.prompt([
594
+ {
595
+ type: 'list',
596
+ name: 'database',
597
+ message: 'Which database would you like to use?',
598
+ choices: [
599
+ {
600
+ name: 'Convex (Recommended) - Real-time, serverless, TypeScript-first',
601
+ value: 'convex',
602
+ },
603
+ {
604
+ name: 'Supabase - PostgreSQL with Auth, Storage, and Edge Functions',
605
+ value: 'supabase',
606
+ },
607
+ {
608
+ name: 'None - I\'ll set up my own database later',
609
+ value: 'none',
610
+ },
611
+ ],
612
+ },
613
+ ]);
614
+
615
+ selectedDatabase = database !== 'none' ? database : null;
616
+ if (selectedDatabase) {
617
+ console.log(chalk.green(` ✅ Database: ${selectedDatabase}\n`));
618
+ }
619
+ } else {
620
+ console.log(chalk.green(` ✅ Detected ${dbDetection.primary?.type || 'existing'} database\n`));
621
+ selectedDatabase = dbDetection.primary?.type || 'existing';
622
+ }
623
+ }
624
+
419
625
  // Step 5: OAuth provider selection (if OAuth enabled)
420
626
  let oauthProviders = [];
421
627
  if (features.includes('oauth')) {
@@ -468,7 +674,13 @@ async function handleSpread() {
468
674
  }
469
675
  }
470
676
 
471
- // Step 8: Generate files
677
+ // Step 8: Check for uncommitted changes before generating files
678
+ const shouldProceed = await checkGitStatusBeforeGeneration(detection.projectPath);
679
+ if (!shouldProceed) {
680
+ return;
681
+ }
682
+
683
+ // Step 9: Generate files
472
684
  console.log(chalk.cyan('\n 📝 Generating files...\n'));
473
685
 
474
686
  // Extract framework metadata for generation
@@ -489,15 +701,38 @@ async function handleSpread() {
489
701
  isTypeScript,
490
702
  routerType,
491
703
  frameworkType: detection.framework.type || 'unknown',
704
+ integrationPath,
705
+ selectedDatabase,
492
706
  };
493
707
 
494
708
  const generatedFiles = await fileGenerator.generate(generationOptions);
495
709
 
496
710
  // Display results
497
711
  console.log(chalk.green(' ✅ Files generated:\n'));
712
+
713
+ // API client files (api-only and quickstart paths)
498
714
  if (generatedFiles.apiClient) {
499
715
  console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.apiClient)}`));
500
716
  }
717
+ if (generatedFiles.types) {
718
+ console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.types)}`));
719
+ }
720
+ if (generatedFiles.webhooks) {
721
+ console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.webhooks)}`));
722
+ }
723
+ if (generatedFiles.index) {
724
+ console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.index)}`));
725
+ }
726
+
727
+ // MCP files (mcp-assisted path)
728
+ if (generatedFiles.mcpConfig) {
729
+ console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.mcpConfig)}`));
730
+ }
731
+ if (generatedFiles.mcpGuide) {
732
+ console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.mcpGuide)}`));
733
+ }
734
+
735
+ // Common files
501
736
  if (generatedFiles.envFile) {
502
737
  console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.envFile)}`));
503
738
  }
@@ -641,6 +876,8 @@ async function handleSpread() {
641
876
  oauthProviders,
642
877
  productionDomain,
643
878
  frameworkType: detection.framework.type,
879
+ integrationPath,
880
+ selectedDatabase,
644
881
  createdAt: Date.now(),
645
882
  updatedAt: isUpdate ? Date.now() : undefined,
646
883
  };
@@ -678,6 +915,10 @@ async function handleSpread() {
678
915
 
679
916
  console.log(chalk.gray(' Your project is now connected to L4YERCAK3! 🍰\n'));
680
917
 
918
+ // Show menu for next actions
919
+ const action = await showMainMenu({ isLoggedIn: true, isInProject: true, hasExistingConfig: true });
920
+ await executeMenuAction(action);
921
+
681
922
  } catch (error) {
682
923
  console.error(chalk.red(`\n ❌ Error: ${error.message}\n`));
683
924
  if (error.stack) {
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Database Detector
3
+ * Detects existing database configurations in a project
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ /**
10
+ * Check if a file exists
11
+ * @param {string} filePath - Path to check
12
+ * @returns {boolean}
13
+ */
14
+ function fileExists(filePath) {
15
+ try {
16
+ return fs.existsSync(filePath);
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Read and parse package.json
24
+ * @param {string} projectPath - Project root path
25
+ * @returns {object|null}
26
+ */
27
+ function readPackageJson(projectPath) {
28
+ try {
29
+ const packagePath = path.join(projectPath, 'package.json');
30
+ if (fileExists(packagePath)) {
31
+ const content = fs.readFileSync(packagePath, 'utf8');
32
+ return JSON.parse(content);
33
+ }
34
+ } catch {
35
+ // Ignore parse errors
36
+ }
37
+ return null;
38
+ }
39
+
40
+ /**
41
+ * Detect database configuration in a project
42
+ * @param {string} projectPath - Project root path
43
+ * @returns {object} Detection result
44
+ */
45
+ function detectDatabase(projectPath = process.cwd()) {
46
+ const detections = [];
47
+
48
+ // Check for Convex
49
+ const convexDir = path.join(projectPath, 'convex');
50
+ if (fileExists(convexDir)) {
51
+ const hasSchema = fileExists(path.join(convexDir, 'schema.ts')) ||
52
+ fileExists(path.join(convexDir, 'schema.js'));
53
+ detections.push({
54
+ type: 'convex',
55
+ confidence: 'high',
56
+ configPath: 'convex/',
57
+ hasSchema,
58
+ details: {
59
+ schemaFile: hasSchema ? 'convex/schema.ts' : null,
60
+ },
61
+ });
62
+ }
63
+
64
+ // Check for Supabase
65
+ const supabaseDir = path.join(projectPath, 'supabase');
66
+ if (fileExists(supabaseDir)) {
67
+ const hasMigrations = fileExists(path.join(supabaseDir, 'migrations'));
68
+ detections.push({
69
+ type: 'supabase',
70
+ confidence: 'high',
71
+ configPath: 'supabase/',
72
+ hasMigrations,
73
+ details: {
74
+ migrationsDir: hasMigrations ? 'supabase/migrations/' : null,
75
+ },
76
+ });
77
+ }
78
+
79
+ // Check for Prisma
80
+ const prismaDir = path.join(projectPath, 'prisma');
81
+ if (fileExists(prismaDir)) {
82
+ const hasSchema = fileExists(path.join(prismaDir, 'schema.prisma'));
83
+ detections.push({
84
+ type: 'prisma',
85
+ confidence: 'high',
86
+ configPath: 'prisma/',
87
+ hasSchema,
88
+ details: {
89
+ schemaFile: hasSchema ? 'prisma/schema.prisma' : null,
90
+ },
91
+ });
92
+ }
93
+
94
+ // Check for Drizzle config
95
+ const drizzleConfig = [
96
+ 'drizzle.config.ts',
97
+ 'drizzle.config.js',
98
+ 'drizzle.config.mjs',
99
+ ].find(f => fileExists(path.join(projectPath, f)));
100
+ if (drizzleConfig) {
101
+ detections.push({
102
+ type: 'drizzle',
103
+ confidence: 'high',
104
+ configPath: drizzleConfig,
105
+ details: {
106
+ configFile: drizzleConfig,
107
+ },
108
+ });
109
+ }
110
+
111
+ // Check package.json for database dependencies
112
+ const packageJson = readPackageJson(projectPath);
113
+ if (packageJson) {
114
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
115
+
116
+ // Convex from package.json
117
+ if (deps['convex'] && !detections.find(d => d.type === 'convex')) {
118
+ detections.push({
119
+ type: 'convex',
120
+ confidence: 'medium',
121
+ source: 'package.json',
122
+ details: {
123
+ version: deps['convex'],
124
+ },
125
+ });
126
+ }
127
+
128
+ // Supabase from package.json
129
+ if (deps['@supabase/supabase-js'] && !detections.find(d => d.type === 'supabase')) {
130
+ detections.push({
131
+ type: 'supabase',
132
+ confidence: 'medium',
133
+ source: 'package.json',
134
+ details: {
135
+ version: deps['@supabase/supabase-js'],
136
+ },
137
+ });
138
+ }
139
+
140
+ // Prisma from package.json
141
+ if ((deps['prisma'] || deps['@prisma/client']) && !detections.find(d => d.type === 'prisma')) {
142
+ detections.push({
143
+ type: 'prisma',
144
+ confidence: 'medium',
145
+ source: 'package.json',
146
+ details: {
147
+ version: deps['prisma'] || deps['@prisma/client'],
148
+ },
149
+ });
150
+ }
151
+
152
+ // Drizzle from package.json
153
+ if (deps['drizzle-orm'] && !detections.find(d => d.type === 'drizzle')) {
154
+ detections.push({
155
+ type: 'drizzle',
156
+ confidence: 'medium',
157
+ source: 'package.json',
158
+ details: {
159
+ version: deps['drizzle-orm'],
160
+ },
161
+ });
162
+ }
163
+
164
+ // MongoDB/Mongoose
165
+ if (deps['mongoose']) {
166
+ detections.push({
167
+ type: 'mongodb',
168
+ confidence: 'medium',
169
+ source: 'package.json',
170
+ details: {
171
+ client: 'mongoose',
172
+ version: deps['mongoose'],
173
+ },
174
+ });
175
+ }
176
+
177
+ if (deps['mongodb'] && !detections.find(d => d.type === 'mongodb')) {
178
+ detections.push({
179
+ type: 'mongodb',
180
+ confidence: 'medium',
181
+ source: 'package.json',
182
+ details: {
183
+ client: 'mongodb',
184
+ version: deps['mongodb'],
185
+ },
186
+ });
187
+ }
188
+
189
+ // Firebase/Firestore
190
+ if (deps['firebase'] || deps['firebase-admin']) {
191
+ detections.push({
192
+ type: 'firebase',
193
+ confidence: 'medium',
194
+ source: 'package.json',
195
+ details: {
196
+ client: deps['firebase'] ? 'firebase' : 'firebase-admin',
197
+ version: deps['firebase'] || deps['firebase-admin'],
198
+ },
199
+ });
200
+ }
201
+
202
+ // PostgreSQL (pg)
203
+ if (deps['pg'] && !detections.find(d => ['prisma', 'drizzle', 'supabase'].includes(d.type))) {
204
+ detections.push({
205
+ type: 'postgresql',
206
+ confidence: 'low',
207
+ source: 'package.json',
208
+ details: {
209
+ client: 'pg',
210
+ version: deps['pg'],
211
+ },
212
+ });
213
+ }
214
+
215
+ // MySQL
216
+ if (deps['mysql2'] || deps['mysql']) {
217
+ detections.push({
218
+ type: 'mysql',
219
+ confidence: 'low',
220
+ source: 'package.json',
221
+ details: {
222
+ client: deps['mysql2'] ? 'mysql2' : 'mysql',
223
+ version: deps['mysql2'] || deps['mysql'],
224
+ },
225
+ });
226
+ }
227
+ }
228
+
229
+ // Sort by confidence (high first)
230
+ const sortedDetections = detections.sort((a, b) => {
231
+ const confidenceOrder = { high: 2, medium: 1, low: 0 };
232
+ return (confidenceOrder[b.confidence] || 0) - (confidenceOrder[a.confidence] || 0);
233
+ });
234
+
235
+ return {
236
+ hasDatabase: sortedDetections.length > 0,
237
+ detections: sortedDetections,
238
+ primary: sortedDetections[0] || null,
239
+ };
240
+ }
241
+
242
+ module.exports = {
243
+ detect: detectDatabase,
244
+ detectDatabase,
245
+ };
@@ -13,7 +13,7 @@ class ExpoDetector extends BaseDetector {
13
13
  }
14
14
 
15
15
  get priority() {
16
- return 95; // High priority - specific framework
16
+ return 95; // Slightly lower than Next.js (100), but high enough for framework detection
17
17
  }
18
18
 
19
19
  /**
@@ -106,11 +106,11 @@ class ExpoDetector extends BaseDetector {
106
106
  }
107
107
 
108
108
  // Determine confidence based on what we found
109
- let confidence = 0.7; // Base confidence for React Native
109
+ let confidence = 0.85; // Base confidence for React Native
110
110
  if (results.isExpo) {
111
- confidence = 0.9; // Higher for Expo
111
+ confidence = 0.9; // Higher confidence for Expo projects
112
112
  if (results.config) {
113
- confidence = 0.95; // Even higher with config file
113
+ confidence = 0.95; // Even higher with config file (app.json)
114
114
  }
115
115
  }
116
116