@aifabrix/builder 2.31.0 → 2.32.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.
- package/README.md +9 -9
- package/integration/hubspot/README.md +2 -2
- package/integration/hubspot/hubspot-deploy-company.json +17 -14
- package/integration/hubspot/hubspot-deploy-contact.json +19 -16
- package/integration/hubspot/hubspot-deploy-deal.json +21 -18
- package/lib/api/types/datasources.types.js +31 -5
- package/lib/api/types/wizard.types.js +142 -0
- package/lib/api/wizard.api.js +177 -0
- package/lib/{app-config.js → app/config.js} +4 -4
- package/lib/{app-deploy.js → app/deploy.js} +8 -8
- package/lib/app/display.js +90 -0
- package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
- package/lib/{app-down.js → app/down.js} +4 -4
- package/lib/app/helpers.js +218 -0
- package/lib/app/index.js +298 -0
- package/lib/{app-list.js → app/list.js} +6 -6
- package/lib/{app-push.js → app/push.js} +4 -4
- package/lib/{app-readme.js → app/readme.js} +34 -13
- package/lib/{app-register.js → app/register.js} +9 -9
- package/lib/{app-rotate-secret.js → app/rotate-secret.js} +123 -37
- package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
- package/lib/{app-run.js → app/run.js} +6 -6
- package/lib/{build.js → build/index.js} +59 -32
- package/lib/build/package.json +7 -0
- package/lib/cli.js +245 -179
- package/lib/commands/app.js +3 -3
- package/lib/commands/datasource.js +4 -4
- package/lib/commands/login-credentials.js +209 -0
- package/lib/commands/login-device.js +254 -0
- package/lib/commands/login.js +67 -378
- package/lib/commands/logout.js +1 -1
- package/lib/commands/secrets-set.js +1 -1
- package/lib/commands/secure.js +2 -2
- package/lib/commands/wizard.js +498 -0
- package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
- package/lib/{config.js → core/config.js} +28 -26
- package/lib/{diff.js → core/diff.js} +157 -72
- package/lib/{secrets.js → core/secrets.js} +86 -49
- package/lib/{templates.js → core/templates-env.js} +14 -222
- package/lib/core/templates.js +279 -0
- package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
- package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
- package/lib/datasource/list.js +223 -0
- package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
- package/lib/{deployer.js → deployment/deployer.js} +48 -18
- package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
- package/lib/{push.js → deployment/push.js} +1 -1
- package/lib/external-system/deploy-helpers.js +145 -0
- package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
- package/lib/external-system/download-helpers.js +114 -0
- package/lib/{external-system-download.js → external-system/download.js} +92 -135
- package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
- package/lib/external-system/test-auth.js +40 -0
- package/lib/external-system/test-execution.js +84 -0
- package/lib/external-system/test-helpers.js +109 -0
- package/lib/{external-system-test.js → external-system/test.js} +174 -192
- package/lib/{generator-builders.js → generator/builders.js} +87 -10
- package/lib/{generator-external.js → generator/external.js} +115 -52
- package/lib/{github-generator.js → generator/github.js} +116 -15
- package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
- package/lib/{generator.js → generator/index.js} +49 -22
- package/lib/{generator-split.js → generator/split.js} +108 -55
- package/lib/generator/wizard-prompts.js +357 -0
- package/lib/generator/wizard.js +490 -0
- package/lib/{infra.js → infrastructure/index.js} +49 -22
- package/lib/schema/external-datasource.schema.json +145 -133
- package/lib/schema/external-system.schema.json +42 -0
- package/lib/utils/api.js +9 -5
- package/lib/utils/app-register-api.js +60 -32
- package/lib/utils/app-register-auth.js +172 -47
- package/lib/utils/app-register-config.js +130 -59
- package/lib/utils/app-run-containers.js +29 -8
- package/lib/utils/build-helpers.js +1 -1
- package/lib/utils/cli-utils.js +78 -30
- package/lib/utils/compose-generator.js +145 -65
- package/lib/utils/config-paths.js +2 -0
- package/lib/utils/deployment-errors.js +1 -1
- package/lib/utils/device-code.js +99 -41
- package/lib/utils/env-config-loader.js +1 -1
- package/lib/utils/env-copy.js +21 -18
- package/lib/utils/env-endpoints.js +115 -67
- package/lib/utils/env-map.js +13 -14
- package/lib/utils/env-ports.js +45 -25
- package/lib/utils/env-template.js +84 -42
- package/lib/utils/error-formatter.js +26 -9
- package/lib/utils/error-formatters/error-parser.js +90 -4
- package/lib/utils/error-formatters/http-status-errors.js +54 -17
- package/lib/utils/error-formatters/network-errors.js +103 -26
- package/lib/utils/external-system-display.js +184 -90
- package/lib/utils/external-system-validators.js +164 -42
- package/lib/utils/file-upload.js +109 -0
- package/lib/utils/health-check.js +199 -83
- package/lib/utils/infra-containers.js +1 -1
- package/lib/utils/infra-status.js +66 -15
- package/lib/utils/local-secrets.js +45 -25
- package/lib/utils/paths.js +45 -33
- package/lib/utils/schema-loader.js +42 -25
- package/lib/utils/schema-resolver.js +123 -74
- package/lib/utils/secrets-encryption.js +62 -25
- package/lib/utils/secrets-helpers.js +126 -63
- package/lib/utils/secrets-path.js +1 -1
- package/lib/utils/secrets-url.js +1 -1
- package/lib/utils/token-manager-refresh.js +181 -0
- package/lib/utils/token-manager.js +76 -123
- package/lib/utils/variable-transformer.js +154 -77
- package/lib/utils/yaml-preserve.js +41 -47
- package/lib/{template-validator.js → validation/template.js} +54 -23
- package/lib/{validate.js → validation/validate.js} +205 -125
- package/lib/{validator.js → validation/validator.js} +58 -39
- package/package.json +34 -3
- package/scripts/install-local.js +210 -0
- package/templates/external-system/deploy.ps1.hbs +34 -0
- package/templates/external-system/deploy.sh.hbs +34 -0
- package/templates/external-system/external-datasource.json.hbs +31 -12
- package/lib/app.js +0 -467
- package/lib/datasource-list.js +0 -141
- /package/lib/{app-prompts.js → app/prompts.js} +0 -0
- /package/lib/{env-reader.js → core/env-reader.js} +0 -0
- /package/lib/{key-generator.js → core/key-generator.js} +0 -0
|
@@ -12,72 +12,210 @@ const chalk = require('chalk');
|
|
|
12
12
|
const logger = require('./logger');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Displays
|
|
16
|
-
* @
|
|
17
|
-
* @param {
|
|
15
|
+
* Displays system file results
|
|
16
|
+
* @function displaySystemResults
|
|
17
|
+
* @param {Object[]} systemResults - System file results
|
|
18
18
|
*/
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
19
|
+
function displaySystemResults(systemResults) {
|
|
20
|
+
if (systemResults.length === 0) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
logger.log(chalk.blue('System Files:'));
|
|
24
|
+
for (const systemResult of systemResults) {
|
|
25
|
+
if (systemResult.valid) {
|
|
26
|
+
logger.log(chalk.green(` ✓ ${systemResult.file}`));
|
|
27
|
+
} else {
|
|
28
|
+
logger.log(chalk.red(` ✗ ${systemResult.file}`));
|
|
30
29
|
}
|
|
31
30
|
}
|
|
31
|
+
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Displays verbose datasource details
|
|
35
|
+
* @function displayVerboseDatasourceDetails
|
|
36
|
+
* @param {Object} dsResult - Datasource result
|
|
37
|
+
*/
|
|
38
|
+
function displayVerboseDatasourceDetails(dsResult) {
|
|
39
|
+
if (dsResult.warnings.length > 0) {
|
|
40
|
+
dsResult.warnings.forEach(warn => logger.log(chalk.yellow(` ⚠ ${warn}`)));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (dsResult.fieldMappingResults) {
|
|
44
|
+
const fm = dsResult.fieldMappingResults;
|
|
45
|
+
logger.log(chalk.gray(` Field mappings: ${Object.keys(fm.mappedFields || {}).length} fields`));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (dsResult.metadataSchemaResults) {
|
|
49
|
+
const ms = dsResult.metadataSchemaResults;
|
|
50
|
+
const statusMsg = ms.valid ? ' Metadata schema: ✓ Valid' : ' Metadata schema: ✗ Invalid';
|
|
51
|
+
logger.log(ms.valid ? chalk.gray(statusMsg) : chalk.red(statusMsg));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Displays datasource file results
|
|
57
|
+
* @function displayDatasourceResults
|
|
58
|
+
* @param {Object[]} datasourceResults - Datasource file results
|
|
59
|
+
* @param {boolean} verbose - Show detailed output
|
|
60
|
+
*/
|
|
61
|
+
function displayDatasourceResults(datasourceResults, verbose) {
|
|
62
|
+
if (datasourceResults.length === 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
logger.log(chalk.blue('\nDatasource Files:'));
|
|
66
|
+
for (const dsResult of datasourceResults) {
|
|
67
|
+
if (dsResult.valid) {
|
|
68
|
+
logger.log(chalk.green(` ✓ ${dsResult.key} (${dsResult.file})`));
|
|
69
|
+
} else {
|
|
70
|
+
logger.log(chalk.red(` ✗ ${dsResult.key} (${dsResult.file})`));
|
|
71
|
+
if (verbose && dsResult.errors.length > 0) {
|
|
72
|
+
dsResult.errors.forEach(err => logger.log(chalk.red(` - ${err}`)));
|
|
60
73
|
}
|
|
61
74
|
}
|
|
75
|
+
|
|
76
|
+
if (verbose) {
|
|
77
|
+
displayVerboseDatasourceDetails(dsResult);
|
|
78
|
+
}
|
|
62
79
|
}
|
|
80
|
+
}
|
|
63
81
|
|
|
64
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Displays errors and warnings
|
|
84
|
+
* @function displayErrorsAndWarnings
|
|
85
|
+
* @param {string[]} errors - Error messages
|
|
86
|
+
* @param {string[]} warnings - Warning messages
|
|
87
|
+
*/
|
|
88
|
+
function displayErrorsAndWarnings(errors, warnings) {
|
|
89
|
+
if (errors.length > 0) {
|
|
65
90
|
logger.log(chalk.red('\n❌ Errors:'));
|
|
66
|
-
|
|
91
|
+
errors.forEach(err => logger.log(chalk.red(` - ${err}`)));
|
|
67
92
|
}
|
|
68
93
|
|
|
69
|
-
if (
|
|
94
|
+
if (warnings.length > 0) {
|
|
70
95
|
logger.log(chalk.yellow('\n⚠ Warnings:'));
|
|
71
|
-
|
|
96
|
+
warnings.forEach(warn => logger.log(chalk.yellow(` - ${warn}`)));
|
|
72
97
|
}
|
|
98
|
+
}
|
|
73
99
|
|
|
74
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Displays final test status
|
|
102
|
+
* @function displayFinalTestStatus
|
|
103
|
+
* @param {boolean} valid - Whether all tests passed
|
|
104
|
+
*/
|
|
105
|
+
function displayFinalTestStatus(valid) {
|
|
106
|
+
if (valid) {
|
|
75
107
|
logger.log(chalk.green('\n✅ All tests passed!'));
|
|
76
108
|
} else {
|
|
77
109
|
logger.log(chalk.red('\n❌ Some tests failed'));
|
|
78
110
|
}
|
|
79
111
|
}
|
|
80
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Displays formatted test results
|
|
115
|
+
* @param {Object} results - Test results
|
|
116
|
+
* @param {boolean} verbose - Show detailed output
|
|
117
|
+
*/
|
|
118
|
+
function displayTestResults(results, verbose = false) {
|
|
119
|
+
logger.log(chalk.blue('\n📊 Test Results\n'));
|
|
120
|
+
|
|
121
|
+
displaySystemResults(results.systemResults);
|
|
122
|
+
displayDatasourceResults(results.datasourceResults, verbose);
|
|
123
|
+
displayErrorsAndWarnings(results.errors, results.warnings);
|
|
124
|
+
displayFinalTestStatus(results.valid);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Displays validation results in verbose mode
|
|
129
|
+
* @function displayVerboseValidationResults
|
|
130
|
+
* @param {Object} vr - Validation results
|
|
131
|
+
*/
|
|
132
|
+
function displayVerboseValidationResults(vr) {
|
|
133
|
+
if (vr.isValid) {
|
|
134
|
+
logger.log(chalk.gray(' Validation: ✓ Valid'));
|
|
135
|
+
} else {
|
|
136
|
+
logger.log(chalk.red(' Validation: ✗ Invalid'));
|
|
137
|
+
}
|
|
138
|
+
if (vr.errors && vr.errors.length > 0) {
|
|
139
|
+
vr.errors.forEach(err => logger.log(chalk.red(` - ${err}`)));
|
|
140
|
+
}
|
|
141
|
+
if (vr.warnings && vr.warnings.length > 0) {
|
|
142
|
+
vr.warnings.forEach(warn => logger.log(chalk.yellow(` ⚠ ${warn}`)));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Displays field mapping results in verbose mode
|
|
148
|
+
* @function displayVerboseFieldMappingResults
|
|
149
|
+
* @param {Object} fmr - Field mapping results
|
|
150
|
+
*/
|
|
151
|
+
function displayVerboseFieldMappingResults(fmr) {
|
|
152
|
+
logger.log(chalk.gray(` Field mappings: ${fmr.mappingCount || 0} attributes`));
|
|
153
|
+
if (fmr.dimensions && Object.keys(fmr.dimensions).length > 0) {
|
|
154
|
+
const dimensionKeys = Object.keys(fmr.dimensions);
|
|
155
|
+
logger.log(chalk.gray(` Dimensions: ${dimensionKeys.join(', ')}`));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Displays endpoint test results in verbose mode
|
|
161
|
+
* @function displayVerboseEndpointResults
|
|
162
|
+
* @param {Object} etr - Endpoint test results
|
|
163
|
+
*/
|
|
164
|
+
function displayVerboseEndpointResults(etr) {
|
|
165
|
+
if (etr.endpointConfigured) {
|
|
166
|
+
logger.log(chalk.gray(' Endpoint: ✓ Configured'));
|
|
167
|
+
} else {
|
|
168
|
+
logger.log(chalk.gray(' Endpoint: Not configured'));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Displays verbose integration test details
|
|
174
|
+
* @function displayVerboseIntegrationDetails
|
|
175
|
+
* @param {Object} dsResult - Datasource result
|
|
176
|
+
*/
|
|
177
|
+
function displayVerboseIntegrationDetails(dsResult) {
|
|
178
|
+
if (!dsResult.validationResults) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
displayVerboseValidationResults(dsResult.validationResults);
|
|
183
|
+
|
|
184
|
+
if (dsResult.fieldMappingResults) {
|
|
185
|
+
displayVerboseFieldMappingResults(dsResult.fieldMappingResults);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (dsResult.endpointTestResults) {
|
|
189
|
+
displayVerboseEndpointResults(dsResult.endpointTestResults);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Displays a single datasource integration test result
|
|
195
|
+
* @function displayDatasourceIntegrationResult
|
|
196
|
+
* @param {Object} dsResult - Datasource result
|
|
197
|
+
* @param {boolean} verbose - Show detailed output
|
|
198
|
+
*/
|
|
199
|
+
function displayDatasourceIntegrationResult(dsResult, verbose) {
|
|
200
|
+
if (dsResult.skipped) {
|
|
201
|
+
logger.log(chalk.yellow(` ⚠ ${dsResult.key}: ${dsResult.reason}`));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (dsResult.success) {
|
|
206
|
+
logger.log(chalk.green(` ✓ ${dsResult.key}`));
|
|
207
|
+
} else {
|
|
208
|
+
logger.log(chalk.red(` ✗ ${dsResult.key}`));
|
|
209
|
+
if (dsResult.error) {
|
|
210
|
+
logger.log(chalk.red(` Error: ${dsResult.error}`));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (verbose) {
|
|
215
|
+
displayVerboseIntegrationDetails(dsResult);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
81
219
|
/**
|
|
82
220
|
* Displays formatted integration test results
|
|
83
221
|
* @param {Object} results - Integration test results
|
|
@@ -93,51 +231,7 @@ function displayIntegrationTestResults(results, verbose = false) {
|
|
|
93
231
|
}
|
|
94
232
|
|
|
95
233
|
for (const dsResult of results.datasourceResults) {
|
|
96
|
-
|
|
97
|
-
logger.log(chalk.yellow(` ⚠ ${dsResult.key}: ${dsResult.reason}`));
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (dsResult.success) {
|
|
102
|
-
logger.log(chalk.green(` ✓ ${dsResult.key}`));
|
|
103
|
-
} else {
|
|
104
|
-
logger.log(chalk.red(` ✗ ${dsResult.key}`));
|
|
105
|
-
if (dsResult.error) {
|
|
106
|
-
logger.log(chalk.red(` Error: ${dsResult.error}`));
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (verbose && dsResult.validationResults) {
|
|
111
|
-
const vr = dsResult.validationResults;
|
|
112
|
-
if (vr.isValid) {
|
|
113
|
-
logger.log(chalk.gray(' Validation: ✓ Valid'));
|
|
114
|
-
} else {
|
|
115
|
-
logger.log(chalk.red(' Validation: ✗ Invalid'));
|
|
116
|
-
}
|
|
117
|
-
if (vr.errors && vr.errors.length > 0) {
|
|
118
|
-
vr.errors.forEach(err => logger.log(chalk.red(` - ${err}`)));
|
|
119
|
-
}
|
|
120
|
-
if (vr.warnings && vr.warnings.length > 0) {
|
|
121
|
-
vr.warnings.forEach(warn => logger.log(chalk.yellow(` ⚠ ${warn}`)));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (dsResult.fieldMappingResults) {
|
|
125
|
-
const fmr = dsResult.fieldMappingResults;
|
|
126
|
-
logger.log(chalk.gray(` Field mappings: ${fmr.mappingCount || 0} fields`));
|
|
127
|
-
if (fmr.accessFields && fmr.accessFields.length > 0) {
|
|
128
|
-
logger.log(chalk.gray(` Access fields: ${fmr.accessFields.join(', ')}`));
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (dsResult.endpointTestResults) {
|
|
133
|
-
const etr = dsResult.endpointTestResults;
|
|
134
|
-
if (etr.endpointConfigured) {
|
|
135
|
-
logger.log(chalk.gray(' Endpoint: ✓ Configured'));
|
|
136
|
-
} else {
|
|
137
|
-
logger.log(chalk.gray(' Endpoint: Not configured'));
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
234
|
+
displayDatasourceIntegrationResult(dsResult, verbose);
|
|
141
235
|
}
|
|
142
236
|
|
|
143
237
|
if (results.success) {
|
|
@@ -58,6 +58,158 @@ function validateFieldMappingExpression(expression) {
|
|
|
58
58
|
* @param {Object} testPayload - Test payload object
|
|
59
59
|
* @returns {Object} Validation results
|
|
60
60
|
*/
|
|
61
|
+
/**
|
|
62
|
+
* Validates a single field mapping
|
|
63
|
+
* @function validateSingleFieldMapping
|
|
64
|
+
* @param {string} fieldName - Field name
|
|
65
|
+
* @param {Object} fieldConfig - Field configuration
|
|
66
|
+
* @param {Object} payloadTemplate - Payload template
|
|
67
|
+
* @param {Object} results - Results object to update
|
|
68
|
+
*/
|
|
69
|
+
function validateSingleFieldMapping(fieldName, fieldConfig, payloadTemplate, results) {
|
|
70
|
+
if (!fieldConfig.expression) {
|
|
71
|
+
results.errors.push(`Field '${fieldName}' missing expression`);
|
|
72
|
+
results.valid = false;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Validate expression syntax
|
|
77
|
+
const exprValidation = validateFieldMappingExpression(fieldConfig.expression);
|
|
78
|
+
if (!exprValidation.isValid) {
|
|
79
|
+
results.errors.push(`Field '${fieldName}': ${exprValidation.error}`);
|
|
80
|
+
results.valid = false;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Try to extract path from expression
|
|
85
|
+
const pathMatch = fieldConfig.expression.match(/\{\{([^}]+)\}\}/);
|
|
86
|
+
if (pathMatch) {
|
|
87
|
+
const fieldPath = pathMatch[1].trim();
|
|
88
|
+
const pathExists = checkPathExistsInPayload(fieldPath, payloadTemplate);
|
|
89
|
+
|
|
90
|
+
if (!pathExists) {
|
|
91
|
+
results.warnings.push(`Field '${fieldName}': Path '${fieldPath}' may not exist in payload`);
|
|
92
|
+
} else {
|
|
93
|
+
results.mappedFields[fieldName] = fieldConfig.expression;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Checks if path exists in payload
|
|
100
|
+
* @function checkPathExistsInPayload
|
|
101
|
+
* @param {string} fieldPath - Field path
|
|
102
|
+
* @param {Object} payloadTemplate - Payload template
|
|
103
|
+
* @returns {boolean} True if path exists
|
|
104
|
+
*/
|
|
105
|
+
function checkPathExistsInPayload(fieldPath, payloadTemplate) {
|
|
106
|
+
const pathParts = fieldPath.split('.');
|
|
107
|
+
let current = payloadTemplate;
|
|
108
|
+
|
|
109
|
+
for (const part of pathParts) {
|
|
110
|
+
if (current && typeof current === 'object' && part in current) {
|
|
111
|
+
current = current[part];
|
|
112
|
+
} else {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validates dimensions object structure and content
|
|
122
|
+
* @param {Object} dimensions - Dimensions object to validate
|
|
123
|
+
* @param {Object} results - Results object to update
|
|
124
|
+
*/
|
|
125
|
+
function validateDimensions(dimensions, results) {
|
|
126
|
+
if (!dimensions) {
|
|
127
|
+
results.errors.push('fieldMappings.dimensions is required (dimensions-first model)');
|
|
128
|
+
results.valid = false;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (typeof dimensions !== 'object' || Array.isArray(dimensions)) {
|
|
133
|
+
results.errors.push('fieldMappings.dimensions must be an object mapping dimension keys to attribute paths');
|
|
134
|
+
results.valid = false;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Validate dimension keys and values
|
|
139
|
+
for (const [dimensionKey, attributePath] of Object.entries(dimensions)) {
|
|
140
|
+
if (!/^[a-zA-Z0-9_]+$/.test(dimensionKey)) {
|
|
141
|
+
results.errors.push(`Invalid dimension key '${dimensionKey}': must match pattern ^[a-zA-Z0-9_]+$`);
|
|
142
|
+
results.valid = false;
|
|
143
|
+
}
|
|
144
|
+
if (typeof attributePath !== 'string' || !/^[a-zA-Z0-9_.]+$/.test(attributePath)) {
|
|
145
|
+
results.errors.push(`Invalid attribute path '${attributePath}' for dimension '${dimensionKey}': must match pattern ^[a-zA-Z0-9_.]+$`);
|
|
146
|
+
results.valid = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Validates attributes object structure
|
|
153
|
+
* @param {Object} attributes - Attributes object to validate
|
|
154
|
+
* @param {Object} results - Results object to update
|
|
155
|
+
* @returns {Object|null} Attributes object if valid, null otherwise
|
|
156
|
+
*/
|
|
157
|
+
function validateAttributesStructure(attributes, results) {
|
|
158
|
+
if (!attributes) {
|
|
159
|
+
results.errors.push('fieldMappings.attributes is required');
|
|
160
|
+
results.valid = false;
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (typeof attributes !== 'object' || Array.isArray(attributes)) {
|
|
165
|
+
results.errors.push('fieldMappings.attributes must be an object');
|
|
166
|
+
results.valid = false;
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return attributes;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Validates a single attribute configuration
|
|
175
|
+
* @param {string} attributeName - Attribute name
|
|
176
|
+
* @param {Object} attributeConfig - Attribute configuration
|
|
177
|
+
* @param {Object} payloadTemplate - Payload template
|
|
178
|
+
* @param {Object} results - Results object to update
|
|
179
|
+
*/
|
|
180
|
+
function validateSingleAttribute(attributeName, attributeConfig, payloadTemplate, results) {
|
|
181
|
+
if (!attributeConfig || typeof attributeConfig !== 'object') {
|
|
182
|
+
results.errors.push(`Attribute '${attributeName}' must be an object with expression and type`);
|
|
183
|
+
results.valid = false;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Validate indexed property if present
|
|
188
|
+
if ('indexed' in attributeConfig && typeof attributeConfig.indexed !== 'boolean') {
|
|
189
|
+
results.errors.push(`Attribute '${attributeName}': indexed property must be a boolean`);
|
|
190
|
+
results.valid = false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Validate expression
|
|
194
|
+
if (!attributeConfig.expression || typeof attributeConfig.expression !== 'string') {
|
|
195
|
+
results.errors.push(`Attribute '${attributeName}': expression is required`);
|
|
196
|
+
results.valid = false;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Validate record_ref: expressions
|
|
201
|
+
if (attributeConfig.expression.startsWith('record_ref:')) {
|
|
202
|
+
const entityType = attributeConfig.expression.substring(11);
|
|
203
|
+
if (!/^[a-z0-9-]+$/.test(entityType)) {
|
|
204
|
+
results.errors.push(`Attribute '${attributeName}': Invalid entity type in record_ref expression: ${entityType}`);
|
|
205
|
+
results.valid = false;
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
// Validate regular expression syntax
|
|
209
|
+
validateSingleFieldMapping(attributeName, attributeConfig, payloadTemplate, results);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
61
213
|
function validateFieldMappings(datasource, testPayload) {
|
|
62
214
|
const results = {
|
|
63
215
|
valid: true,
|
|
@@ -66,54 +218,24 @@ function validateFieldMappings(datasource, testPayload) {
|
|
|
66
218
|
mappedFields: {}
|
|
67
219
|
};
|
|
68
220
|
|
|
69
|
-
if (!datasource.fieldMappings
|
|
221
|
+
if (!datasource.fieldMappings) {
|
|
70
222
|
results.warnings.push('No field mappings defined');
|
|
71
223
|
return results;
|
|
72
224
|
}
|
|
73
225
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// Validate each field mapping expression
|
|
78
|
-
for (const [fieldName, fieldConfig] of Object.entries(fields)) {
|
|
79
|
-
if (!fieldConfig.expression) {
|
|
80
|
-
results.errors.push(`Field '${fieldName}' missing expression`);
|
|
81
|
-
results.valid = false;
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Validate expression syntax
|
|
86
|
-
const exprValidation = validateFieldMappingExpression(fieldConfig.expression);
|
|
87
|
-
if (!exprValidation.isValid) {
|
|
88
|
-
results.errors.push(`Field '${fieldName}': ${exprValidation.error}`);
|
|
89
|
-
results.valid = false;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
226
|
+
// Validate dimensions (required in new schema)
|
|
227
|
+
validateDimensions(datasource.fieldMappings.dimensions, results);
|
|
92
228
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const pathParts = fieldPath.split('.');
|
|
99
|
-
let current = payloadTemplate;
|
|
100
|
-
let pathExists = true;
|
|
101
|
-
|
|
102
|
-
for (const part of pathParts) {
|
|
103
|
-
if (current && typeof current === 'object' && part in current) {
|
|
104
|
-
current = current[part];
|
|
105
|
-
} else {
|
|
106
|
-
pathExists = false;
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
229
|
+
// Validate attributes structure (required in new schema)
|
|
230
|
+
const attributes = validateAttributesStructure(datasource.fieldMappings.attributes, results);
|
|
231
|
+
if (!attributes) {
|
|
232
|
+
return results;
|
|
233
|
+
}
|
|
110
234
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
}
|
|
235
|
+
// Validate each attribute
|
|
236
|
+
const payloadTemplate = testPayload.payloadTemplate || testPayload;
|
|
237
|
+
for (const [attributeName, attributeConfig] of Object.entries(attributes)) {
|
|
238
|
+
validateSingleAttribute(attributeName, attributeConfig, payloadTemplate, results);
|
|
117
239
|
}
|
|
118
240
|
|
|
119
241
|
return results;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview File upload utilities for multipart/form-data requests
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs').promises;
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { makeApiCall, authenticatedApiCall } = require('./api');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Upload a file using multipart/form-data
|
|
13
|
+
* @async
|
|
14
|
+
* @function uploadFile
|
|
15
|
+
* @param {string} url - API endpoint URL
|
|
16
|
+
* @param {string} filePath - Path to file to upload
|
|
17
|
+
* @param {string} fieldName - Form field name for the file (default: 'file')
|
|
18
|
+
* @param {Object} [authConfig] - Authentication configuration
|
|
19
|
+
* @param {string} [authConfig.type] - Auth type ('bearer' | 'client-credentials')
|
|
20
|
+
* @param {string} [authConfig.token] - Bearer token
|
|
21
|
+
* @param {string} [authConfig.clientId] - Client ID
|
|
22
|
+
* @param {string} [authConfig.clientSecret] - Client secret
|
|
23
|
+
* @param {Object} [additionalFields] - Additional form fields to include
|
|
24
|
+
* @returns {Promise<Object>} API response
|
|
25
|
+
* @throws {Error} If file upload fails
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Validates file exists
|
|
29
|
+
* @async
|
|
30
|
+
* @function validateFileExists
|
|
31
|
+
* @param {string} filePath - File path
|
|
32
|
+
* @throws {Error} If file not found
|
|
33
|
+
*/
|
|
34
|
+
async function validateFileExists(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
await fs.access(filePath);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new Error(`File not found: ${filePath}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Builds FormData with file and additional fields
|
|
44
|
+
* @async
|
|
45
|
+
* @function buildFormData
|
|
46
|
+
* @param {string} filePath - File path
|
|
47
|
+
* @param {string} fieldName - Field name
|
|
48
|
+
* @param {Object} additionalFields - Additional fields
|
|
49
|
+
* @returns {Promise<FormData>} FormData object
|
|
50
|
+
*/
|
|
51
|
+
async function buildFormData(filePath, fieldName, additionalFields) {
|
|
52
|
+
const formData = new FormData();
|
|
53
|
+
const fileContent = await fs.readFile(filePath);
|
|
54
|
+
const fileName = path.basename(filePath);
|
|
55
|
+
const fileBlob = new Blob([fileContent], { type: 'application/octet-stream' });
|
|
56
|
+
formData.append(fieldName, fileBlob, fileName);
|
|
57
|
+
|
|
58
|
+
for (const [key, value] of Object.entries(additionalFields)) {
|
|
59
|
+
formData.append(key, String(value));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return formData;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Builds authentication headers
|
|
67
|
+
* @function buildAuthHeaders
|
|
68
|
+
* @param {Object} authConfig - Authentication configuration
|
|
69
|
+
* @returns {Object} Headers object
|
|
70
|
+
*/
|
|
71
|
+
function buildAuthHeaders(authConfig) {
|
|
72
|
+
const headers = {};
|
|
73
|
+
if (authConfig.type === 'bearer' && authConfig.token) {
|
|
74
|
+
headers['Authorization'] = `Bearer ${authConfig.token}`;
|
|
75
|
+
} else if (authConfig.type === 'client-credentials') {
|
|
76
|
+
if (authConfig.clientId) {
|
|
77
|
+
headers['x-client-id'] = authConfig.clientId;
|
|
78
|
+
}
|
|
79
|
+
if (authConfig.clientSecret) {
|
|
80
|
+
headers['x-client-secret'] = authConfig.clientSecret;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return headers;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function uploadFile(url, filePath, fieldName = 'file', authConfig = {}, additionalFields = {}) {
|
|
87
|
+
await validateFileExists(filePath);
|
|
88
|
+
|
|
89
|
+
const formData = await buildFormData(filePath, fieldName, additionalFields);
|
|
90
|
+
const headers = buildAuthHeaders(authConfig);
|
|
91
|
+
|
|
92
|
+
const options = {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers,
|
|
95
|
+
body: formData
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Use authenticatedApiCall if bearer token, otherwise makeApiCall
|
|
99
|
+
if (authConfig.type === 'bearer' && authConfig.token) {
|
|
100
|
+
return await authenticatedApiCall(url, options, authConfig.token);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return await makeApiCall(url, options);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = {
|
|
107
|
+
uploadFile
|
|
108
|
+
};
|
|
109
|
+
|