@aifabrix/builder 2.32.2 → 2.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/.cursor/rules/project-rules.mdc +8 -0
  2. package/README.md +36 -8
  3. package/bin/aifabrix.js +6 -8
  4. package/integration/hubspot/README.md +8 -7
  5. package/integration/hubspot/companies.json +2048 -0
  6. package/integration/hubspot/create-hubspot.js +665 -0
  7. package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
  8. package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
  9. package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
  10. package/integration/hubspot/hubspot-deploy.json +832 -81
  11. package/integration/hubspot/hubspot-system.json +99 -0
  12. package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
  13. package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
  14. package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
  15. package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
  16. package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
  17. package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
  18. package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
  19. package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
  20. package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
  21. package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
  22. package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
  23. package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
  24. package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
  25. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
  26. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
  27. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
  28. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
  29. package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
  30. package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
  31. package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
  32. package/integration/hubspot/test-dataplane-down-tests.js +419 -0
  33. package/integration/hubspot/test-dataplane-down.js +157 -0
  34. package/integration/hubspot/test.js +1517 -0
  35. package/integration/hubspot/variables.yaml +4 -4
  36. package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
  37. package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
  38. package/lib/api/applications.api.js +1 -0
  39. package/lib/api/index.js +10 -5
  40. package/lib/api/types/wizard.types.js +176 -38
  41. package/lib/api/wizard.api.js +207 -38
  42. package/lib/app/deploy.js +116 -54
  43. package/lib/app/display.js +6 -5
  44. package/lib/app/dockerfile.js +2 -1
  45. package/lib/app/list.js +78 -37
  46. package/lib/app/prompts.js +9 -5
  47. package/lib/app/readme.js +41 -112
  48. package/lib/app/register.js +44 -9
  49. package/lib/app/rotate-secret.js +50 -32
  50. package/lib/cli.js +243 -65
  51. package/lib/commands/app.js +4 -9
  52. package/lib/commands/auth-config.js +125 -0
  53. package/lib/commands/auth-status.js +261 -0
  54. package/lib/commands/datasource.js +3 -6
  55. package/lib/commands/login-credentials.js +4 -4
  56. package/lib/commands/login-device.js +43 -29
  57. package/lib/commands/login.js +22 -13
  58. package/lib/commands/wizard-config-normalizer.js +92 -0
  59. package/lib/commands/wizard-core.js +515 -0
  60. package/lib/commands/wizard-dataplane.js +122 -0
  61. package/lib/commands/wizard-headless.js +115 -0
  62. package/lib/commands/wizard.js +129 -357
  63. package/lib/core/config.js +46 -0
  64. package/lib/core/secrets.js +3 -22
  65. package/lib/core/templates-env.js +1 -1
  66. package/lib/datasource/deploy.js +34 -23
  67. package/lib/datasource/list.js +8 -6
  68. package/lib/deployment/deployer.js +25 -0
  69. package/lib/deployment/environment.js +10 -13
  70. package/lib/external-system/delete.js +151 -0
  71. package/lib/external-system/deploy.js +54 -378
  72. package/lib/external-system/download-helpers.js +45 -65
  73. package/lib/external-system/download.js +34 -13
  74. package/lib/external-system/generator.js +11 -7
  75. package/lib/external-system/test-auth.js +5 -3
  76. package/lib/generator/builders.js +3 -1
  77. package/lib/generator/external-controller-manifest.js +157 -0
  78. package/lib/generator/external-schema-utils.js +236 -0
  79. package/lib/generator/external.js +55 -3
  80. package/lib/generator/index.js +22 -10
  81. package/lib/generator/wizard-prompts.js +33 -10
  82. package/lib/generator/wizard.js +69 -86
  83. package/lib/infrastructure/compose.js +100 -0
  84. package/lib/infrastructure/helpers.js +139 -0
  85. package/lib/infrastructure/index.js +52 -311
  86. package/lib/infrastructure/services.js +168 -0
  87. package/lib/schema/application-schema.json +24 -5
  88. package/lib/schema/external-datasource.schema.json +303 -17
  89. package/lib/schema/external-system.schema.json +1 -1
  90. package/lib/schema/wizard-config.schema.json +234 -0
  91. package/lib/utils/api.js +37 -42
  92. package/lib/utils/app-existence.js +42 -0
  93. package/lib/utils/app-register-config.js +7 -2
  94. package/lib/utils/app-register-display.js +2 -1
  95. package/lib/utils/auth-config-validator.js +92 -0
  96. package/lib/utils/cli-utils.js +3 -1
  97. package/lib/utils/command-header.js +43 -0
  98. package/lib/utils/compose-generator.js +113 -70
  99. package/lib/utils/controller-url.js +115 -0
  100. package/lib/utils/dataplane-health.js +115 -0
  101. package/lib/utils/dataplane-resolver.js +29 -0
  102. package/lib/utils/dev-config.js +6 -2
  103. package/lib/utils/env-copy.js +2 -1
  104. package/lib/utils/env-map.js +2 -1
  105. package/lib/utils/env-ports.js +2 -1
  106. package/lib/utils/env-template.js +1 -1
  107. package/lib/utils/error-formatter.js +149 -28
  108. package/lib/utils/external-readme.js +125 -0
  109. package/lib/utils/help-builder.js +190 -0
  110. package/lib/utils/infra-status.js +13 -3
  111. package/lib/utils/paths.js +17 -2
  112. package/lib/utils/port-resolver.js +111 -0
  113. package/lib/utils/secrets-helpers.js +3 -15
  114. package/lib/utils/secrets-utils.js +2 -2
  115. package/lib/utils/token-manager.js +69 -4
  116. package/lib/utils/variable-transformer.js +7 -2
  117. package/lib/validation/external-manifest-validator.js +202 -0
  118. package/lib/validation/validate-display.js +406 -0
  119. package/lib/validation/validate.js +159 -123
  120. package/lib/validation/validator.js +38 -4
  121. package/lib/validation/wizard-config-validator.js +267 -0
  122. package/package.json +4 -2
  123. package/templates/applications/README.md.hbs +19 -17
  124. package/templates/applications/miso-controller/env.template +1 -1
  125. package/templates/applications/miso-controller/rbac.yaml +7 -7
  126. package/templates/external-system/README.md.hbs +99 -0
  127. package/templates/external-system/external-system.json.hbs +1 -1
  128. package/templates/infra/compose.yaml.hbs +35 -0
  129. package/templates/python/docker-compose.hbs +26 -0
  130. package/templates/typescript/docker-compose.hbs +26 -0
@@ -0,0 +1,665 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable max-lines */
3
+ /**
4
+ * HubSpot Integration Creation Helper Script
5
+ *
6
+ * Creates a new HubSpot integration using the wizard and prepares files for manual editing
7
+ *
8
+ * @fileoverview Helper script for creating HubSpot integrations
9
+ * @author AI Fabrix Team
10
+ * @version 2.0.0
11
+ */
12
+ 'use strict';
13
+
14
+ const fs = require('fs').promises;
15
+ const path = require('path');
16
+ const { execFile } = require('child_process');
17
+ const { promisify } = require('util');
18
+ const yaml = require('js-yaml');
19
+ const chalk = require('chalk');
20
+ const logger = require('../../lib/utils/logger');
21
+
22
+ const execFileAsync = promisify(execFile);
23
+
24
+ const DEFAULT_CONTROLLER_URL = process.env.CONTROLLER_URL || 'http://localhost:3110';
25
+ const DEFAULT_ENVIRONMENT = process.env.ENVIRONMENT || 'miso';
26
+ const DEFAULT_OPENAPI_FILE = process.env.HUBSPOT_OPENAPI_FILE ||
27
+ path.join(process.cwd(), 'integration', 'hubspot', 'companies.json');
28
+
29
+ /**
30
+ * Prints usage information
31
+ * @function printUsage
32
+ * @returns {void}
33
+ */
34
+ function printUsage() {
35
+ logger.log([
36
+ 'Usage:',
37
+ ' node integration/hubspot/create-hubspot.js [options]',
38
+ '',
39
+ 'Options:',
40
+ ' --name <name> Application name (required)',
41
+ ' --openapi <path> Path to OpenAPI file (default: env HUBSPOT_OPENAPI_FILE)',
42
+ ' --output <dir> Output directory for files (default: integration/<name>)',
43
+ ' --controller <url> Controller URL (default: env CONTROLLER_URL)',
44
+ ' --environment <env> Environment name (default: env ENVIRONMENT)',
45
+ ' --dataplane <url> Dataplane URL (optional, will be auto-discovered)',
46
+ ' --keep-wizard-files Keep wizard-generated files in integration/ directory',
47
+ ' --help Show this help message',
48
+ '',
49
+ 'Examples:',
50
+ ' node integration/hubspot/create-hubspot.js --name my-hubspot',
51
+ ' node integration/hubspot/create-hubspot.js --name my-hubspot --output ~/my-project',
52
+ ' node integration/hubspot/create-hubspot.js --name my-hubspot --openapi /path/to/openapi.json'
53
+ ].join('\n'));
54
+ }
55
+
56
+ /**
57
+ * Parses command line arguments
58
+ * @function parseArgs
59
+ * @param {string[]} argv - Command line arguments
60
+ * @returns {Object} Parsed arguments
61
+ */
62
+ function parseArgs(argv) {
63
+ const args = {
64
+ name: null,
65
+ openapi: DEFAULT_OPENAPI_FILE,
66
+ output: null,
67
+ controller: DEFAULT_CONTROLLER_URL,
68
+ environment: DEFAULT_ENVIRONMENT,
69
+ dataplane: process.env.DATAPLANE_URL || null,
70
+ keepWizardFiles: false,
71
+ help: false
72
+ };
73
+
74
+ const flagMap = {
75
+ '--name': 'name',
76
+ '--openapi': 'openapi',
77
+ '--output': 'output',
78
+ '--controller': 'controller',
79
+ '--environment': 'environment',
80
+ '--dataplane': 'dataplane'
81
+ };
82
+
83
+ for (let i = 2; i < argv.length; i += 1) {
84
+ const arg = argv[i];
85
+ const argKey = flagMap[arg];
86
+
87
+ if (argKey && argv[i + 1]) {
88
+ args[argKey] = argv[i + 1];
89
+ i += 1;
90
+ continue;
91
+ }
92
+
93
+ if (arg === '--keep-wizard-files') {
94
+ args.keepWizardFiles = true;
95
+ continue;
96
+ }
97
+
98
+ if (arg === '--help' || arg === '-h') {
99
+ args.help = true;
100
+ }
101
+ }
102
+
103
+ return args;
104
+ }
105
+
106
+ /**
107
+ * Logs info message
108
+ * @function logInfo
109
+ * @param {string} message - Message to log
110
+ * @returns {void}
111
+ */
112
+ function logInfo(message) {
113
+ logger.log(chalk.cyan(message));
114
+ }
115
+
116
+ /**
117
+ * Logs success message
118
+ * @function logSuccess
119
+ * @param {string} message - Message to log
120
+ * @returns {void}
121
+ */
122
+ function logSuccess(message) {
123
+ logger.log(chalk.green(message));
124
+ }
125
+
126
+ /**
127
+ * Logs error message
128
+ * @function logError
129
+ * @param {string} message - Message to log
130
+ * @returns {void}
131
+ */
132
+ function logError(message) {
133
+ logger.error(chalk.red(message));
134
+ }
135
+
136
+ /**
137
+ * Checks if file exists
138
+ * @async
139
+ * @function fileExists
140
+ * @param {string} filePath - File path to check
141
+ * @returns {Promise<boolean>} True if file exists
142
+ */
143
+ async function fileExists(filePath) {
144
+ try {
145
+ await fs.access(filePath);
146
+ return true;
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Ensures directory exists
154
+ * @async
155
+ * @function ensureDir
156
+ * @param {string} dirPath - Directory path
157
+ * @returns {Promise<void>} Resolves when directory is created
158
+ */
159
+ async function ensureDir(dirPath) {
160
+ await fs.mkdir(dirPath, { recursive: true });
161
+ }
162
+
163
+ /**
164
+ * Creates wizard configuration file
165
+ * @async
166
+ * @function createWizardConfig
167
+ * @param {string} appName - Application name
168
+ * @param {string} openapiFile - OpenAPI file path
169
+ * @param {string} controllerUrl - Controller URL
170
+ * @param {string} environment - Environment name
171
+ * @returns {Promise<string>} Path to created config file
172
+ */
173
+ async function createWizardConfig(appName, openapiFile, controllerUrl, environment) {
174
+ const configDir = path.join(process.cwd(), 'integration', 'hubspot');
175
+ await ensureDir(configDir);
176
+ const configPath = path.join(configDir, `wizard-${appName}.yaml`);
177
+
178
+ const config = {
179
+ appName,
180
+ mode: 'create-system',
181
+ source: {
182
+ type: 'openapi-file',
183
+ filePath: openapiFile
184
+ },
185
+ credential: {
186
+ action: 'skip'
187
+ },
188
+ preferences: {
189
+ intent: `HubSpot CRM integration for ${appName}`,
190
+ fieldOnboardingLevel: 'full',
191
+ enableOpenAPIGeneration: true,
192
+ enableABAC: true,
193
+ enableRBAC: false
194
+ },
195
+ deployment: {
196
+ controller: controllerUrl,
197
+ environment
198
+ }
199
+ };
200
+
201
+ const yamlContent = yaml.dump(config, { lineWidth: -1, noRefs: true });
202
+ await fs.writeFile(configPath, yamlContent, 'utf8');
203
+ return configPath;
204
+ }
205
+
206
+ /**
207
+ * Tests a single endpoint for reachability
208
+ * @async
209
+ * @function testEndpoint
210
+ * @param {string} testUrl - URL to test
211
+ * @param {number} timeoutMs - Timeout in milliseconds
212
+ * @returns {Promise<boolean>} True if endpoint is reachable
213
+ */
214
+ async function testEndpoint(testUrl, timeoutMs) {
215
+ try {
216
+ const controller = new AbortController();
217
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
218
+
219
+ const response = await Promise.race([
220
+ fetch(testUrl, {
221
+ method: 'GET',
222
+ signal: controller.signal,
223
+ headers: { 'Accept': 'application/json' }
224
+ }),
225
+ new Promise((_, reject) =>
226
+ setTimeout(() => reject(new Error('Timeout')), timeoutMs)
227
+ )
228
+ ]).catch(() => null);
229
+
230
+ clearTimeout(timeoutId);
231
+
232
+ // If we get any response (even 404 or 401), the service is reachable
233
+ // 401 is OK because it means the API is working, just needs auth
234
+ if (response && (response.ok || response.status === 404 || response.status === 401)) {
235
+ return true;
236
+ }
237
+
238
+ // If we get a 500 or other server error, the service is up but broken
239
+ // Still consider it "reachable" - the wizard will handle the actual error
240
+ return response && response.status >= 500;
241
+ } catch (error) {
242
+ // Timeout or abort means endpoint is not reachable
243
+ return false;
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Checks if dataplane URL is reachable and API is functional
249
+ * @async
250
+ * @function checkDataplaneHealth
251
+ * @param {string} dataplaneUrl - Dataplane URL to check
252
+ * @param {number} timeoutMs - Timeout in milliseconds (default: 5000)
253
+ * @returns {Promise<boolean>} True if dataplane is reachable and functional
254
+ */
255
+ async function checkDataplaneHealth(dataplaneUrl, timeoutMs = 5000) {
256
+ if (!dataplaneUrl) {
257
+ return false;
258
+ }
259
+
260
+ const baseUrl = dataplaneUrl.replace(/\/$/, '');
261
+ const endpointsToTest = ['/health', '/api/v1/health', '/api/health', ''];
262
+
263
+ for (const endpoint of endpointsToTest) {
264
+ const testUrl = endpoint ? `${baseUrl}${endpoint}` : baseUrl;
265
+ const isReachable = await testEndpoint(testUrl, timeoutMs);
266
+ if (isReachable) {
267
+ return true;
268
+ }
269
+ }
270
+
271
+ return false;
272
+ }
273
+
274
+ /**
275
+ * Validates dataplane health before running wizard
276
+ * @async
277
+ * @function validateDataplaneHealth
278
+ * @param {string} dataplaneUrl - Dataplane URL to check
279
+ * @returns {Promise<Object|null>} Error object if unhealthy, null if healthy
280
+ */
281
+ async function validateDataplaneHealth(dataplaneUrl) {
282
+ logInfo('Checking dataplane connectivity...');
283
+ try {
284
+ const isHealthy = await checkDataplaneHealth(dataplaneUrl, 5000);
285
+ if (!isHealthy) {
286
+ return {
287
+ success: false,
288
+ stdout: '',
289
+ stderr: '',
290
+ error: `Dataplane is not reachable at ${dataplaneUrl}.\n\n` +
291
+ 'Please ensure the dataplane service is running and accessible, then try again.\n' +
292
+ `You can check dataplane status with: curl ${dataplaneUrl}/health`
293
+ };
294
+ }
295
+ logSuccess('✓ Dataplane is reachable');
296
+ return null;
297
+ } catch (error) {
298
+ return {
299
+ success: false,
300
+ stdout: '',
301
+ stderr: '',
302
+ error: `Failed to check dataplane health: ${error.message}\n\n` +
303
+ `The dataplane at ${dataplaneUrl} may be down or unreachable.`
304
+ };
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Builds wizard command arguments
310
+ * @function buildWizardArgs
311
+ * @param {string} configPath - Path to wizard config file
312
+ * @param {string} controllerUrl - Controller URL
313
+ * @param {string} environment - Environment name
314
+ * @param {string|null} dataplaneUrl - Optional dataplane URL
315
+ * @returns {string[]} Command arguments
316
+ */
317
+ function buildWizardArgs(configPath, controllerUrl, environment, dataplaneUrl) {
318
+ const args = [
319
+ 'bin/aifabrix.js',
320
+ 'wizard',
321
+ '--config',
322
+ configPath,
323
+ '--controller',
324
+ controllerUrl,
325
+ '--environment',
326
+ environment
327
+ ];
328
+
329
+ if (dataplaneUrl) {
330
+ args.push('--dataplane', dataplaneUrl);
331
+ }
332
+
333
+ return args;
334
+ }
335
+
336
+ /**
337
+ * Handles wizard command execution errors
338
+ * @function handleWizardError
339
+ * @param {Error} error - Error object
340
+ * @param {string|null} dataplaneUrl - Dataplane URL
341
+ * @returns {Object} Error result object
342
+ */
343
+ function handleWizardError(error, dataplaneUrl) {
344
+ const baseResult = {
345
+ success: false,
346
+ stdout: error.stdout || '',
347
+ stderr: error.stderr || ''
348
+ };
349
+
350
+ // Check for timeout
351
+ if (error.code === 'ETIMEDOUT' || error.signal === 'SIGTERM') {
352
+ const dataplaneInfo = dataplaneUrl ? `dataplane at ${dataplaneUrl}` : 'dataplane (auto-discovered)';
353
+ return {
354
+ ...baseResult,
355
+ error: `Wizard command timed out after 2 minutes. This usually indicates the ${dataplaneInfo} is down or unreachable.\n\n` +
356
+ 'Please ensure the dataplane service is running and accessible, then try again.'
357
+ };
358
+ }
359
+
360
+ // Check for connection errors
361
+ const errorOutput = (error.stdout || '') + (error.stderr || '') + (error.message || '');
362
+ const lowerOutput = errorOutput.toLowerCase();
363
+ const isConnectionError = lowerOutput.includes('dataplane') ||
364
+ lowerOutput.includes('connection') ||
365
+ lowerOutput.includes('timeout') ||
366
+ lowerOutput.includes('econnrefused');
367
+
368
+ if (isConnectionError) {
369
+ return {
370
+ ...baseResult,
371
+ error: `Wizard failed: ${error.message}\n\nThis may be due to dataplane connectivity issues. Ensure the dataplane service is running at ${dataplaneUrl || 'the discovered URL'}.`
372
+ };
373
+ }
374
+
375
+ return { ...baseResult, error: error.message };
376
+ }
377
+
378
+ /**
379
+ * Runs wizard command
380
+ * @async
381
+ * @function runWizard
382
+ * @param {string} configPath - Path to wizard config file
383
+ * @param {string} controllerUrl - Controller URL
384
+ * @param {string} environment - Environment name
385
+ * @param {string|null} dataplaneUrl - Optional dataplane URL
386
+ * @returns {Promise<Object>} Command result object
387
+ */
388
+ async function runWizard(configPath, controllerUrl, environment, dataplaneUrl) {
389
+ if (dataplaneUrl) {
390
+ const healthError = await validateDataplaneHealth(dataplaneUrl);
391
+ if (healthError) {
392
+ return healthError;
393
+ }
394
+ } else {
395
+ logInfo('Note: No dataplane URL provided. Wizard will attempt to discover it from controller.');
396
+ logInfo('If dataplane discovery fails or hangs, provide --dataplane <url> explicitly.');
397
+ }
398
+
399
+ const args = buildWizardArgs(configPath, controllerUrl, environment, dataplaneUrl);
400
+
401
+ try {
402
+ const result = await execFileAsync('node', args, {
403
+ cwd: process.cwd(),
404
+ env: { ...process.env },
405
+ maxBuffer: 10 * 1024 * 1024,
406
+ timeout: 2 * 60 * 1000
407
+ });
408
+ return { success: true, stdout: result.stdout || '', stderr: result.stderr || '' };
409
+ } catch (error) {
410
+ return handleWizardError(error, dataplaneUrl);
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Lists files in directory
416
+ * @async
417
+ * @function listFiles
418
+ * @param {string} dirPath - Directory path
419
+ * @returns {Promise<string[]>} Array of file names
420
+ */
421
+ async function listFiles(dirPath) {
422
+ try {
423
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
424
+ return entries
425
+ .filter(entry => entry.isFile())
426
+ .map(entry => entry.name);
427
+ } catch {
428
+ return [];
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Copies file
434
+ * @async
435
+ * @function copyFile
436
+ * @param {string} src - Source file path
437
+ * @param {string} dest - Destination file path
438
+ * @returns {Promise<void>} Resolves when file is copied
439
+ */
440
+ async function copyFile(src, dest) {
441
+ await ensureDir(path.dirname(dest));
442
+ await fs.copyFile(src, dest);
443
+ }
444
+
445
+ /**
446
+ * Copies directory contents
447
+ * @async
448
+ * @function copyDirectoryContents
449
+ * @param {string} srcDir - Source directory
450
+ * @param {string} destDir - Destination directory
451
+ * @param {string[]} filePatterns - File patterns to match (e.g., ['*.json', '*.yaml'])
452
+ * @returns {Promise<string[]>} Array of copied file names
453
+ */
454
+ async function copyDirectoryContents(srcDir, destDir, filePatterns = null) {
455
+ await ensureDir(destDir);
456
+ const files = await listFiles(srcDir);
457
+ const copiedFiles = [];
458
+
459
+ for (const fileName of files) {
460
+ // Filter by patterns if provided
461
+ if (filePatterns) {
462
+ const matches = filePatterns.some(pattern => {
463
+ if (pattern.startsWith('*.')) {
464
+ const ext = pattern.slice(1);
465
+ return fileName.endsWith(ext);
466
+ }
467
+ return fileName.includes(pattern);
468
+ });
469
+ if (!matches) {
470
+ continue;
471
+ }
472
+ }
473
+
474
+ const srcPath = path.join(srcDir, fileName);
475
+ const destPath = path.join(destDir, fileName);
476
+ await copyFile(srcPath, destPath);
477
+ copiedFiles.push(fileName);
478
+ }
479
+
480
+ return copiedFiles;
481
+ }
482
+
483
+ /**
484
+ * Validates command line arguments
485
+ * @function validateArgs
486
+ * @param {Object} args - Parsed arguments
487
+ * @returns {void}
488
+ * @throws {Error} If validation fails
489
+ */
490
+ function validateArgs(args) {
491
+ if (!args.name) {
492
+ throw new Error('--name is required');
493
+ }
494
+
495
+ if (!/^[a-z0-9-_]+$/.test(args.name)) {
496
+ throw new Error('App name must contain only lowercase letters, numbers, hyphens, and underscores');
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Validates OpenAPI file exists
502
+ * @async
503
+ * @function validateOpenApiFile
504
+ * @param {string} openapiPath - OpenAPI file path
505
+ * @returns {Promise<void>} Resolves if file exists
506
+ * @throws {Error} If file doesn't exist
507
+ */
508
+ async function validateOpenApiFile(openapiPath) {
509
+ if (!(await fileExists(openapiPath))) {
510
+ throw new Error(`OpenAPI file not found: ${openapiPath}`);
511
+ }
512
+ }
513
+
514
+ /**
515
+ * Handles wizard execution
516
+ * @async
517
+ * @function executeWizard
518
+ * @param {Object} args - Parsed arguments
519
+ * @returns {Promise<string>} Path to wizard config file
520
+ */
521
+ async function executeWizard(args) {
522
+ logInfo('\n1. Creating wizard configuration...');
523
+ const configPath = await createWizardConfig(
524
+ args.name,
525
+ args.openapi,
526
+ args.controller,
527
+ args.environment
528
+ );
529
+ logSuccess(`✓ Created config: ${configPath}`);
530
+
531
+ logInfo('\n2. Running wizard...');
532
+ const wizardResult = await runWizard(
533
+ configPath,
534
+ args.controller,
535
+ args.environment,
536
+ args.dataplane
537
+ );
538
+
539
+ if (!wizardResult.success) {
540
+ logError('Wizard failed:');
541
+ if (wizardResult.stdout) {
542
+ logger.log(wizardResult.stdout);
543
+ }
544
+ if (wizardResult.stderr) {
545
+ logger.error(wizardResult.stderr);
546
+ }
547
+ throw new Error('Wizard execution failed');
548
+ }
549
+
550
+ logSuccess('✓ Wizard completed successfully');
551
+ return configPath;
552
+ }
553
+
554
+ /**
555
+ * Processes generated files
556
+ * @async
557
+ * @function processGeneratedFiles
558
+ * @param {Object} args - Parsed arguments
559
+ * @returns {Promise<string>} Output directory path
560
+ */
561
+ async function processGeneratedFiles(args) {
562
+ const wizardOutputDir = path.join(process.cwd(), 'integration', args.name);
563
+ if (!(await fileExists(wizardOutputDir))) {
564
+ throw new Error(`Wizard output directory not found: ${wizardOutputDir}`);
565
+ }
566
+
567
+ const generatedFiles = await listFiles(wizardOutputDir);
568
+ logInfo(`\n3. Generated files: ${generatedFiles.join(', ')}`);
569
+
570
+ const outputDir = args.output || wizardOutputDir;
571
+ if (outputDir !== wizardOutputDir) {
572
+ logInfo(`\n4. Copying files to: ${outputDir}`);
573
+ const copiedFiles = await copyDirectoryContents(wizardOutputDir, outputDir);
574
+ logSuccess(`✓ Copied ${copiedFiles.length} files`);
575
+ } else {
576
+ logInfo(`\n4. Files are in: ${outputDir}`);
577
+ }
578
+
579
+ return { outputDir, wizardOutputDir };
580
+ }
581
+
582
+ /**
583
+ * Prints summary information
584
+ * @async
585
+ * @function printSummary
586
+ * @param {string} outputDir - Output directory path
587
+ * @param {string} appName - Application name
588
+ * @param {string} environment - Environment name
589
+ * @returns {Promise<void>} Resolves when summary is printed
590
+ */
591
+ async function printSummary(outputDir, appName, environment) {
592
+ logSuccess('\n✓ HubSpot integration created successfully!');
593
+ logger.log('\nGenerated files:');
594
+ const files = await listFiles(outputDir);
595
+ for (const file of files) {
596
+ logger.log(` - ${file}`);
597
+ }
598
+
599
+ logger.log('\nNext steps:');
600
+ logger.log(` 1. Review files in: ${outputDir}`);
601
+ logger.log(' 2. Edit hubspot-system.json and datasource files as needed');
602
+ logger.log(` 3. Validate: node bin/aifabrix.js validate ${appName}`);
603
+ logger.log(` 4. Deploy: node bin/aifabrix.js deploy ${appName} --environment ${environment}`);
604
+ }
605
+
606
+ /**
607
+ * Cleans up temporary files
608
+ * @async
609
+ * @function cleanupFiles
610
+ * @param {string} configPath - Config file path
611
+ * @param {string} wizardOutputDir - Wizard output directory
612
+ * @param {string} outputDir - Final output directory
613
+ * @param {boolean} keepWizardFiles - Whether to keep wizard files
614
+ * @returns {Promise<void>} Resolves when cleanup is complete
615
+ */
616
+ async function cleanupFiles(configPath, wizardOutputDir, outputDir, keepWizardFiles) {
617
+ if (!keepWizardFiles && outputDir !== wizardOutputDir) {
618
+ logInfo('\n5. Cleaning up wizard files...');
619
+ await fs.rm(wizardOutputDir, { recursive: true, force: true });
620
+ logSuccess('✓ Cleaned up wizard files');
621
+ }
622
+
623
+ await fs.unlink(configPath).catch(() => {
624
+ // Ignore errors
625
+ });
626
+ }
627
+
628
+ /**
629
+ * Main function
630
+ * @async
631
+ * @function main
632
+ * @returns {Promise<void>} Resolves when complete
633
+ */
634
+ async function main() {
635
+ const args = parseArgs(process.argv);
636
+
637
+ if (args.help) {
638
+ printUsage();
639
+ return;
640
+ }
641
+
642
+ try {
643
+ validateArgs(args);
644
+ await validateOpenApiFile(args.openapi);
645
+
646
+ logInfo(`Creating HubSpot integration: ${args.name}`);
647
+ logInfo(`OpenAPI file: ${args.openapi}`);
648
+ logInfo(`Controller: ${args.controller}`);
649
+ logInfo(`Environment: ${args.environment}`);
650
+ const configPath = await executeWizard(args);
651
+ const { outputDir, wizardOutputDir } = await processGeneratedFiles(args);
652
+ await printSummary(outputDir, args.name, args.environment);
653
+ await cleanupFiles(configPath, wizardOutputDir, outputDir, args.keepWizardFiles);
654
+ } catch (error) {
655
+ logError(`Error: ${error.message}`);
656
+ if (error.stack) {
657
+ logger.error(error.stack);
658
+ }
659
+ process.exit(1);
660
+ }
661
+ }
662
+ main().catch(error => {
663
+ logError(`Unexpected error: ${error.message}`);
664
+ process.exit(1);
665
+ });
@@ -3,7 +3,7 @@
3
3
  "displayName": "HubSpot Company",
4
4
  "description": "HubSpot companies datasource with field mappings for CRM company data",
5
5
  "systemKey": "hubspot",
6
- "entityType": "company",
6
+ "entityType": "record-storage",
7
7
  "resourceType": "customer",
8
8
  "enabled": true,
9
9
  "version": "1.0.0",
@@ -3,7 +3,7 @@
3
3
  "displayName": "HubSpot Contact",
4
4
  "description": "HubSpot contacts datasource with field mappings for CRM contact data",
5
5
  "systemKey": "hubspot",
6
- "entityType": "contact",
6
+ "entityType": "record-storage",
7
7
  "resourceType": "contact",
8
8
  "enabled": true,
9
9
  "version": "1.0.0",
@@ -3,7 +3,7 @@
3
3
  "displayName": "HubSpot Deal",
4
4
  "description": "HubSpot deals datasource with field mappings for CRM deal data",
5
5
  "systemKey": "hubspot",
6
- "entityType": "deal",
6
+ "entityType": "record-storage",
7
7
  "resourceType": "deal",
8
8
  "enabled": true,
9
9
  "version": "1.0.0",