@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
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Batch validation helpers for validate --integration / --builder.
3
+ * @fileoverview Builds batch results and runs validation across multiple apps
4
+ * @author AI Fabrix Team
5
+ * @version 2.0.0
6
+ */
7
+
8
+ const { listIntegrationAppNames, listBuilderAppNames } = require('../utils/paths');
9
+
10
+ /**
11
+ * Collects error strings from a single validation result (various shapes).
12
+ * @param {Object} result - Single-app validation result
13
+ * @returns {string[]} Error messages
14
+ */
15
+ function collectResultErrors(result) {
16
+ const errs = [];
17
+ if (result.errors && Array.isArray(result.errors)) {
18
+ errs.push(...result.errors);
19
+ }
20
+ if (result.application && result.application.errors && Array.isArray(result.application.errors)) {
21
+ errs.push(...result.application.errors);
22
+ }
23
+ if (result.steps) {
24
+ ['application', 'components', 'manifest'].forEach(step => {
25
+ const s = result.steps[step];
26
+ if (s && s.errors && Array.isArray(s.errors)) {
27
+ errs.push(...s.errors);
28
+ }
29
+ });
30
+ }
31
+ return errs;
32
+ }
33
+
34
+ /**
35
+ * Collects warning strings from a single validation result.
36
+ * @param {Object} result - Single-app validation result
37
+ * @returns {string[]} Warning messages
38
+ */
39
+ function collectResultWarnings(result) {
40
+ const w = [];
41
+ if (result.warnings && Array.isArray(result.warnings)) {
42
+ w.push(...result.warnings);
43
+ }
44
+ if (result.application && result.application.warnings && Array.isArray(result.application.warnings)) {
45
+ w.push(...result.application.warnings);
46
+ }
47
+ if (result.steps) {
48
+ ['application', 'components', 'manifest'].forEach(step => {
49
+ const s = result.steps[step];
50
+ if (s && s.warnings && Array.isArray(s.warnings)) {
51
+ w.push(...s.warnings);
52
+ }
53
+ });
54
+ }
55
+ return w;
56
+ }
57
+
58
+ /**
59
+ * Builds batch result from per-app results. Each item has appName and either result or error.
60
+ * @param {Array<{appName: string, result?: Object, error?: string}>} items - Per-app results
61
+ * @returns {{ batch: true, valid: boolean, results: Array, errors: string[], warnings: string[] }}
62
+ */
63
+ function buildBatchResult(items) {
64
+ const errors = [];
65
+ const warnings = [];
66
+ items.forEach(item => {
67
+ if (item.error) {
68
+ errors.push(`${item.appName}: ${item.error}`);
69
+ } else if (item.result) {
70
+ collectResultErrors(item.result).forEach(e => errors.push(`${item.appName}: ${e}`));
71
+ collectResultWarnings(item.result).forEach(w => warnings.push(`${item.appName}: ${w}`));
72
+ }
73
+ });
74
+ const valid = items.every(item => item.result && item.result.valid);
75
+ return {
76
+ batch: true,
77
+ valid,
78
+ results: items,
79
+ errors,
80
+ warnings
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Validates all apps under integration/ (each as external system).
86
+ * @async
87
+ * @param {Function} validateAppOrFile - validateAppOrFile(appName, options) from validate.js
88
+ * @param {Object} [options] - Validation options
89
+ * @returns {Promise<{ batch: true, valid: boolean, results: Array, errors: string[], warnings: string[] }>}
90
+ */
91
+ async function validateAllIntegrations(validateAppOrFile, options = {}) {
92
+ const names = listIntegrationAppNames();
93
+ const items = [];
94
+ for (const appName of names) {
95
+ try {
96
+ const result = await validateAppOrFile(appName, options);
97
+ items.push({ appName, result });
98
+ } catch (error) {
99
+ items.push({ appName, error: error.message || String(error) });
100
+ }
101
+ }
102
+ return buildBatchResult(items);
103
+ }
104
+
105
+ /**
106
+ * Validates all apps under builder/.
107
+ * @async
108
+ * @param {Function} validateAppOrFile - validateAppOrFile(appName, options) from validate.js
109
+ * @param {Object} [options] - Validation options
110
+ * @returns {Promise<{ batch: true, valid: boolean, results: Array, errors: string[], warnings: string[] }>}
111
+ */
112
+ async function validateAllBuilderApps(validateAppOrFile, options = {}) {
113
+ const names = listBuilderAppNames();
114
+ const items = [];
115
+ for (const appName of names) {
116
+ try {
117
+ const result = await validateAppOrFile(appName, options);
118
+ items.push({ appName, result });
119
+ } catch (error) {
120
+ items.push({ appName, error: error.message || String(error) });
121
+ }
122
+ }
123
+ return buildBatchResult(items);
124
+ }
125
+
126
+ /**
127
+ * Validates all integration and builder apps in one run.
128
+ * @async
129
+ * @param {Function} validateAppOrFile - validateAppOrFile(appName, options) from validate.js
130
+ * @param {Object} [options] - Validation options
131
+ * @returns {Promise<{ batch: true, valid: boolean, results: Array, errors: string[], warnings: string[] }>}
132
+ */
133
+ async function validateAll(validateAppOrFile, options = {}) {
134
+ const [integrationResult, builderResult] = await Promise.all([
135
+ validateAllIntegrations(validateAppOrFile, options),
136
+ validateAllBuilderApps(validateAppOrFile, options)
137
+ ]);
138
+ const mergedResults = [...integrationResult.results, ...builderResult.results];
139
+ return buildBatchResult(mergedResults);
140
+ }
141
+
142
+ module.exports = {
143
+ buildBatchResult,
144
+ collectResultErrors,
145
+ collectResultWarnings,
146
+ validateAllIntegrations,
147
+ validateAllBuilderApps,
148
+ validateAll
149
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @fileoverview Async validation of datasourceKeys against dataplane API
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const { getPlatformDetails } = require('../api/wizard.api');
8
+ const { validateDatasourceKeysForPlatform } = require('./wizard-datasource-validation');
9
+
10
+ /**
11
+ * Validate datasourceKeys against platform's available datasources; throws if invalid
12
+ * @async
13
+ * @param {string} dataplaneUrl - Dataplane URL
14
+ * @param {Object} authConfig - Auth config
15
+ * @param {string} platformKey - Platform key
16
+ * @param {string[]} datasourceKeys - Datasource keys to validate
17
+ * @throws {Error} If any key is invalid
18
+ */
19
+ async function validateDatasourceKeysBeforePlatformConfig(dataplaneUrl, authConfig, platformKey, datasourceKeys) {
20
+ if (!Array.isArray(datasourceKeys) || datasourceKeys.length === 0) return;
21
+ const platformDetails = await getPlatformDetails(dataplaneUrl, authConfig, platformKey);
22
+ const datasources = platformDetails?.data?.datasources ?? platformDetails?.datasources ?? [];
23
+ const { valid, invalidKeys } = validateDatasourceKeysForPlatform(datasourceKeys, datasources);
24
+ if (!valid) {
25
+ const availableKeys = datasources.map(d => d.key).filter(Boolean);
26
+ throw new Error(
27
+ `Invalid datasource keys: [${invalidKeys.join(', ')}]. ` +
28
+ `Available for platform '${platformKey}': [${availableKeys.join(', ')}].`
29
+ );
30
+ }
31
+ }
32
+
33
+ module.exports = { validateDatasourceKeysBeforePlatformConfig };
@@ -8,6 +8,7 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
+ const path = require('path');
11
12
  const chalk = require('chalk');
12
13
  const logger = require('../utils/logger');
13
14
  const { loadConfigFile } = require('../utils/config-format');
@@ -242,22 +243,25 @@ function displayApplicationStep(application) {
242
243
  * @returns {void}
243
244
  */
244
245
  function displayComponentsStep(components) {
245
- if (!components.files || components.files.length === 0) {
246
+ const hasFiles = components.files && components.files.length > 0;
247
+ const hasErrors = components.errors && components.errors.length > 0;
248
+ if (!hasFiles && !hasErrors) {
246
249
  return;
247
250
  }
248
251
 
249
252
  logger.log(chalk.blue('\nExternal Integration Files:'));
250
- components.files.forEach(file => {
251
- if (file.valid) {
252
- logger.log(chalk.green(` ✓ ${file.file} (${file.type})`));
253
- } else {
254
- logger.log(chalk.red(` ✗ ${file.file} (${file.type})`));
255
- }
256
- });
257
-
258
- if (components.errors && components.errors.length > 0) {
253
+ if (hasFiles) {
254
+ components.files.forEach(file => {
255
+ if (file.valid) {
256
+ logger.log(chalk.green(` ✓ ${file.file} (${file.type})`));
257
+ } else {
258
+ logger.log(chalk.red(` ✗ ${file.file} (${file.type})`));
259
+ }
260
+ });
261
+ }
262
+ if (hasErrors) {
259
263
  components.errors.forEach(error => {
260
- logger.log(chalk.red(` • ${error}`));
264
+ logger.log(chalk.red(` • ${error}`));
261
265
  });
262
266
  }
263
267
  }
@@ -301,7 +305,9 @@ function displayDimensionsStep(datasourceFiles) {
301
305
  */
302
306
  function displayManifestStep(manifest, componentFiles) {
303
307
  logger.log(chalk.blue('\nDeployment Manifest:'));
304
- if (manifest.valid) {
308
+ if (manifest.skipped) {
309
+ logger.log(chalk.yellow(' ⊘ Skipped (fix errors above first)'));
310
+ } else if (manifest.valid) {
305
311
  logger.log(chalk.green(' ✓ Full deployment manifest is valid'));
306
312
  if (componentFiles) {
307
313
  const datasourceFiles = componentFiles.filter(f => f.type === 'datasource' || f.type === 'external-datasource');
@@ -311,10 +317,14 @@ function displayManifestStep(manifest, componentFiles) {
311
317
  }
312
318
  } else {
313
319
  logger.log(chalk.red(' ✗ Full deployment manifest validation failed:'));
314
- if (manifest.errors && manifest.errors.length > 0) {
315
- manifest.errors.forEach(error => {
316
- logger.log(chalk.red(` • ${error}`));
320
+ const errs = manifest.errors && manifest.errors.length > 0 ? manifest.errors : [];
321
+ if (errs.length > 0) {
322
+ errs.forEach(error => {
323
+ const msg = typeof error === 'string' ? error : String(error);
324
+ logger.log(chalk.red(` • ${msg}`));
317
325
  });
326
+ } else {
327
+ logger.log(chalk.red(' • No error details available (check schema and manifest structure).'));
318
328
  }
319
329
  }
320
330
  if (manifest.warnings && manifest.warnings.length > 0) {
@@ -338,7 +348,7 @@ function displayStepByStepValidation(result) {
338
348
 
339
349
  displayApplicationStep(result.steps.application);
340
350
 
341
- if (result.steps.components.files && result.steps.components.files.length > 0) {
351
+ if (!result.steps.components.valid || (result.steps.components.files && result.steps.components.files.length > 0)) {
342
352
  displayComponentsStep(result.steps.components);
343
353
  }
344
354
 
@@ -356,6 +366,68 @@ function displayStepByStepValidation(result) {
356
366
  if (result.warnings && result.warnings.length > 0) {
357
367
  displayAggregatedWarnings(result.warnings);
358
368
  }
369
+
370
+ displayOverallStatus(result);
371
+ if (result.appPath) {
372
+ logger.log(chalk.gray(`\n${path.resolve(result.appPath)}`));
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Displays batch validation results (per-app blocks then summary).
378
+ * Expects batchResult.batch === true and batchResult.results.
379
+ * @function displayBatchValidationResults
380
+ * @param {Object} batchResult - Batch result from validateAllIntegrations / validateAllBuilderApps / validateAll
381
+ */
382
+ function displayBatchValidationResults(batchResult) {
383
+ if (!batchResult || batchResult.batch !== true || !Array.isArray(batchResult.results)) {
384
+ return;
385
+ }
386
+
387
+ const results = batchResult.results;
388
+ results.forEach(item => {
389
+ logger.log(chalk.blue(`\n--- ${item.appName} ---`));
390
+ if (item.error) {
391
+ logger.log(chalk.red(` ✗ ${item.error}`));
392
+ } else if (item.result) {
393
+ displayValidationResults(item.result);
394
+ }
395
+ });
396
+
397
+ const passed = results.filter(r => r.result && r.result.valid).length;
398
+ const failed = results.length - passed;
399
+ logger.log(chalk.blue('\n--- Summary ---'));
400
+ if (failed === 0) {
401
+ logger.log(chalk.green(`✓ ${passed} passed, 0 failed`));
402
+ logger.log(chalk.green('Overall: Passed'));
403
+ } else {
404
+ logger.log(chalk.red(`✗ ${passed} passed, ${failed} failed`));
405
+ logger.log(chalk.red('Overall: Failed'));
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Displays overall validation status
411
+ * @param {Object} result - Validation result
412
+ */
413
+ function displayOverallStatus(result) {
414
+ let hasErrors = result.errors && result.errors.length > 0;
415
+ if (!hasErrors && result.steps) {
416
+ const stepErrors = [result.steps.application, result.steps.components, result.steps.manifest]
417
+ .filter(Boolean)
418
+ .some(s => s.errors && s.errors.length > 0);
419
+ if (stepErrors) {
420
+ hasErrors = true;
421
+ }
422
+ }
423
+ const hasWarnings = result.warnings && result.warnings.length > 0;
424
+ if (hasErrors) {
425
+ logger.log(chalk.red('\nOverall: Failed'));
426
+ } else if (hasWarnings) {
427
+ logger.log(chalk.yellow('\nOverall: Passed with warnings'));
428
+ } else {
429
+ logger.log(chalk.green('\nOverall: Passed'));
430
+ }
359
431
  }
360
432
 
361
433
  /**
@@ -390,10 +462,16 @@ function displayValidationResults(result) {
390
462
  // Combine all warnings
391
463
  const allWarnings = [...(result.warnings || []), ...dimensionWarnings];
392
464
  displayAggregatedWarnings(allWarnings);
465
+
466
+ displayOverallStatus(result);
467
+ if (result.appPath) {
468
+ logger.log(chalk.gray(`\n${path.resolve(result.appPath)}`));
469
+ }
393
470
  }
394
471
 
395
472
  module.exports = {
396
473
  displayValidationResults,
474
+ displayBatchValidationResults,
397
475
  displayStepByStepValidation,
398
476
  displayApplicationValidation,
399
477
  displayExternalFilesValidation,
@@ -16,11 +16,16 @@ const { resolveExternalFiles } = require('../utils/schema-resolver');
16
16
  const { loadExternalSystemSchema, loadExternalDataSourceSchema, detectSchemaType } = require('../utils/schema-loader');
17
17
  const { formatValidationErrors } = require('../utils/error-formatter');
18
18
  const { detectAppType } = require('../utils/paths');
19
+ const batch = require('./validate-batch');
19
20
  const { logOfflinePathWhenType } = require('../utils/cli-utils');
20
21
  const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
21
- const { displayValidationResults } = require('./validate-display');
22
+ const { displayValidationResults, displayBatchValidationResults } = require('./validate-display');
22
23
  const { generateControllerManifest } = require('../generator/external-controller-manifest');
23
24
  const { validateControllerManifest } = require('./external-manifest-validator');
25
+ const {
26
+ validateOAuth2GrantTypeAndAuthorizationUrl,
27
+ validateConfigurationNoStandardAuthVariables
28
+ } = require('./external-system-auth-rules');
24
29
 
25
30
  /**
26
31
  * Validates a file path (detects type and validates)
@@ -191,6 +196,8 @@ async function validateExternalFile(filePath, type) {
191
196
 
192
197
  if (normalizedType === 'system') {
193
198
  validateRoleReferences(parseResult.parsed, errors);
199
+ validateOAuth2GrantTypeAndAuthorizationUrl(parseResult.parsed, errors);
200
+ validateConfigurationNoStandardAuthVariables(parseResult.parsed, errors);
194
201
  }
195
202
 
196
203
  return {
@@ -398,6 +405,7 @@ async function validateExternalSystemComplete(appName, options = {}) {
398
405
  throw new Error('App name is required and must be a string');
399
406
  }
400
407
 
408
+ const { appPath } = await detectAppType(appName, options);
401
409
  const steps = {
402
410
  application: { valid: false, errors: [], warnings: [] },
403
411
  components: { valid: false, errors: [], warnings: [], files: [] },
@@ -414,11 +422,13 @@ async function validateExternalSystemComplete(appName, options = {}) {
414
422
 
415
423
  // If components have errors, return early (don't validate manifest)
416
424
  if (!steps.components.valid) {
425
+ steps.manifest = { valid: true, errors: [], warnings: [], skipped: true };
417
426
  return {
418
427
  valid: false,
419
428
  errors: [...steps.application.errors, ...steps.components.errors],
420
429
  warnings: [...steps.application.warnings, ...steps.components.warnings],
421
- steps
430
+ steps,
431
+ appPath
422
432
  };
423
433
  }
424
434
 
@@ -433,7 +443,8 @@ async function validateExternalSystemComplete(appName, options = {}) {
433
443
  valid: allErrors.length === 0,
434
444
  errors: allErrors,
435
445
  warnings: allWarnings,
436
- steps
446
+ steps,
447
+ appPath
437
448
  };
438
449
  }
439
450
 
@@ -451,29 +462,31 @@ async function validateAppOrFile(appOrFile, options = {}) {
451
462
  const { appPath, isExternal } = await detectAppType(appName);
452
463
  logOfflinePathWhenType(appPath);
453
464
 
454
- if (isExternal) {
455
- return await validateExternalSystemComplete(appName, options);
456
- }
465
+ if (isExternal) return await validateExternalSystemComplete(appName, options);
457
466
 
458
467
  const appValidation = await validator.validateApplication(appName, options);
459
468
  const rbacValidation = await validateRbacForExternalSystem(isExternal, appName);
460
-
461
469
  const variablesPath = resolveApplicationConfigPath(appPath);
462
470
  const earlyReturn = loadVariablesAndCheckExternalIntegration(variablesPath, appValidation);
463
- if (earlyReturn) {
464
- return earlyReturn;
465
- }
471
+ if (earlyReturn) return earlyReturn;
466
472
 
467
473
  const externalValidations = await validateExternalFilesForApp(appName, options);
468
- return aggregateValidationResults(appValidation, externalValidations, rbacValidation);
474
+ const result = aggregateValidationResults(appValidation, externalValidations, rbacValidation);
475
+ result.appPath = appPath;
476
+ return result;
469
477
  }
470
478
 
471
479
  module.exports = {
472
480
  validateAppOrFile,
473
481
  validateExternalSystemComplete,
474
482
  displayValidationResults,
483
+ displayBatchValidationResults,
475
484
  validateExternalFile,
476
485
  validateExternalFilesForApp,
477
- validateFilePath
486
+ validateFilePath,
487
+ validateAllIntegrations: (opts = {}) => batch.validateAllIntegrations(validateAppOrFile, opts),
488
+ validateAllBuilderApps: (opts = {}) => batch.validateAllBuilderApps(validateAppOrFile, opts),
489
+ validateAll: (opts = {}) => batch.validateAll(validateAppOrFile, opts),
490
+ buildBatchResult: batch.buildBatchResult
478
491
  };
479
492
 
@@ -21,6 +21,8 @@ const { checkEnvironment } = require('../utils/environment-checker');
21
21
  const { formatValidationErrors } = require('../utils/error-formatter');
22
22
  const { detectAppType, resolveApplicationConfigPath } = require('../utils/paths');
23
23
  const { loadConfigFile } = require('../utils/config-format');
24
+ const { validateAuthKvCoverage } = require('./env-template-auth');
25
+ const { validateKvReferencesInLines } = require('./env-template-kv');
24
26
 
25
27
  /**
26
28
  * Validates application config file against application schema
@@ -250,7 +252,7 @@ async function validateEnvTemplate(appName, options = {}) {
250
252
  }
251
253
 
252
254
  // Support both builder/ and integration/ directories using detectAppType
253
- const { appPath } = await detectAppType(appName, options);
255
+ const { appPath, isExternal } = await detectAppType(appName, options);
254
256
  const templatePath = path.join(appPath, 'env.template');
255
257
 
256
258
  if (!fs.existsSync(templatePath)) {
@@ -281,14 +283,10 @@ async function validateEnvTemplate(appName, options = {}) {
281
283
  }
282
284
  });
283
285
 
284
- // Check for kv:// reference format
285
- const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
286
- let match;
287
- while ((match = kvPattern.exec(content)) !== null) {
288
- const secretKey = match[1];
289
- if (!secretKey) {
290
- errors.push('Invalid kv:// reference format');
291
- }
286
+ validateKvReferencesInLines(lines, errors);
287
+
288
+ if (isExternal) {
289
+ await validateAuthKvCoverage(appPath, content, errors, warnings, options);
292
290
  }
293
291
 
294
292
  return {
@@ -365,6 +363,69 @@ function validateDeploymentJson(deployment) {
365
363
  };
366
364
  }
367
365
 
366
+ /** Pattern matching ${VAR} style unresolved variables in strings */
367
+ const UNRESOLVED_VAR_REGEX = /\$\{[^}]+\}/g;
368
+
369
+ /**
370
+ * Recursively finds all string values in obj that contain ${...} placeholders.
371
+ * Used to ensure deployment manifest has no unresolved variables before deploy.
372
+ *
373
+ * @function findUnresolvedVariablesInObject
374
+ * @param {Object} obj - Object to scan (e.g. deployment manifest)
375
+ * @param {string} [prefix=''] - Path prefix for error reporting
376
+ * @returns {string[]} List of paths with example placeholder (e.g. "port: ${PORT}")
377
+ *
378
+ * @example
379
+ * findUnresolvedVariablesInObject({ port: '${PORT}' }) // ['port: ${PORT}']
380
+ */
381
+ function findUnresolvedVariablesInObject(obj, prefix = '') {
382
+ const found = [];
383
+ if (obj === null || obj === undefined) {
384
+ return found;
385
+ }
386
+ if (typeof obj === 'string') {
387
+ const matches = obj.match(UNRESOLVED_VAR_REGEX);
388
+ if (matches && matches.length > 0) {
389
+ const pathLabel = prefix || 'value';
390
+ found.push(`${pathLabel}: ${matches[0]}`);
391
+ }
392
+ return found;
393
+ }
394
+ if (Array.isArray(obj)) {
395
+ obj.forEach((item, i) => {
396
+ found.push(...findUnresolvedVariablesInObject(item, `${prefix}[${i}]`));
397
+ });
398
+ return found;
399
+ }
400
+ if (typeof obj === 'object') {
401
+ for (const [key, value] of Object.entries(obj)) {
402
+ const path = prefix ? `${prefix}.${key}` : key;
403
+ found.push(...findUnresolvedVariablesInObject(value, path));
404
+ }
405
+ return found;
406
+ }
407
+ return found;
408
+ }
409
+
410
+ /**
411
+ * Validates that deployment manifest contains no unresolved ${...} variables.
412
+ * Throws if any are found, with a message to use secret variables or literal values.
413
+ *
414
+ * @function validateNoUnresolvedVariablesInDeployment
415
+ * @param {Object} deployment - Deployment manifest object
416
+ * @throws {Error} If any ${...} placeholders are found
417
+ */
418
+ function validateNoUnresolvedVariablesInDeployment(deployment) {
419
+ const unresolved = findUnresolvedVariablesInObject(deployment);
420
+ if (unresolved.length > 0) {
421
+ const examples = [...new Set(unresolved)].slice(0, 5).join(', ');
422
+ throw new Error(
423
+ `Deployment manifest contains unresolved variables (e.g. ${examples}). ` +
424
+ 'Use secret variables (kv://) in env.template for sensitive values, and set the application port as a number in application.yaml.'
425
+ );
426
+ }
427
+ }
428
+
368
429
  /**
369
430
  * Validates all application configuration files
370
431
  * Runs complete validation suite for an application
@@ -411,6 +472,8 @@ module.exports = {
411
472
  validateRbac,
412
473
  validateEnvTemplate,
413
474
  validateDeploymentJson,
475
+ validateNoUnresolvedVariablesInDeployment,
476
+ findUnresolvedVariablesInObject,
414
477
  validateObjectAgainstApplicationSchema,
415
478
  checkEnvironment,
416
479
  formatValidationErrors,
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @fileoverview Wizard datasource validation helpers - validate datasourceKeys and entityName against dataplane
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ /**
8
+ * Validate that all datasourceKeys exist in the platform's available datasources
9
+ * @function validateDatasourceKeysForPlatform
10
+ * @param {string[]} datasourceKeys - User-provided datasource keys
11
+ * @param {Array<{key: string, displayName?: string, entity?: string}>} availableDatasources - Platform datasources
12
+ * @returns {{ valid: boolean, invalidKeys: string[] }} Validation result
13
+ */
14
+ function validateDatasourceKeysForPlatform(datasourceKeys, availableDatasources) {
15
+ if (!Array.isArray(datasourceKeys) || datasourceKeys.length === 0) {
16
+ return { valid: true, invalidKeys: [] };
17
+ }
18
+ const availableKeys = Array.isArray(availableDatasources)
19
+ ? availableDatasources.map(d => (d && typeof d === 'object' && d.key) ? d.key : null).filter(Boolean)
20
+ : [];
21
+ const invalidKeys = datasourceKeys.filter(k => !availableKeys.includes(k));
22
+ return {
23
+ valid: invalidKeys.length === 0,
24
+ invalidKeys
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Validate that entityName exists in the discovered entities list
30
+ * @function validateEntityNameForOpenApi
31
+ * @param {string} entityName - User-provided entity name
32
+ * @param {Array<{name: string}>} entities - Discovered entities from OpenAPI
33
+ * @returns {{ valid: boolean }} Validation result
34
+ */
35
+ function validateEntityNameForOpenApi(entityName, entities) {
36
+ if (!entityName || typeof entityName !== 'string' || entityName.trim() === '') {
37
+ return { valid: true };
38
+ }
39
+ const entityNames = Array.isArray(entities)
40
+ ? entities.map(e => (e && typeof e === 'object' && e.name) ? e.name : null).filter(Boolean)
41
+ : [];
42
+ return {
43
+ valid: entityNames.includes(entityName)
44
+ };
45
+ }
46
+
47
+ module.exports = {
48
+ validateDatasourceKeysForPlatform,
49
+ validateEntityNameForOpenApi
50
+ };
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.40.2",
3
+ "version": "2.42.0",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
7
7
  "aifabrix": "bin/aifabrix.js",
8
- "aifx": "bin/aifabrix.js"
8
+ "af": "bin/aifabrix.js"
9
9
  },
10
10
  "scripts": {
11
11
  "test": "node tests/scripts/test-wrapper.js",
12
12
  "test:ci": "bash tests/scripts/ci-simulate.sh",
13
+ "test:same-as-github": "cross-env CI=true npm run lint && cross-env CI=true node tests/scripts/test-wrapper.js",
13
14
  "test:coverage": "cross-env RUN_COVERAGE=1 node tests/scripts/test-wrapper.js",
14
15
  "test:coverage:nyc": "nyc --reporter=text --reporter=lcov --reporter=html jest --config jest.config.coverage.js --runInBand",
15
16
  "test:watch": "jest --watch",
@@ -71,13 +72,16 @@
71
72
  },
72
73
  "dependencies": {
73
74
  "ajv": "^8.12.0",
75
+ "ajv-formats": "^3.0.1",
74
76
  "axios": "^1.6.0",
75
77
  "chalk": "^4.1.2",
76
78
  "commander": "^11.1.0",
77
79
  "handlebars": "^4.7.8",
78
80
  "inquirer": "^8.2.5",
81
+ "inquirer-autocomplete-prompt": "^2.0.0",
79
82
  "js-yaml": "^4.1.0",
80
- "ora": "^5.4.1"
83
+ "ora": "^5.4.1",
84
+ "yaml": "^2.4.0"
81
85
  },
82
86
  "devDependencies": {
83
87
  "@babel/preset-env": "^7.29.0",
@@ -86,6 +90,7 @@
86
90
  "cross-env": "^10.1.0",
87
91
  "eslint": "^8.55.0",
88
92
  "jest": "^30.2.0",
93
+ "markdownlint-cli": "^0.47.0",
89
94
  "nyc": "^17.1.0"
90
95
  },
91
96
  "repository": {