@l4yercak3/cli 1.2.21 → 1.3.1

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.
@@ -5,198 +5,27 @@
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
- const { execSync } = require('child_process');
9
8
  const configManager = require('../config/config-manager');
10
9
  const backendClient = require('../api/backend-client');
11
10
  const projectDetector = require('../detectors');
12
11
  const fileGenerator = require('../generators');
12
+ const manifestGenerator = require('../generators/manifest-generator');
13
+ const pageDetector = require('../detectors/page-detector');
14
+ const { suggestMappings } = require('../detectors/mapping-suggestor');
13
15
  const { generateProjectPathHash } = require('../utils/file-utils');
16
+ const {
17
+ createOrganization,
18
+ generateNewApiKey,
19
+ checkGitStatusBeforeGeneration,
20
+ requireAuth,
21
+ } = require('../utils/init-helpers');
14
22
  const inquirer = require('inquirer');
15
23
  const chalk = require('chalk');
16
24
  const pkg = require('../../package.json');
17
25
  const { showMainMenu, executeMenuAction } = require('../utils/prompt-utils');
18
26
 
19
- /**
20
- * Helper function to create an organization
21
- */
22
- async function createOrganization(orgName) {
23
- console.log(chalk.gray(` Creating organization "${orgName}"...`));
24
- const newOrg = await backendClient.createOrganization(orgName);
25
- // Handle different response formats
26
- const organizationId = newOrg.organizationId || newOrg.id || newOrg.data?.organizationId || newOrg.data?.id;
27
- const organizationName = newOrg.name || orgName;
28
-
29
- if (!organizationId) {
30
- throw new Error('Organization ID not found in response. Please check backend API endpoint.');
31
- }
32
-
33
- console.log(chalk.green(` ✅ Organization created: ${organizationName}\n`));
34
- return { organizationId, organizationName };
35
- }
36
-
37
- /**
38
- * Helper function to generate a new API key
39
- */
40
- async function generateNewApiKey(organizationId) {
41
- console.log(chalk.gray(' Generating API key...'));
42
- const apiKeyResponse = await backendClient.generateApiKey(
43
- organizationId,
44
- 'CLI Generated Key',
45
- ['*']
46
- );
47
- // Handle different response formats
48
- const apiKey = apiKeyResponse.key || apiKeyResponse.apiKey || apiKeyResponse.data?.key || apiKeyResponse.data?.apiKey;
49
-
50
- if (!apiKey) {
51
- throw new Error('API key not found in response. Please check backend API endpoint.');
52
- }
53
-
54
- console.log(chalk.green(` ✅ API key generated\n`));
55
- return apiKey;
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
- const debug = process.env.L4YERCAK3_DEBUG;
94
-
95
- if (debug) {
96
- console.log('\n[DEBUG] Git status check:');
97
- console.log(` projectPath: "${projectPath}"`);
98
- }
99
-
100
- // Skip if not a git repo
101
- if (!isGitRepo(projectPath)) {
102
- if (debug) {
103
- console.log(' → Not a git repo, skipping check');
104
- }
105
- return true;
106
- }
107
-
108
- const status = getGitStatus(projectPath);
109
-
110
- if (debug) {
111
- console.log(` → Git status: "${status.substring(0, 100)}${status.length > 100 ? '...' : ''}"`);
112
- }
113
-
114
- // No uncommitted changes - proceed
115
- if (!status) {
116
- if (debug) {
117
- console.log(' → No uncommitted changes, proceeding');
118
- }
119
- return true;
120
- }
121
-
122
- // Count changes
123
- const changes = status.split('\n').filter(line => line.trim());
124
- const modifiedCount = changes.filter(line => line.startsWith(' M') || line.startsWith('M ')).length;
125
- const untrackedCount = changes.filter(line => line.startsWith('??')).length;
126
- const stagedCount = changes.filter(line => /^[MADRC]/.test(line)).length;
127
-
128
- console.log(chalk.yellow(' ⚠️ Uncommitted changes detected\n'));
129
-
130
- if (modifiedCount > 0) {
131
- console.log(chalk.gray(` ${modifiedCount} modified file(s)`));
132
- }
133
- if (untrackedCount > 0) {
134
- console.log(chalk.gray(` ${untrackedCount} untracked file(s)`));
135
- }
136
- if (stagedCount > 0) {
137
- console.log(chalk.gray(` ${stagedCount} staged file(s)`));
138
- }
139
-
140
- console.log('');
141
- console.log(chalk.gray(' We recommend committing your changes before generating'));
142
- console.log(chalk.gray(' new files, so you can easily revert if needed.\n'));
143
-
144
- const { action } = await inquirer.prompt([
145
- {
146
- type: 'list',
147
- name: 'action',
148
- message: 'How would you like to proceed?',
149
- choices: [
150
- {
151
- name: 'Continue anyway - I\'ll handle it later',
152
- value: 'continue',
153
- },
154
- {
155
- name: 'Commit changes now - Create a checkpoint commit',
156
- value: 'commit',
157
- },
158
- {
159
- name: 'Abort - I\'ll commit manually first',
160
- value: 'abort',
161
- },
162
- ],
163
- },
164
- ]);
165
-
166
- if (action === 'abort') {
167
- console.log(chalk.gray('\n No worries! Run "l4yercak3 spread" again after committing.\n'));
168
- return false;
169
- }
170
-
171
- if (action === 'commit') {
172
- try {
173
- // Stage all changes
174
- execSync('git add -A', { cwd: projectPath, stdio: 'pipe' });
175
-
176
- // Create commit
177
- const commitMessage = 'chore: checkpoint before L4YERCAK3 integration';
178
- execSync(`git commit -m "${commitMessage}"`, { cwd: projectPath, stdio: 'pipe' });
179
-
180
- console.log(chalk.green('\n ✅ Changes committed successfully'));
181
- console.log(chalk.gray(` Message: "${commitMessage}"`));
182
- console.log(chalk.gray(' You can revert with: git reset --soft HEAD~1\n'));
183
- } catch (error) {
184
- console.log(chalk.yellow('\n ⚠️ Could not create commit automatically'));
185
- console.log(chalk.gray(` ${error.message}`));
186
- console.log(chalk.gray(' Proceeding with file generation anyway...\n'));
187
- }
188
- }
189
-
190
- return true;
191
- }
192
-
193
27
  async function handleSpread() {
194
- // Check if logged in
195
- if (!configManager.isLoggedIn()) {
196
- console.log(chalk.yellow(' ⚠️ You must be logged in first'));
197
- console.log(chalk.gray('\n Run "l4yercak3 login" to authenticate\n'));
198
- process.exit(1);
199
- }
28
+ requireAuth(configManager);
200
29
 
201
30
  console.log(chalk.cyan(' 🍰 Setting up your Layer Cake integration...\n'));
202
31
 
@@ -293,6 +122,45 @@ async function handleSpread() {
293
122
  }
294
123
  }
295
124
 
125
+ // Display model detection
126
+ if (detection.models && detection.models.hasModels) {
127
+ console.log(chalk.green(` ✅ Detected ${detection.models.models.length} model(s)`));
128
+ for (const model of detection.models.models.slice(0, 5)) {
129
+ console.log(chalk.gray(` • ${model.name} (${model.source}) [${model.fields.length} fields]`));
130
+ }
131
+ if (detection.models.models.length > 5) {
132
+ console.log(chalk.gray(` ... and ${detection.models.models.length - 5} more`));
133
+ }
134
+ }
135
+
136
+ // Scan routes with HTTP methods
137
+ const detectedRoutes = pageDetector.detect(
138
+ detection.projectPath,
139
+ detection.framework.type,
140
+ detection.framework.metadata || {}
141
+ );
142
+ const apiRoutes = detectedRoutes.filter(r => r.pageType === 'api_route');
143
+ if (apiRoutes.length > 0) {
144
+ console.log(chalk.green(` ✅ Detected ${apiRoutes.length} API route(s)`));
145
+ for (const route of apiRoutes.slice(0, 5)) {
146
+ const methods = route.methods ? route.methods.join(', ') : 'GET, POST';
147
+ console.log(chalk.gray(` • ${route.path} [${methods}]`));
148
+ }
149
+ if (apiRoutes.length > 5) {
150
+ console.log(chalk.gray(` ... and ${apiRoutes.length - 5} more`));
151
+ }
152
+ }
153
+
154
+ // Compute suggested mappings
155
+ const models = detection.models ? detection.models.models : [];
156
+ const mappings = suggestMappings(models);
157
+ if (mappings.length > 0) {
158
+ console.log(chalk.green(` ✅ ${mappings.length} suggested mapping(s)`));
159
+ for (const mapping of mappings) {
160
+ console.log(chalk.gray(` • ${mapping.localModel} → ${mapping.platformType} (${mapping.confidence}% confidence)`));
161
+ }
162
+ }
163
+
296
164
  console.log('');
297
165
 
298
166
  // Step 1.5: Project name
@@ -553,22 +421,43 @@ async function handleSpread() {
553
421
  }
554
422
 
555
423
  // Step 4: Feature selection
424
+ // Auto-check features based on detected model mappings
425
+ const platformTypeToFeature = {
426
+ contact: 'crm',
427
+ booking: 'events',
428
+ event: 'events',
429
+ product: 'products',
430
+ invoice: 'invoicing',
431
+ project: 'projects',
432
+ form: 'forms',
433
+ certificate: 'certificates',
434
+ benefit: 'benefits',
435
+ };
436
+ const autoCheckedFeatures = new Set(['crm']); // CRM always default
437
+ for (const mapping of mappings) {
438
+ const feature = platformTypeToFeature[mapping.platformType];
439
+ if (feature) autoCheckedFeatures.add(feature);
440
+ }
441
+
556
442
  console.log(chalk.cyan(' ⚙️ Feature Selection\n'));
443
+ if (mappings.length > 0) {
444
+ console.log(chalk.gray(' Features pre-selected based on detected models:\n'));
445
+ }
557
446
  const { features } = await inquirer.prompt([
558
447
  {
559
448
  type: 'checkbox',
560
449
  name: 'features',
561
450
  message: 'Select features to enable:',
562
451
  choices: [
563
- { name: 'CRM (contacts, organizations)', value: 'crm', checked: true },
564
- { name: 'Events (event management, registrations)', value: 'events', checked: false },
565
- { name: 'Forms (form builder, submissions)', value: 'forms', checked: false },
566
- { name: 'Products (product catalog, inventory)', value: 'products', checked: false },
567
- { name: 'Checkout (cart, payments)', value: 'checkout', checked: false },
568
- { name: 'Invoicing (B2B/B2C invoices)', value: 'invoicing', checked: false },
569
- { name: 'Benefits (claims, commissions)', value: 'benefits', checked: false },
570
- { name: 'Certificates (CME, attendance)', value: 'certificates', checked: false },
571
- { name: 'Projects (task management)', value: 'projects', checked: false },
452
+ { name: 'CRM (contacts, organizations)', value: 'crm', checked: autoCheckedFeatures.has('crm') },
453
+ { name: 'Events (event management, registrations)', value: 'events', checked: autoCheckedFeatures.has('events') },
454
+ { name: 'Forms (form builder, submissions)', value: 'forms', checked: autoCheckedFeatures.has('forms') },
455
+ { name: 'Products (product catalog, inventory)', value: 'products', checked: autoCheckedFeatures.has('products') },
456
+ { name: 'Checkout (cart, payments)', value: 'checkout', checked: autoCheckedFeatures.has('checkout') },
457
+ { name: 'Invoicing (B2B/B2C invoices)', value: 'invoicing', checked: autoCheckedFeatures.has('invoicing') },
458
+ { name: 'Benefits (claims, commissions)', value: 'benefits', checked: autoCheckedFeatures.has('benefits') },
459
+ { name: 'Certificates (CME, attendance)', value: 'certificates', checked: autoCheckedFeatures.has('certificates') },
460
+ { name: 'Projects (task management)', value: 'projects', checked: autoCheckedFeatures.has('projects') },
572
461
  { name: 'OAuth Authentication', value: 'oauth', checked: false },
573
462
  ],
574
463
  },
@@ -772,6 +661,16 @@ async function handleSpread() {
772
661
  console.log(chalk.gray(` • ${path.relative(process.cwd(), generatedFiles.gitignore)} (updated)`));
773
662
  }
774
663
 
664
+ // Generate .l4yercak3.json manifest
665
+ const manifestPath = manifestGenerator.generate({
666
+ projectPath: detection.projectPath,
667
+ detection,
668
+ models,
669
+ routes: detectedRoutes,
670
+ mappings,
671
+ });
672
+ console.log(chalk.gray(` • ${path.relative(process.cwd(), manifestPath)}`));
673
+
775
674
  // Step 9: Register application with backend
776
675
  console.log(chalk.cyan('\n 🔗 Registering with L4YERCAK3...\n'));
777
676
 
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Sync Command
3
+ * Re-detects project structure and syncs the manifest with the L4YERCAK3 platform
4
+ *
5
+ * Flow:
6
+ * 1. Re-run all detectors (models, routes, pages)
7
+ * 2. Re-compute suggested mappings
8
+ * 3. Diff old manifest vs new detection results
9
+ * 4. Write updated .l4yercak3.json
10
+ * 5. Push manifest to platform if connected
11
+ */
12
+
13
+ const configManager = require('../config/config-manager');
14
+ const backendClient = require('../api/backend-client');
15
+ const projectDetector = require('../detectors');
16
+ const pageDetector = require('../detectors/page-detector');
17
+ const { suggestMappings } = require('../detectors/mapping-suggestor');
18
+ const manifestGenerator = require('../generators/manifest-generator');
19
+ const { requireAuth } = require('../utils/init-helpers');
20
+ const chalk = require('chalk');
21
+
22
+ async function handleSync() {
23
+ requireAuth(configManager);
24
+
25
+ console.log(chalk.cyan(' 🔄 Syncing project structure...\n'));
26
+
27
+ try {
28
+ const projectPath = process.cwd();
29
+
30
+ // Step 1: Load existing manifest
31
+ const existingManifest = manifestGenerator.loadManifest(projectPath);
32
+ if (!existingManifest) {
33
+ console.log(chalk.yellow(' ⚠️ No .l4yercak3.json manifest found.'));
34
+ console.log(chalk.gray(' Run "l4yercak3 init" first to scan your project.\n'));
35
+ process.exit(1);
36
+ }
37
+
38
+ console.log(chalk.gray(' Loading existing manifest...'));
39
+ console.log(chalk.gray(` Framework: ${existingManifest.framework}`));
40
+ console.log(chalk.gray(` Models: ${existingManifest.detectedModels?.length || 0}`));
41
+ console.log(chalk.gray(` Routes: ${existingManifest.detectedRoutes?.length || 0}`));
42
+ console.log(chalk.gray(` Last synced: ${existingManifest.lastSyncedAt || 'never'}\n`));
43
+
44
+ // Step 2: Re-run detection
45
+ console.log(chalk.gray(' 🔍 Re-scanning project...\n'));
46
+ const detection = projectDetector.detect(projectPath);
47
+
48
+ // Step 3: Re-scan routes with methods
49
+ const detectedRoutes = pageDetector.detect(
50
+ projectPath,
51
+ detection.framework.type,
52
+ detection.framework.metadata || {}
53
+ );
54
+
55
+ // Step 4: Re-compute mappings
56
+ const models = detection.models ? detection.models.models : [];
57
+ const mappings = suggestMappings(models);
58
+
59
+ // Step 5: Build new manifest data for diff
60
+ const newManifestData = {
61
+ version: existingManifest.version || '1.0.0',
62
+ framework: detection.framework.type || existingManifest.framework,
63
+ routerType: detection.framework.metadata?.routerType || existingManifest.routerType,
64
+ typescript: detection.framework.metadata?.hasTypeScript || existingManifest.typescript,
65
+ database: detection.database?.primary?.type || existingManifest.database,
66
+ detectedModels: models.map(m => ({
67
+ name: m.name,
68
+ source: m.source,
69
+ fields: m.fields || [],
70
+ })),
71
+ detectedRoutes: detectedRoutes
72
+ .filter(r => r.pageType === 'api_route')
73
+ .map(r => ({
74
+ path: r.path,
75
+ methods: r.methods || ['GET', 'POST'],
76
+ })),
77
+ suggestedMappings: mappings.map(m => ({
78
+ localModel: m.localModel,
79
+ platformType: m.platformType,
80
+ confidence: m.confidence,
81
+ })),
82
+ };
83
+
84
+ // Step 6: Compute diff
85
+ const diff = manifestGenerator.diff(existingManifest, newManifestData);
86
+
87
+ if (!diff.hasChanges) {
88
+ console.log(chalk.green(' ✅ No changes detected. Your manifest is up to date.\n'));
89
+ } else {
90
+ console.log(chalk.cyan(' 📊 Changes detected:\n'));
91
+
92
+ if (diff.modelsAdded.length > 0) {
93
+ console.log(chalk.green(` + ${diff.modelsAdded.length} new model(s)`));
94
+ for (const m of diff.modelsAdded) {
95
+ console.log(chalk.gray(` • ${m.name} (${m.source})`));
96
+ }
97
+ }
98
+
99
+ if (diff.modelsRemoved.length > 0) {
100
+ console.log(chalk.red(` - ${diff.modelsRemoved.length} removed model(s)`));
101
+ for (const m of diff.modelsRemoved) {
102
+ console.log(chalk.gray(` • ${m.name} (${m.source})`));
103
+ }
104
+ }
105
+
106
+ if (diff.routesAdded.length > 0) {
107
+ console.log(chalk.green(` + ${diff.routesAdded.length} new route(s)`));
108
+ for (const r of diff.routesAdded) {
109
+ console.log(chalk.gray(` • ${r.path} [${(r.methods || []).join(', ')}]`));
110
+ }
111
+ }
112
+
113
+ if (diff.routesRemoved.length > 0) {
114
+ console.log(chalk.red(` - ${diff.routesRemoved.length} removed route(s)`));
115
+ for (const r of diff.routesRemoved) {
116
+ console.log(chalk.gray(` • ${r.path}`));
117
+ }
118
+ }
119
+
120
+ console.log('');
121
+ }
122
+
123
+ // Step 7: Write updated manifest
124
+ manifestGenerator.generate({
125
+ projectPath,
126
+ detection,
127
+ models,
128
+ routes: detectedRoutes,
129
+ mappings,
130
+ });
131
+ console.log(chalk.green(` ✅ Manifest updated: .l4yercak3.json`));
132
+
133
+ // Step 8: Summary
134
+ console.log(chalk.gray(` Models: ${newManifestData.detectedModels.length}`));
135
+ console.log(chalk.gray(` Routes: ${newManifestData.detectedRoutes.length}`));
136
+ console.log(chalk.gray(` Mappings: ${newManifestData.suggestedMappings.length}\n`));
137
+
138
+ // Step 9: Push to platform if connected
139
+ const projectConfig = configManager.getProjectConfig(projectPath);
140
+ if (projectConfig && projectConfig.applicationId) {
141
+ console.log(chalk.gray(' Syncing to platform...'));
142
+ try {
143
+ const updatedManifest = manifestGenerator.loadManifest(projectPath);
144
+ await backendClient.syncManifest(projectConfig.applicationId, updatedManifest);
145
+ console.log(chalk.green(' ✅ Manifest synced to L4YERCAK3 platform\n'));
146
+ } catch (syncError) {
147
+ console.log(chalk.yellow(` ⚠️ Could not sync to platform: ${syncError.message}`));
148
+ console.log(chalk.gray(' The manifest was updated locally. Platform sync may not be available yet.\n'));
149
+ }
150
+ } else {
151
+ console.log(chalk.gray(' ℹ️ Not connected to platform. Run "l4yercak3 connect" to enable remote sync.\n'));
152
+ }
153
+
154
+ console.log(chalk.cyan(' 🎉 Sync complete!\n'));
155
+
156
+ } catch (error) {
157
+ console.error(chalk.red(`\n ❌ Error: ${error.message}\n`));
158
+ if (process.env.L4YERCAK3_DEBUG && error.stack) {
159
+ console.error(chalk.gray(error.stack));
160
+ }
161
+ process.exit(1);
162
+ }
163
+ }
164
+
165
+ module.exports = {
166
+ command: 'sync',
167
+ description: 'Re-detect project structure and sync with L4YERCAK3',
168
+ handler: handleSync,
169
+ };
@@ -11,6 +11,7 @@ const githubDetector = require('./github-detector');
11
11
  const apiClientDetector = require('./api-client-detector');
12
12
  const oauthDetector = require('./oauth-detector');
13
13
  const databaseDetector = require('./database-detector');
14
+ const modelDetector = require('./model-detector');
14
15
 
15
16
  class ProjectDetector {
16
17
  /**
@@ -28,6 +29,7 @@ class ProjectDetector {
28
29
  const apiClientInfo = apiClientDetector.detect(projectPath);
29
30
  const oauthInfo = oauthDetector.detect(projectPath);
30
31
  const databaseInfo = databaseDetector.detect(projectPath);
32
+ const modelInfo = modelDetector.detect(projectPath);
31
33
 
32
34
  // Get detector instance if we have a match
33
35
  const detector = frameworkDetection.detected
@@ -49,6 +51,7 @@ class ProjectDetector {
49
51
  apiClient: apiClientInfo,
50
52
  oauth: oauthInfo,
51
53
  database: databaseInfo,
54
+ models: modelInfo,
52
55
 
53
56
  // Raw detection results (for debugging)
54
57
  _raw: {
@@ -60,6 +63,16 @@ class ProjectDetector {
60
63
  };
61
64
  }
62
65
 
66
+ /**
67
+ * Detect models/types in a project
68
+ *
69
+ * @param {string} projectPath - Path to project directory
70
+ * @returns {object} Model detection results
71
+ */
72
+ detectModels(projectPath = process.cwd()) {
73
+ return modelDetector.detect(projectPath);
74
+ }
75
+
63
76
  /**
64
77
  * Detect database configuration in a project
65
78
  *
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Mapping Suggestor
3
+ * Suggests mappings between local data models and L4YERCAK3 platform types
4
+ * Pure logic module — no I/O
5
+ */
6
+
7
+ /**
8
+ * Platform types supported by L4YERCAK3
9
+ */
10
+ const PLATFORM_TYPES = [
11
+ 'contact',
12
+ 'booking',
13
+ 'event',
14
+ 'product',
15
+ 'invoice',
16
+ 'project',
17
+ 'form',
18
+ 'certificate',
19
+ 'benefit',
20
+ ];
21
+
22
+ /**
23
+ * Mapping rules: keyword patterns → platform type + base confidence
24
+ * Patterns are matched against lowercase model names
25
+ */
26
+ const MAPPING_RULES = [
27
+ {
28
+ patterns: ['user', 'customer', 'client', 'member', 'contact', 'person', 'lead', 'subscriber', 'account', 'profile'],
29
+ type: 'contact',
30
+ confidence: 90,
31
+ },
32
+ {
33
+ patterns: ['appointment', 'booking', 'reservation', 'schedule', 'slot'],
34
+ type: 'booking',
35
+ confidence: 85,
36
+ },
37
+ {
38
+ patterns: ['event', 'meeting', 'conference', 'webinar', 'session', 'workshop'],
39
+ type: 'event',
40
+ confidence: 85,
41
+ },
42
+ {
43
+ patterns: ['product', 'item', 'sku', 'merchandise', 'listing', 'catalog'],
44
+ type: 'product',
45
+ confidence: 85,
46
+ },
47
+ {
48
+ patterns: ['invoice', 'bill', 'receipt', 'charge', 'payment', 'order', 'transaction'],
49
+ type: 'invoice',
50
+ confidence: 80,
51
+ },
52
+ {
53
+ patterns: ['project', 'task', 'ticket', 'issue', 'sprint', 'milestone'],
54
+ type: 'project',
55
+ confidence: 80,
56
+ },
57
+ {
58
+ patterns: ['form', 'survey', 'questionnaire', 'submission', 'response'],
59
+ type: 'form',
60
+ confidence: 80,
61
+ },
62
+ {
63
+ patterns: ['certificate', 'credential', 'badge', 'diploma', 'cme', 'license'],
64
+ type: 'certificate',
65
+ confidence: 75,
66
+ },
67
+ {
68
+ patterns: ['benefit', 'claim', 'commission', 'payout', 'reward', 'bonus'],
69
+ type: 'benefit',
70
+ confidence: 75,
71
+ },
72
+ ];
73
+
74
+ /**
75
+ * Suggest platform type mappings for detected models
76
+ * @param {Array<{name: string, source: string, fields: string[]}>} models
77
+ * @returns {Array<{localModel: string, platformType: string, confidence: number}>}
78
+ */
79
+ function suggestMappings(models) {
80
+ if (!models || models.length === 0) return [];
81
+
82
+ const mappings = [];
83
+
84
+ for (const model of models) {
85
+ const lowerName = model.name.toLowerCase();
86
+ let bestMatch = null;
87
+
88
+ for (const rule of MAPPING_RULES) {
89
+ for (const pattern of rule.patterns) {
90
+ if (lowerName.includes(pattern)) {
91
+ // Exact match gets higher confidence
92
+ const isExact = lowerName === pattern;
93
+ const confidence = isExact ? Math.min(rule.confidence + 5, 99) : rule.confidence;
94
+
95
+ if (!bestMatch || confidence > bestMatch.confidence) {
96
+ bestMatch = {
97
+ localModel: model.name,
98
+ platformType: rule.type,
99
+ confidence,
100
+ };
101
+ }
102
+ break; // Only need first pattern match per rule
103
+ }
104
+ }
105
+ }
106
+
107
+ if (bestMatch) {
108
+ mappings.push(bestMatch);
109
+ }
110
+ }
111
+
112
+ return mappings;
113
+ }
114
+
115
+ module.exports = {
116
+ suggestMappings,
117
+ PLATFORM_TYPES,
118
+ MAPPING_RULES,
119
+ };