@aifabrix/builder 2.32.3 → 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.
- package/.cursor/rules/project-rules.mdc +8 -0
- package/README.md +36 -8
- package/bin/aifabrix.js +6 -8
- package/integration/hubspot/README.md +8 -7
- package/integration/hubspot/companies.json +2048 -0
- package/integration/hubspot/create-hubspot.js +665 -0
- package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
- package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
- package/integration/hubspot/hubspot-deploy.json +832 -81
- package/integration/hubspot/hubspot-system.json +99 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
- package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
- package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
- package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
- package/integration/hubspot/test-dataplane-down-tests.js +419 -0
- package/integration/hubspot/test-dataplane-down.js +157 -0
- package/integration/hubspot/test.js +1517 -0
- package/integration/hubspot/variables.yaml +4 -4
- package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
- package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
- package/lib/api/applications.api.js +1 -0
- package/lib/api/types/wizard.types.js +176 -38
- package/lib/api/wizard.api.js +161 -23
- package/lib/app/deploy.js +116 -54
- package/lib/app/display.js +6 -5
- package/lib/app/dockerfile.js +2 -1
- package/lib/app/list.js +17 -10
- package/lib/app/readme.js +41 -112
- package/lib/app/register.js +44 -9
- package/lib/app/rotate-secret.js +48 -31
- package/lib/cli.js +219 -70
- package/lib/commands/app.js +4 -9
- package/lib/commands/auth-config.js +125 -0
- package/lib/commands/auth-status.js +7 -8
- package/lib/commands/datasource.js +3 -6
- package/lib/commands/login-credentials.js +4 -4
- package/lib/commands/login-device.js +26 -17
- package/lib/commands/login.js +12 -10
- package/lib/commands/wizard-config-normalizer.js +92 -0
- package/lib/commands/wizard-core.js +515 -0
- package/lib/commands/wizard-dataplane.js +122 -0
- package/lib/commands/wizard-headless.js +115 -0
- package/lib/commands/wizard.js +110 -332
- package/lib/core/config.js +46 -0
- package/lib/core/secrets.js +3 -22
- package/lib/core/templates-env.js +1 -1
- package/lib/datasource/deploy.js +29 -21
- package/lib/datasource/list.js +8 -6
- package/lib/deployment/deployer.js +25 -0
- package/lib/deployment/environment.js +10 -13
- package/lib/external-system/delete.js +151 -0
- package/lib/external-system/deploy.js +53 -378
- package/lib/external-system/download-helpers.js +45 -65
- package/lib/external-system/download.js +33 -13
- package/lib/external-system/generator.js +11 -7
- package/lib/external-system/test-auth.js +4 -3
- package/lib/generator/builders.js +3 -1
- package/lib/generator/external-controller-manifest.js +157 -0
- package/lib/generator/external-schema-utils.js +236 -0
- package/lib/generator/external.js +55 -3
- package/lib/generator/index.js +22 -10
- package/lib/generator/wizard-prompts.js +33 -10
- package/lib/generator/wizard.js +69 -86
- package/lib/infrastructure/compose.js +100 -0
- package/lib/infrastructure/helpers.js +139 -0
- package/lib/infrastructure/index.js +52 -311
- package/lib/infrastructure/services.js +168 -0
- package/lib/schema/application-schema.json +23 -4
- package/lib/schema/external-datasource.schema.json +2 -2
- package/lib/schema/wizard-config.schema.json +234 -0
- package/lib/utils/api.js +32 -50
- package/lib/utils/app-existence.js +42 -0
- package/lib/utils/app-register-config.js +7 -2
- package/lib/utils/auth-config-validator.js +92 -0
- package/lib/utils/command-header.js +43 -0
- package/lib/utils/compose-generator.js +113 -70
- package/lib/utils/controller-url.js +65 -17
- package/lib/utils/dataplane-health.js +115 -0
- package/lib/utils/dataplane-resolver.js +29 -0
- package/lib/utils/dev-config.js +6 -2
- package/lib/utils/env-copy.js +2 -1
- package/lib/utils/env-ports.js +2 -1
- package/lib/utils/env-template.js +1 -1
- package/lib/utils/error-formatter.js +49 -0
- package/lib/utils/external-readme.js +125 -0
- package/lib/utils/help-builder.js +190 -0
- package/lib/utils/infra-status.js +13 -3
- package/lib/utils/paths.js +17 -2
- package/lib/utils/port-resolver.js +111 -0
- package/lib/utils/secrets-helpers.js +3 -15
- package/lib/utils/secrets-utils.js +2 -2
- package/lib/utils/token-manager.js +9 -4
- package/lib/utils/variable-transformer.js +7 -2
- package/lib/validation/external-manifest-validator.js +202 -0
- package/lib/validation/validate-display.js +406 -0
- package/lib/validation/validate.js +159 -123
- package/lib/validation/validator.js +36 -3
- package/lib/validation/wizard-config-validator.js +267 -0
- package/package.json +4 -2
- package/templates/applications/README.md.hbs +18 -16
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/applications/miso-controller/rbac.yaml +7 -7
- package/templates/external-system/README.md.hbs +99 -0
- package/templates/infra/compose.yaml.hbs +35 -0
- package/templates/python/docker-compose.hbs +26 -0
- 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": "
|
|
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": "
|
|
6
|
+
"entityType": "record-storage",
|
|
7
7
|
"resourceType": "contact",
|
|
8
8
|
"enabled": true,
|
|
9
9
|
"version": "1.0.0",
|