@aifabrix/builder 2.40.2 → 2.42.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 (198) hide show
  1. package/.cursor/rules/docs-rules.mdc +30 -0
  2. package/README.md +7 -5
  3. package/integration/hubspot/README.md +8 -4
  4. package/integration/hubspot/application.json +54 -0
  5. package/integration/hubspot/create-hubspot.js +9 -136
  6. package/integration/hubspot/env.template +3 -4
  7. package/integration/hubspot/hubspot-datasource-company.json +343 -5
  8. package/integration/hubspot/hubspot-datasource-contact.json +413 -5
  9. package/integration/hubspot/hubspot-datasource-deal.json +341 -4
  10. package/integration/hubspot/hubspot-datasource-users.json +116 -0
  11. package/integration/hubspot/hubspot-deploy.json +1250 -108
  12. package/integration/hubspot/hubspot-system.json +15 -32
  13. package/integration/hubspot/test-dataplane-down-tests.js +17 -16
  14. package/integration/hubspot/test-dataplane-down.js +2 -2
  15. package/integration/hubspot/test.js +1 -1
  16. package/jest.config.manual.js +2 -1
  17. package/lib/api/credential.api.js +40 -0
  18. package/lib/api/dev.api.js +423 -0
  19. package/lib/api/external-test.api.js +111 -0
  20. package/lib/api/index.js +42 -19
  21. package/lib/api/pipeline.api.js +66 -120
  22. package/lib/api/types/credential.types.js +23 -0
  23. package/lib/api/types/dev.types.js +140 -0
  24. package/lib/api/types/pipeline.types.js +37 -0
  25. package/lib/api/wizard-platform.api.js +61 -0
  26. package/lib/api/wizard.api.js +34 -1
  27. package/lib/app/config.js +44 -11
  28. package/lib/app/down.js +2 -1
  29. package/lib/app/index.js +12 -1
  30. package/lib/app/prompts.js +44 -29
  31. package/lib/app/push.js +36 -12
  32. package/lib/app/readme.js +9 -6
  33. package/lib/app/run-env-compose.js +264 -0
  34. package/lib/app/run-helpers.js +121 -118
  35. package/lib/app/run.js +148 -28
  36. package/lib/app/show-display.js +1 -1
  37. package/lib/app/show.js +5 -2
  38. package/lib/build/index.js +11 -3
  39. package/lib/cli/setup-app.js +172 -15
  40. package/lib/cli/setup-credential-deployment.js +31 -6
  41. package/lib/cli/setup-dev.js +206 -16
  42. package/lib/cli/setup-environment.js +16 -6
  43. package/lib/cli/setup-external-system.js +89 -24
  44. package/lib/cli/setup-infra.js +82 -15
  45. package/lib/cli/setup-secrets.js +52 -5
  46. package/lib/cli/setup-utility.js +129 -24
  47. package/lib/commands/app-install.js +172 -0
  48. package/lib/commands/app-shell.js +75 -0
  49. package/lib/commands/app-test.js +282 -0
  50. package/lib/commands/app.js +1 -1
  51. package/lib/commands/credential-env.js +162 -0
  52. package/lib/commands/credential-list.js +17 -22
  53. package/lib/commands/credential-push.js +96 -0
  54. package/lib/commands/datasource.js +77 -6
  55. package/lib/commands/dev-cli-handlers.js +141 -0
  56. package/lib/commands/dev-down.js +114 -0
  57. package/lib/commands/dev-init.js +347 -0
  58. package/lib/commands/repair-auth-config.js +99 -0
  59. package/lib/commands/repair-datasource-keys.js +208 -0
  60. package/lib/commands/repair-datasource.js +235 -0
  61. package/lib/commands/repair-env-template.js +348 -0
  62. package/lib/commands/repair-internal.js +85 -0
  63. package/lib/commands/repair-rbac.js +158 -0
  64. package/lib/commands/repair.js +507 -0
  65. package/lib/commands/secrets-list.js +118 -0
  66. package/lib/commands/secrets-remove.js +97 -0
  67. package/lib/commands/secrets-set.js +30 -17
  68. package/lib/commands/secrets-validate.js +50 -0
  69. package/lib/commands/test-e2e-external.js +165 -0
  70. package/lib/commands/up-dataplane.js +2 -2
  71. package/lib/commands/up-miso.js +0 -25
  72. package/lib/commands/upload.js +96 -40
  73. package/lib/commands/wizard-core-helpers.js +226 -4
  74. package/lib/commands/wizard-core.js +67 -29
  75. package/lib/commands/wizard-dataplane.js +1 -1
  76. package/lib/commands/wizard-entity-selection.js +43 -0
  77. package/lib/commands/wizard-headless.js +44 -5
  78. package/lib/commands/wizard-helpers.js +7 -3
  79. package/lib/commands/wizard.js +86 -64
  80. package/lib/core/admin-secrets.js +96 -0
  81. package/lib/core/config.js +7 -1
  82. package/lib/core/secrets-ensure.js +378 -0
  83. package/lib/core/secrets-env-write.js +157 -0
  84. package/lib/core/secrets.js +176 -89
  85. package/lib/datasource/deploy.js +12 -3
  86. package/lib/datasource/field-reference-validator.js +91 -0
  87. package/lib/datasource/test-e2e.js +219 -0
  88. package/lib/datasource/test-integration.js +154 -0
  89. package/lib/datasource/validate.js +21 -3
  90. package/lib/deployment/deployer.js +7 -5
  91. package/lib/deployment/environment-config.js +137 -0
  92. package/lib/deployment/environment.js +21 -98
  93. package/lib/deployment/push.js +32 -2
  94. package/lib/external-system/download.js +188 -203
  95. package/lib/external-system/generator.js +204 -56
  96. package/lib/external-system/test-auth.js +7 -3
  97. package/lib/external-system/test-execution.js +2 -1
  98. package/lib/external-system/test-system-level.js +73 -0
  99. package/lib/external-system/test.js +56 -19
  100. package/lib/generator/external-controller-manifest.js +29 -2
  101. package/lib/generator/external-schema-utils.js +1 -1
  102. package/lib/generator/external.js +10 -3
  103. package/lib/generator/index.js +177 -25
  104. package/lib/generator/split-readme.js +1 -0
  105. package/lib/generator/split-variables.js +7 -1
  106. package/lib/generator/split.js +194 -54
  107. package/lib/generator/wizard-prompts-secondary.js +294 -0
  108. package/lib/generator/wizard-prompts.js +105 -106
  109. package/lib/generator/wizard-readme.js +88 -0
  110. package/lib/generator/wizard.js +155 -158
  111. package/lib/infrastructure/compose.js +11 -1
  112. package/lib/infrastructure/helpers.js +103 -20
  113. package/lib/infrastructure/index.js +98 -12
  114. package/lib/infrastructure/services.js +88 -22
  115. package/lib/schema/application-schema.json +32 -8
  116. package/lib/schema/external-datasource.schema.json +49 -26
  117. package/lib/schema/external-system.schema.json +509 -411
  118. package/lib/schema/wizard-config.schema.json +16 -0
  119. package/lib/utils/api.js +41 -13
  120. package/lib/utils/app-register-auth.js +25 -3
  121. package/lib/utils/auth-headers.js +8 -7
  122. package/lib/utils/cli-utils.js +20 -0
  123. package/lib/utils/compose-generator.js +77 -76
  124. package/lib/utils/compose-handlebars-helpers.js +54 -0
  125. package/lib/utils/compose-vector-helper.js +18 -0
  126. package/lib/utils/config-format-preference.js +51 -0
  127. package/lib/utils/config-format.js +36 -0
  128. package/lib/utils/config-paths.js +127 -2
  129. package/lib/utils/configuration-env-resolver.js +179 -0
  130. package/lib/utils/credential-display.js +83 -0
  131. package/lib/utils/credential-secrets-env.js +357 -0
  132. package/lib/utils/dataplane-pipeline-warning.js +28 -0
  133. package/lib/utils/deployment-validation-helpers.js +4 -4
  134. package/lib/utils/dev-ca-install.js +139 -0
  135. package/lib/utils/dev-cert-helper.js +122 -0
  136. package/lib/utils/device-code-helpers.js +224 -0
  137. package/lib/utils/device-code.js +37 -336
  138. package/lib/utils/docker-build.js +40 -8
  139. package/lib/utils/env-copy.js +103 -13
  140. package/lib/utils/env-map.js +35 -5
  141. package/lib/utils/env-template.js +6 -5
  142. package/lib/utils/error-formatters/http-status-errors.js +20 -2
  143. package/lib/utils/error-formatters/permission-errors.js +0 -1
  144. package/lib/utils/error-formatters/validation-errors.js +0 -1
  145. package/lib/utils/external-readme.js +56 -29
  146. package/lib/utils/external-system-display.js +59 -1
  147. package/lib/utils/external-system-test-helpers.js +21 -8
  148. package/lib/utils/external-system-validators.js +3 -0
  149. package/lib/utils/file-upload.js +20 -50
  150. package/lib/utils/help-builder.js +16 -2
  151. package/lib/utils/infra-status.js +80 -45
  152. package/lib/utils/local-secrets.js +7 -52
  153. package/lib/utils/mutagen-install.js +195 -0
  154. package/lib/utils/mutagen.js +146 -0
  155. package/lib/utils/paths.js +128 -37
  156. package/lib/utils/port-resolver.js +28 -16
  157. package/lib/utils/remote-dev-auth.js +38 -0
  158. package/lib/utils/remote-docker-env.js +43 -0
  159. package/lib/utils/remote-secrets-loader.js +60 -0
  160. package/lib/utils/secrets-canonical.js +93 -0
  161. package/lib/utils/secrets-generator.js +114 -6
  162. package/lib/utils/secrets-helpers.js +108 -114
  163. package/lib/utils/secrets-path.js +2 -2
  164. package/lib/utils/secrets-utils.js +52 -1
  165. package/lib/utils/secrets-validation.js +84 -0
  166. package/lib/utils/ssh-key-helper.js +116 -0
  167. package/lib/utils/test-log-writer.js +56 -0
  168. package/lib/utils/token-manager-messages.js +90 -0
  169. package/lib/utils/token-manager.js +29 -36
  170. package/lib/utils/variable-transformer.js +3 -3
  171. package/lib/validation/env-template-auth.js +157 -0
  172. package/lib/validation/env-template-kv.js +41 -0
  173. package/lib/validation/external-manifest-validator.js +25 -0
  174. package/lib/validation/external-system-auth-rules.js +86 -0
  175. package/lib/validation/validate-batch.js +149 -0
  176. package/lib/validation/validate-datasource-keys-api.js +33 -0
  177. package/lib/validation/validate-display.js +94 -16
  178. package/lib/validation/validate.js +25 -12
  179. package/lib/validation/validator.js +72 -9
  180. package/lib/validation/wizard-datasource-validation.js +50 -0
  181. package/package.json +8 -3
  182. package/scripts/install-local.js +34 -15
  183. package/templates/README.md +0 -1
  184. package/templates/applications/README.md.hbs +4 -4
  185. package/templates/applications/dataplane/application.yaml +6 -5
  186. package/templates/applications/dataplane/env.template +15 -10
  187. package/templates/applications/dataplane/rbac.yaml +2 -2
  188. package/templates/applications/keycloak/env.template +2 -0
  189. package/templates/applications/miso-controller/application.yaml +1 -0
  190. package/templates/applications/miso-controller/env.template +12 -10
  191. package/templates/external-system/README.md.hbs +65 -25
  192. package/templates/external-system/deploy.js.hbs +4 -2
  193. package/templates/external-system/external-datasource.yaml.hbs +217 -0
  194. package/templates/external-system/external-system.json.hbs +1 -18
  195. package/templates/infra/compose.yaml.hbs +6 -0
  196. package/templates/python/docker-compose.hbs +49 -23
  197. package/templates/typescript/docker-compose.hbs +48 -22
  198. package/integration/hubspot/application.yaml +0 -37
@@ -1,25 +1,29 @@
1
1
  /**
2
- * Upload external system to dataplane (upload → validate → publish).
2
+ * Upload external system to dataplane (single pipeline upload: upload → validate → publish).
3
3
  *
4
4
  * @fileoverview Upload command handler for aifabrix upload <system-key>
5
5
  * @author AI Fabrix Team
6
6
  * @version 2.0.0
7
7
  */
8
8
 
9
+ const path = require('path');
9
10
  const chalk = require('chalk');
10
11
  const logger = require('../utils/logger');
11
12
  const { resolveControllerUrl } = require('../utils/controller-url');
12
13
  const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
13
14
  const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
15
+ const { getIntegrationPath } = require('../utils/paths');
16
+ const { pushCredentialSecrets } = require('../utils/credential-secrets-env');
17
+ const {
18
+ buildResolvedEnvMapForIntegration,
19
+ resolveConfigurationValues
20
+ } = require('../utils/configuration-env-resolver');
14
21
  const { validateExternalSystemComplete } = require('../validation/validate');
15
22
  const { displayValidationResults } = require('../validation/validate-display');
16
23
  const { generateControllerManifest } = require('../generator/external-controller-manifest');
17
- const {
18
- uploadApplicationViaPipeline,
19
- validateUploadViaPipeline,
20
- publishUploadViaPipeline
21
- } = require('../api/pipeline.api');
24
+ const { uploadApplicationViaPipeline } = require('../api/pipeline.api');
22
25
  const { formatApiError } = require('../utils/api-error-handler');
26
+ const { logDataplanePipelineWarning } = require('../utils/dataplane-pipeline-warning');
23
27
 
24
28
  /**
25
29
  * Validates system-key format (same as download).
@@ -37,25 +41,25 @@ function validateSystemKeyFormat(systemKey) {
37
41
 
38
42
  /**
39
43
  * Builds pipeline upload payload from controller manifest.
40
- * Payload: { version, application, dataSources }; application = system with RBAC.
44
+ * Payload: { version, application, dataSources, status }; Builder always uses status "draft".
41
45
  * @param {Object} manifest - Controller manifest from generateControllerManifest
42
- * @returns {Object} { version, application, dataSources }
46
+ * @returns {Object} { version, application, dataSources, status: "draft" }
43
47
  */
44
48
  function buildUploadPayload(manifest) {
45
49
  return {
46
50
  version: manifest.version || '1.0.0',
47
51
  application: manifest.system,
48
- dataSources: manifest.dataSources || []
52
+ dataSources: manifest.dataSources || [],
53
+ status: 'draft'
49
54
  };
50
55
  }
51
56
 
52
57
  /**
53
58
  * Resolves dataplane URL and auth (same pattern as download).
54
59
  * @param {string} systemKey - System key
55
- * @param {Object} options - Options with optional dataplane override
56
60
  * @returns {Promise<{ dataplaneUrl: string, authConfig: Object, environment: string }>}
57
61
  */
58
- async function resolveDataplaneAndAuth(systemKey, options) {
62
+ async function resolveDataplaneAndAuth(systemKey) {
59
63
  const { resolveEnvironment } = require('../core/config');
60
64
  const environment = await resolveEnvironment();
61
65
  const controllerUrl = await resolveControllerUrl();
@@ -65,42 +69,42 @@ async function resolveDataplaneAndAuth(systemKey, options) {
65
69
  throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register <system-key>" first.');
66
70
  }
67
71
 
68
- let dataplaneUrl;
69
- if (options.dataplane) {
70
- dataplaneUrl = options.dataplane.replace(/\/$/, '');
71
- } else {
72
- logger.log(chalk.blue('Resolving dataplane URL...'));
73
- dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
74
- }
75
-
72
+ logger.log(chalk.blue('Resolving dataplane URL...'));
73
+ const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
76
74
  return { dataplaneUrl, authConfig, environment };
77
75
  }
78
76
 
79
77
  /**
80
- * Runs upload → validate → publish on the dataplane.
78
+ * Runs single pipeline upload (upload → validate → publish) on the dataplane.
81
79
  * @param {string} dataplaneUrl - Dataplane base URL
82
80
  * @param {Object} authConfig - Auth config
83
- * @param {Object} payload - { version, application, dataSources }
84
- * @returns {Promise<{ uploadId: string }>}
81
+ * @param {Object} payload - { version, application, dataSources, status: "draft" }
82
+ * @returns {Promise<Object>} Publication result from Dataplane
85
83
  */
86
84
  async function runUploadValidatePublish(dataplaneUrl, authConfig, payload) {
87
- const uploadRes = await uploadApplicationViaPipeline(dataplaneUrl, authConfig, payload);
88
- const uploadId = uploadRes?.data?.uploadId ?? uploadRes?.data?.id ?? uploadRes?.uploadId;
89
- if (!uploadId) {
90
- const msg = uploadRes?.success === false
91
- ? formatApiError(uploadRes, dataplaneUrl)
92
- : 'Upload did not return an upload ID';
85
+ const res = await uploadApplicationViaPipeline(dataplaneUrl, authConfig, payload);
86
+ if (res?.success === false) {
87
+ const msg = formatApiError(res, dataplaneUrl);
93
88
  throw new Error(msg);
94
89
  }
90
+ return res;
91
+ }
95
92
 
96
- const validateRes = await validateUploadViaPipeline(dataplaneUrl, uploadId, authConfig);
97
- if (validateRes?.success === false) {
98
- const msg = formatApiError(validateRes, dataplaneUrl);
99
- throw new Error(`Upload validation failed: ${msg}`);
93
+ /**
94
+ * Builds a short summary of validation errors for the thrown message.
95
+ * @param {Object} validationResult - Result from validateExternalSystemComplete
96
+ * @returns {string} First few errors joined for the error message
97
+ */
98
+ function formatValidationErrorSummary(validationResult) {
99
+ const errors = validationResult.errors || [];
100
+ if (errors.length === 0) {
101
+ return 'Validation failed. Fix errors before uploading.';
100
102
  }
101
-
102
- await publishUploadViaPipeline(dataplaneUrl, uploadId, authConfig);
103
- return { uploadId };
103
+ const maxShow = 3;
104
+ const shown = errors.slice(0, maxShow).map(e => (typeof e === 'string' ? e : String(e)));
105
+ const summary = shown.join('; ');
106
+ const more = errors.length > maxShow ? ` (and ${errors.length - maxShow} more)` : '';
107
+ return `Validation failed: ${summary}${more}. Fix errors above and run the command again.`;
104
108
  }
105
109
 
106
110
  /**
@@ -111,22 +115,62 @@ async function runUploadValidatePublish(dataplaneUrl, authConfig, payload) {
111
115
  function throwIfValidationFailed(validationResult) {
112
116
  if (!validationResult.valid) {
113
117
  displayValidationResults(validationResult);
114
- throw new Error('Validation failed. Fix errors before uploading.');
118
+ throw new Error(formatValidationErrorSummary(validationResult));
115
119
  }
116
120
  }
117
121
 
118
122
  /**
119
- * Uploads external system to dataplane (upload validate publish). No controller deploy.
123
+ * Resolves configuration arrays in the upload payload (application + dataSources) from env and secrets.
124
+ * @param {string} systemKey - System key
125
+ * @param {Object} payload - Upload payload (mutated)
126
+ */
127
+ async function resolvePayloadConfiguration(systemKey, payload) {
128
+ const { envMap, secrets } = await buildResolvedEnvMapForIntegration(systemKey);
129
+ if (Array.isArray(payload.application?.configuration) && payload.application.configuration.length > 0) {
130
+ resolveConfigurationValues(payload.application.configuration, envMap, secrets, systemKey);
131
+ }
132
+ for (const ds of payload.dataSources || []) {
133
+ if (Array.isArray(ds?.configuration) && ds.configuration.length > 0) {
134
+ resolveConfigurationValues(ds.configuration, envMap, secrets, systemKey);
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Pushes credential secrets from .env and payload to dataplane; logs result or warning.
141
+ * @param {string} dataplaneUrl - Dataplane URL
142
+ * @param {Object} authConfig - Auth config
143
+ * @param {string} systemKey - System key
144
+ * @param {Object} payload - Upload payload
145
+ */
146
+ async function pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey, payload) {
147
+ const envFilePath = path.join(getIntegrationPath(systemKey), '.env');
148
+ const pushResult = await pushCredentialSecrets(dataplaneUrl, authConfig, {
149
+ envFilePath,
150
+ appName: systemKey,
151
+ payload
152
+ });
153
+ if (pushResult.pushed > 0) {
154
+ const keyList = pushResult.keys?.length ? ` (${pushResult.keys.join(', ')})` : '';
155
+ logger.log(chalk.green(`Pushed ${pushResult.pushed} credential secret(s) to dataplane${keyList}.`));
156
+ } else {
157
+ logger.log(chalk.yellow('Secret push skipped'));
158
+ }
159
+ if (pushResult.warning) {
160
+ logger.log(chalk.yellow(`Warning: ${pushResult.warning}`));
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Uploads external system to dataplane (single pipeline upload). No controller deploy.
120
166
  * @param {string} systemKey - External system key (integration/<system-key>/)
121
167
  * @param {Object} [options] - Options
122
168
  * @param {boolean} [options.dryRun] - Validate and build payload only; no API calls
123
- * @param {string} [options.dataplane] - Override dataplane URL
124
169
  * @returns {Promise<void>}
125
170
  * @throws {Error} If validation or API calls fail
126
171
  */
127
172
  async function uploadExternalSystem(systemKey, options = {}) {
128
173
  validateSystemKeyFormat(systemKey);
129
-
130
174
  logger.log(chalk.blue(`\nUploading external system to dataplane: ${systemKey}`));
131
175
 
132
176
  const validationResult = await validateExternalSystemComplete(systemKey, { type: 'external' });
@@ -135,6 +179,7 @@ async function uploadExternalSystem(systemKey, options = {}) {
135
179
 
136
180
  const manifest = await generateControllerManifest(systemKey, { type: 'external' });
137
181
  const payload = buildUploadPayload(manifest);
182
+ await resolvePayloadConfiguration(systemKey, payload);
138
183
 
139
184
  if (options.dryRun) {
140
185
  logger.log(chalk.yellow('Dry run: would upload payload (no API calls).'));
@@ -142,12 +187,23 @@ async function uploadExternalSystem(systemKey, options = {}) {
142
187
  return;
143
188
  }
144
189
 
145
- const { dataplaneUrl, authConfig, environment } = await resolveDataplaneAndAuth(systemKey, options);
190
+ const { dataplaneUrl, authConfig, environment } = await resolveDataplaneAndAuth(systemKey);
146
191
  requireBearerForDataplanePipeline(authConfig);
147
192
  logger.log(chalk.blue(`Dataplane: ${dataplaneUrl}`));
193
+ logDataplanePipelineWarning();
148
194
 
195
+ await pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey, payload);
149
196
  await runUploadValidatePublish(dataplaneUrl, authConfig, payload);
197
+ logUploadSuccess(environment, systemKey, dataplaneUrl);
198
+ }
150
199
 
200
+ /**
201
+ * Logs upload success summary.
202
+ * @param {string} environment - Environment key
203
+ * @param {string} systemKey - System key
204
+ * @param {string} dataplaneUrl - Dataplane URL
205
+ */
206
+ function logUploadSuccess(environment, systemKey, dataplaneUrl) {
151
207
  logger.log(chalk.green('\nUpload validated and published to dataplane.'));
152
208
  logger.log(chalk.blue(`Environment: ${environment}`));
153
209
  logger.log(chalk.blue(`System: ${systemKey}`));
@@ -6,8 +6,14 @@
6
6
 
7
7
  const chalk = require('chalk');
8
8
  const ora = require('ora');
9
+ const path = require('path');
10
+ const fs = require('fs').promises;
11
+ const yaml = require('js-yaml');
9
12
  const logger = require('../utils/logger');
13
+ const { getIntegrationPath } = require('../utils/paths');
10
14
  const { parseOpenApi, testMcpConnection, credentialSelection } = require('../api/wizard.api');
15
+ const { listCredentials } = require('../api/credentials.api');
16
+ const { listExternalSystems, getExternalSystem } = require('../api/external-systems.api');
11
17
 
12
18
  /**
13
19
  * Parse OpenAPI file or URL
@@ -158,6 +164,7 @@ function buildConfigPreferences(configPrefs) {
158
164
  intent: configPrefs?.intent || 'general integration',
159
165
  fieldOnboardingLevel: configPrefs?.fieldOnboardingLevel || 'full',
160
166
  enableOpenAPIGeneration: configPrefs?.enableOpenAPIGeneration !== false,
167
+ debug: configPrefs?.debug === true,
161
168
  userPreferences: {
162
169
  enableMCP: configPrefs?.enableMCP || false,
163
170
  enableABAC: configPrefs?.enableABAC || false,
@@ -175,9 +182,10 @@ function buildConfigPreferences(configPrefs) {
175
182
  * @param {Object} params.prefs - Configuration preferences
176
183
  * @param {string} [params.credentialIdOrKey] - Credential ID or key
177
184
  * @param {string} [params.systemIdOrKey] - System ID or key
185
+ * @param {string} [params.entityName] - Entity name for multi-entity OpenAPI (from discover-entities)
178
186
  * @returns {Object} Configuration payload
179
187
  */
180
- function buildConfigPayload({ openapiSpec, detectedType, mode, prefs, credentialIdOrKey, systemIdOrKey }) {
188
+ function buildConfigPayload({ openapiSpec, detectedType, mode, prefs, credentialIdOrKey, systemIdOrKey, entityName }) {
181
189
  const detectedTypeValue = detectedType?.recommendedType || detectedType?.apiType || detectedType?.selectedType || 'record-based';
182
190
  const payload = {
183
191
  openapiSpec,
@@ -190,6 +198,32 @@ function buildConfigPayload({ openapiSpec, detectedType, mode, prefs, credential
190
198
  };
191
199
  if (credentialIdOrKey) payload.credentialIdOrKey = credentialIdOrKey;
192
200
  if (systemIdOrKey) payload.systemIdOrKey = systemIdOrKey;
201
+ if (entityName) payload.entityName = entityName;
202
+ if (prefs.debug) payload.debug = true;
203
+ return payload;
204
+ }
205
+
206
+ /**
207
+ * Build payload for platform config endpoint.
208
+ * Schema allows only: datasourceKeys?, credentialIdOrKey?, configurationValues?, debug?
209
+ * (additionalProperties: false - do NOT send intent, mode, fieldOnboardingLevel, etc.)
210
+ * @param {Object} params - Parameters object
211
+ * @param {string} [params.credentialIdOrKey] - Credential ID or key
212
+ * @param {string[]} [params.datasourceKeys] - Datasource keys to include (defaults to all)
213
+ * @param {Object} [params.configurationValues] - Configuration value overrides
214
+ * @param {boolean} [params.debug] - When true, capture debug log
215
+ * @returns {Object} Platform config payload
216
+ */
217
+ function buildPlatformConfigPayload({ credentialIdOrKey, datasourceKeys, configurationValues, debug }) {
218
+ const payload = {};
219
+ if (credentialIdOrKey) payload.credentialIdOrKey = credentialIdOrKey;
220
+ if (datasourceKeys && Array.isArray(datasourceKeys) && datasourceKeys.length > 0) {
221
+ payload.datasourceKeys = datasourceKeys;
222
+ }
223
+ if (configurationValues && typeof configurationValues === 'object' && Object.keys(configurationValues).length > 0) {
224
+ payload.configurationValues = configurationValues;
225
+ }
226
+ if (debug) payload.debug = true;
193
227
  return payload;
194
228
  }
195
229
 
@@ -250,14 +284,19 @@ function formatValidationDetailsPlain(errorData) {
250
284
  /**
251
285
  * Create and throw config generation error with optional formatted message
252
286
  * @param {Object} generateResponse - API response (error)
287
+ * @param {Object} [options] - Optional options
288
+ * @param {string} [options.debugManifestHint] - Hint to append when debug manifest was saved (e.g. review debug.log and fix manually)
253
289
  * @throws {Error}
254
290
  */
255
- function throwConfigGenerationError(generateResponse) {
291
+ function throwConfigGenerationError(generateResponse, options = {}) {
256
292
  const summary = generateResponse.error || generateResponse.formattedError || 'Unknown error';
257
293
  const detailsPlain = formatValidationDetailsPlain(generateResponse.errorData);
258
- const message = detailsPlain
294
+ let message = detailsPlain
259
295
  ? `Configuration generation failed: ${summary}\n${detailsPlain}`
260
296
  : `Configuration generation failed: ${summary}`;
297
+ if (options.debugManifestHint) {
298
+ message += `\n\n${options.debugManifestHint}`;
299
+ }
261
300
  const err = new Error(message);
262
301
  if (generateResponse.formattedError) {
263
302
  err.formatted = generateResponse.formattedError;
@@ -265,6 +304,181 @@ function throwConfigGenerationError(generateResponse) {
265
304
  throw err;
266
305
  }
267
306
 
307
+ /**
308
+ * Write debug log to integration/<appName>/debug.log
309
+ * @async
310
+ * @param {string} appName - Application name
311
+ * @param {string} content - Debug log content
312
+ */
313
+ async function writeDebugLog(appName, content) {
314
+ try {
315
+ const dir = getIntegrationPath(appName);
316
+ await fs.mkdir(dir, { recursive: true });
317
+ const debugPath = path.join(dir, 'debug.log');
318
+ await fs.writeFile(debugPath, content, 'utf8');
319
+ logger.log(chalk.gray(` Debug log saved to integration/${appName}/debug.log`));
320
+ } catch (e) {
321
+ logger.warn(`Could not save debug.log: ${e.message}`);
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Write systemConfig and datasourceConfig from error response for manual fix
327
+ * @async
328
+ * @param {string} appName - Application name
329
+ * @param {Object} [systemConfig] - System config from errorData
330
+ * @param {Object|Object[]} [datasourceConfig] - Datasource config(s) from errorData
331
+ * @returns {Promise<string[]>} Names of saved files
332
+ */
333
+ async function writeDebugManifest(appName, systemConfig, datasourceConfig) {
334
+ const saved = [];
335
+ try {
336
+ const dir = getIntegrationPath(appName);
337
+ await fs.mkdir(dir, { recursive: true });
338
+ if (systemConfig && typeof systemConfig === 'object') {
339
+ const systemPath = path.join(dir, 'debug-system.yaml');
340
+ await fs.writeFile(systemPath, yaml.dump(systemConfig, { lineWidth: -1 }), 'utf8');
341
+ saved.push('debug-system.yaml');
342
+ logger.log(chalk.gray(` Debug manifest saved to integration/${appName}/debug-system.yaml`));
343
+ }
344
+ if (datasourceConfig !== undefined && datasourceConfig !== null) {
345
+ const configs = Array.isArray(datasourceConfig) ? datasourceConfig : [datasourceConfig];
346
+ if (configs.length > 0 && configs.every(c => c && typeof c === 'object')) {
347
+ const datasourcePath = path.join(dir, 'debug-datasource.yaml');
348
+ const toWrite = configs.length === 1 ? configs[0] : configs;
349
+ await fs.writeFile(datasourcePath, yaml.dump(toWrite, { lineWidth: -1 }), 'utf8');
350
+ saved.push('debug-datasource.yaml');
351
+ logger.log(chalk.gray(` Debug manifest saved to integration/${appName}/debug-datasource.yaml`));
352
+ }
353
+ }
354
+ } catch (e) {
355
+ logger.warn(`Could not save debug manifest: ${e.message}`);
356
+ }
357
+ return saved;
358
+ }
359
+
360
+ /**
361
+ * Save debug manifest on error and throw
362
+ * @param {Object} generateResponse - API error response
363
+ * @param {Object} opts - Options
364
+ * @param {boolean} opts.enableDebug - Whether debug was enabled
365
+ * @param {string} [opts.appName] - App name for writing files
366
+ */
367
+ async function saveDebugManifestOnErrorAndThrow(generateResponse, opts) {
368
+ const { enableDebug, appName } = opts;
369
+ let debugManifestHint = null;
370
+ if (enableDebug && appName) {
371
+ const errorData = generateResponse.errorData || {};
372
+ const debugLog = errorData.debugLog;
373
+ if (debugLog && typeof debugLog === 'string') {
374
+ await writeDebugLog(appName, debugLog);
375
+ }
376
+ const systemConfig = errorData.systemConfig;
377
+ const datasourceConfig = errorData.datasourceConfig || errorData.datasourceConfigs;
378
+ const savedManifest = await writeDebugManifest(appName, systemConfig, datasourceConfig);
379
+ if (debugLog || savedManifest.length > 0) {
380
+ const files = [debugLog && 'debug.log', ...savedManifest].filter(Boolean).join(', ');
381
+ debugManifestHint = `Debug manifest saved to integration/${appName}/ (${files}). Review the log and fix the manifest manually, then run: aifabrix wizard ${appName}`;
382
+ }
383
+ }
384
+ throwConfigGenerationError(generateResponse, { debugManifestHint });
385
+ }
386
+
387
+ /** Throws with validation error; saves debug manifest when options.debug and options.appName are set. */
388
+ async function throwValidationFailureWithDebug(validateResponse, systemConfig, configs, errorMsg, options) {
389
+ if (!options.debug || !options.appName) throw new Error(`Configuration validation failed: ${errorMsg}`);
390
+ const errorData = validateResponse.errorData || validateResponse.data || {};
391
+ const debugLog = errorData.debugLog;
392
+ if (debugLog && typeof debugLog === 'string') await writeDebugLog(options.appName, debugLog);
393
+ const savedManifest = await writeDebugManifest(
394
+ options.appName, errorData.systemConfig || systemConfig, errorData.datasourceConfig || errorData.datasourceConfigs || configs
395
+ );
396
+ if (!debugLog && savedManifest.length === 0) throw new Error(`Configuration validation failed: ${errorMsg}`);
397
+ const files = [debugLog && 'debug.log', ...savedManifest].filter(Boolean).join(', ');
398
+ throw new Error(`Configuration validation failed: ${errorMsg}\n\nDebug manifest saved to integration/${options.appName}/ (${files}). Review the log and fix the manifest manually, then run: aifabrix wizard ${options.appName}`);
399
+ }
400
+
401
+ /**
402
+ * Resolve credential config from user action (select/skip/create)
403
+ * @async
404
+ * @param {string} dataplaneUrl - Dataplane URL
405
+ * @param {Object} authConfig - Auth config
406
+ * @param {Object} credentialAction - Result from promptForCredentialAction
407
+ * @returns {Promise<Object>} configCredential for handleCredentialSelection
408
+ */
409
+ async function resolveCredentialConfig(dataplaneUrl, authConfig, credentialAction) {
410
+ const { promptForExistingCredential } = require('../generator/wizard-prompts');
411
+ if (credentialAction.action !== 'select') {
412
+ return credentialAction.action === 'skip' ? { action: 'skip' } : { action: credentialAction.action };
413
+ }
414
+ let credentialsList = [];
415
+ try {
416
+ const listResponse = await listCredentials(dataplaneUrl, authConfig, {
417
+ activeOnly: true,
418
+ pageSize: 100
419
+ });
420
+ const body = listResponse?.data ?? listResponse;
421
+ const inner = Array.isArray(body) ? body : (body?.data ?? body);
422
+ credentialsList = Array.isArray(inner) ? inner : (inner?.credentials ?? inner?.items ?? []) || [];
423
+ } catch (_) {
424
+ credentialsList = [];
425
+ }
426
+ const selected = await promptForExistingCredential(Array.isArray(credentialsList) ? credentialsList : []);
427
+ return { action: 'select', credentialIdOrKey: selected.credentialIdOrKey };
428
+ }
429
+
430
+ /**
431
+ * Fetch external systems list from dataplane
432
+ * @async
433
+ * @param {string} dataplaneUrl - Dataplane URL
434
+ * @param {Object} authConfig - Auth config
435
+ * @returns {Promise<Object[]>} Systems list
436
+ */
437
+ async function fetchSystemsListForAddDatasource(dataplaneUrl, authConfig) {
438
+ try {
439
+ const listResponse = await listExternalSystems(dataplaneUrl, authConfig, { pageSize: 100 });
440
+ const body = listResponse?.data ?? listResponse;
441
+ const inner = Array.isArray(body) ? body : (body?.data ?? body);
442
+ const list = Array.isArray(inner) ? inner : (inner?.items ?? inner?.systems ?? []) || [];
443
+ return Array.isArray(list) ? list : [];
444
+ } catch (_) {
445
+ return [];
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Resolve and validate external system for add-datasource (prompt until valid)
451
+ * @async
452
+ * @param {string} dataplaneUrl - Dataplane URL
453
+ * @param {Object} authConfig - Auth config
454
+ * @param {Object[]} systemsList - Systems list
455
+ * @param {string} initialSystemIdOrKey - Initial system ID or key
456
+ * @returns {Promise<{systemResponse: Object, systemIdOrKey: string}>}
457
+ */
458
+ async function resolveExternalSystemForAddDatasource(dataplaneUrl, authConfig, systemsList, initialSystemIdOrKey) {
459
+ const { promptForExistingSystem } = require('../generator/wizard-prompts');
460
+ const { isExternalSystemForAddDatasource } = require('./wizard-helpers');
461
+ let systemIdOrKey = initialSystemIdOrKey;
462
+ let systemResponse;
463
+ for (;;) {
464
+ try {
465
+ systemResponse = await getExternalSystem(dataplaneUrl, systemIdOrKey, authConfig);
466
+ const sys = systemResponse?.data || systemResponse;
467
+ if (sys && (systemResponse?.data || systemResponse?.success)) {
468
+ if (!isExternalSystemForAddDatasource(sys)) {
469
+ logger.log(chalk.red('Cannot add datasource to a webapp. Please select an external system.'));
470
+ } else {
471
+ break;
472
+ }
473
+ }
474
+ } catch (err) {
475
+ logger.log(chalk.red(`System not found or error: ${err.message}`));
476
+ }
477
+ systemIdOrKey = await promptForExistingSystem(systemsList, systemIdOrKey);
478
+ }
479
+ return { systemResponse, systemIdOrKey };
480
+ }
481
+
268
482
  module.exports = {
269
483
  parseOpenApiSource,
270
484
  testMcpServerConnection,
@@ -272,7 +486,15 @@ module.exports = {
272
486
  runCredentialSelectionLoop,
273
487
  buildConfigPreferences,
274
488
  buildConfigPayload,
489
+ buildPlatformConfigPayload,
275
490
  extractConfigurationFromResponse,
276
491
  formatValidationDetailsPlain,
277
- throwConfigGenerationError
492
+ throwConfigGenerationError,
493
+ writeDebugLog,
494
+ writeDebugManifest,
495
+ saveDebugManifestOnErrorAndThrow,
496
+ throwValidationFailureWithDebug,
497
+ resolveCredentialConfig,
498
+ fetchSystemsListForAddDatasource,
499
+ resolveExternalSystemForAddDatasource
278
500
  };