@aifabrix/builder 2.44.2 → 2.44.4

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 (62) hide show
  1. package/.npmrc.token +1 -1
  2. package/integration/roundtrip-test-local/README.md +1 -2
  3. package/integration/roundtrip-test-local2/README.md +1 -2
  4. package/jest.projects.js +12 -1
  5. package/lib/api/certificates.api.js +21 -3
  6. package/lib/api/types/certificates.types.js +1 -1
  7. package/lib/app/show-display.js +60 -16
  8. package/lib/certification/merge-certification-from-artifact.js +46 -16
  9. package/lib/certification/post-unified-cert-sync.js +13 -2
  10. package/lib/certification/sync-after-external-command.js +6 -3
  11. package/lib/certification/sync-system-certification.js +60 -14
  12. package/lib/cli/setup-app.test-commands.js +67 -35
  13. package/lib/cli/setup-utility.js +1 -1
  14. package/lib/commands/datasource-unified-test-cli.js +81 -46
  15. package/lib/commands/datasource-unified-test-cli.options.js +4 -2
  16. package/lib/commands/datasource.js +3 -31
  17. package/lib/commands/repair-datasource-keys.js +1 -1
  18. package/lib/commands/repair-datasource-openapi.js +57 -0
  19. package/lib/commands/repair-datasource.js +5 -0
  20. package/lib/commands/repair-internal.js +2 -4
  21. package/lib/commands/repair.js +1 -2
  22. package/lib/commands/test-e2e-external.js +5 -6
  23. package/lib/commands/upload.js +18 -4
  24. package/lib/commands/wizard-dataplane.js +14 -6
  25. package/lib/datasource/datasource-validate-display.js +162 -0
  26. package/lib/datasource/datasource-validate-summary.js +194 -0
  27. package/lib/datasource/test-e2e.js +65 -37
  28. package/lib/datasource/unified-validation-run-body.js +1 -2
  29. package/lib/datasource/validate.js +14 -6
  30. package/lib/external-system/test.js +12 -8
  31. package/lib/generator/external-controller-manifest.js +12 -2
  32. package/lib/schema/cip-capacity-display.fallback.json +7 -0
  33. package/lib/schema/datasource-test-run.schema.json +79 -1
  34. package/lib/schema/external-datasource.schema.json +94 -2
  35. package/lib/schema/external-system.schema.json +29 -6
  36. package/lib/schema/flag-map-validation-run.json +1 -2
  37. package/lib/schema/type/document-storage.json +83 -3
  38. package/lib/utils/configuration-env-resolver.js +38 -0
  39. package/lib/utils/dataplane-resolver.js +3 -2
  40. package/lib/utils/datasource-test-run-capacity-operations.js +149 -0
  41. package/lib/utils/datasource-test-run-debug-display.js +143 -1
  42. package/lib/utils/datasource-test-run-display.js +46 -33
  43. package/lib/utils/datasource-test-run-tty-log.js +6 -2
  44. package/lib/utils/datasource-test-run-tty-meta-lines.js +123 -0
  45. package/lib/utils/error-formatter.js +32 -2
  46. package/lib/utils/external-system-readiness-core.js +39 -0
  47. package/lib/utils/external-system-readiness-deploy-display.js +2 -3
  48. package/lib/utils/external-system-readiness-display-internals.js +3 -2
  49. package/lib/utils/external-system-system-test-tty.js +33 -9
  50. package/lib/utils/external-system-validators.js +62 -5
  51. package/lib/utils/load-cip-capacity-display-config.js +130 -0
  52. package/lib/utils/paths.js +10 -3
  53. package/lib/utils/schema-resolver.js +98 -2
  54. package/lib/utils/validation-run-poll.js +15 -4
  55. package/lib/utils/validation-run-request.js +4 -6
  56. package/lib/validation/dimension-display-helpers.js +60 -0
  57. package/lib/validation/external-manifest-validator.js +22 -15
  58. package/lib/validation/validate-display-log-helpers.js +39 -0
  59. package/lib/validation/validate-display.js +89 -83
  60. package/package.json +1 -1
  61. package/templates/applications/dataplane/env.template +2 -1
  62. package/templates/external-system/README.md.hbs +1 -2
@@ -118,7 +118,7 @@ function setupResolveCommand(program) {
118
118
  );
119
119
  logger.log(`✔ Generated .env file: ${envPath}`);
120
120
  if (envOnly) {
121
- logger.log(chalk.gray(' (env-only mode: validation skipped; no application.yaml)'));
121
+ logger.log(chalk.gray(' (env-only mode: validation skipped; no application config file)'));
122
122
  } else if (!options.skipValidation) {
123
123
  const validate = require('../validation/validate');
124
124
  const result = await validate.validateAppOrFile(appName);
@@ -94,7 +94,7 @@ async function runDatasourceUnifiedTestOnceForWatch(datasourceKey, runOpts, disp
94
94
  try {
95
95
  const result = await runUnifiedDatasourceValidation(datasourceKey, runOpts);
96
96
  const exitCode = finalizeUnifiedValidationResult(result, displayOpts);
97
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, certCliOptions, 'datasource test');
97
+ await afterUnifiedValidationCertSync(exitCode, datasourceKey, certCliOptions, 'datasource test', result.envelope);
98
98
  return {
99
99
  exitCode,
100
100
  envelope: result.envelope
@@ -121,7 +121,7 @@ async function datasourceTestCommandAction(datasourceKey, options) {
121
121
  const result = await runUnifiedDatasourceValidation(datasourceKey, runOpts);
122
122
  await writeDatasourceTestDebugLogIfRequested(appKey, datasourceKey, result, options);
123
123
  const exitCode = finalizeUnifiedValidationResult(result, displayOpts);
124
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test (watch)');
124
+ await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test (watch)', result.envelope);
125
125
  return {
126
126
  exitCode,
127
127
  envelope: result.envelope
@@ -140,7 +140,7 @@ async function datasourceTestCommandAction(datasourceKey, options) {
140
140
  }
141
141
  }
142
142
  const exitCode = finalizeUnifiedValidationResult(result, displayOpts);
143
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test');
143
+ await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test', result.envelope);
144
144
  process.exit(exitCode);
145
145
  } catch (error) {
146
146
  logger.error(formatBlockingError('Datasource test failed:'), error.message);
@@ -195,7 +195,13 @@ async function runIntegrationOnceForWatch(datasourceKey, integOpts, options, uni
195
195
  if (unifiedModes) {
196
196
  const uni = unifiedCliResultFromIntegrationReturn(result);
197
197
  const exitCode = finalizeUnifiedValidationResult(uni, unifiedDisplayOpts);
198
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-integration (watch)');
198
+ await afterUnifiedValidationCertSync(
199
+ exitCode,
200
+ datasourceKey,
201
+ options,
202
+ 'datasource test-integration (watch)',
203
+ uni.envelope
204
+ );
199
205
  return { exitCode, envelope: uni.envelope };
200
206
  }
201
207
  displayIntegrationTestResults(
@@ -211,7 +217,13 @@ async function runIntegrationOnceForWatch(datasourceKey, integOpts, options, uni
211
217
  warningsAsErrors: unifiedDisplayOpts.warningsAsErrors === true,
212
218
  requireCert: unifiedDisplayOpts.requireCert === true
213
219
  });
214
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-integration (watch)');
220
+ await afterUnifiedValidationCertSync(
221
+ exitCode,
222
+ datasourceKey,
223
+ options,
224
+ 'datasource test-integration (watch)',
225
+ result.datasourceTestRun || null
226
+ );
215
227
  return { exitCode, envelope: result.datasourceTestRun || null };
216
228
  } catch (err) {
217
229
  logger.error(formatBlockingError('Integration test failed:'), err.message);
@@ -219,49 +231,67 @@ async function runIntegrationOnceForWatch(datasourceKey, integOpts, options, uni
219
231
  }
220
232
  }
221
233
 
234
+ async function integrationTestCommandActionWatch(datasourceKey, options, integOpts, unifiedDisplayOpts) {
235
+ const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
236
+ await runDatasourceValidationWatchLoop({
237
+ appKey,
238
+ extraPaths: options.watchPath || [],
239
+ includeApplicationYaml: options.watchApplicationYaml === true,
240
+ watchCi: options.watchCi === true,
241
+ watchFullDiff: options.watchFullDiff === true,
242
+ runOnce: () => runIntegrationOnceForWatch(datasourceKey, integOpts, options, unifiedDisplayOpts)
243
+ });
244
+ }
245
+
246
+ async function integrationTestCommandActionNonWatch(datasourceKey, integOpts, options, unifiedDisplayOpts) {
247
+ const result = await runDatasourceTestIntegration(datasourceKey, integOpts);
248
+ const unifiedModes =
249
+ options.json || options.summary || options.warningsAsErrors || options.requireCert;
250
+ if (unifiedModes) {
251
+ const uni = unifiedCliResultFromIntegrationReturn(result);
252
+ const exitCode = finalizeUnifiedValidationResult(uni, unifiedDisplayOpts);
253
+ await afterUnifiedValidationCertSync(
254
+ exitCode,
255
+ datasourceKey,
256
+ options,
257
+ 'datasource test-integration',
258
+ uni.envelope
259
+ );
260
+ process.exit(exitCode);
261
+ return;
262
+ }
263
+ displayIntegrationTestResults(
264
+ {
265
+ systemKey: result.systemKey || 'unknown',
266
+ datasourceResults: [result],
267
+ success: result.success
268
+ },
269
+ options.verbose,
270
+ { debug: options.debug, runType: 'integration' }
271
+ );
272
+ const integExit = finalizeAfterIntegrationDisplay(result, {
273
+ warningsAsErrors: unifiedDisplayOpts.warningsAsErrors === true,
274
+ requireCert: unifiedDisplayOpts.requireCert === true
275
+ });
276
+ await afterUnifiedValidationCertSync(
277
+ integExit,
278
+ datasourceKey,
279
+ options,
280
+ 'datasource test-integration',
281
+ result.datasourceTestRun || null
282
+ );
283
+ process.exit(integExit);
284
+ }
285
+
222
286
  async function integrationTestCommandAction(datasourceKey, options) {
223
287
  const integOpts = buildIntegrationTestOpts(options);
224
288
  const unifiedDisplayOpts = buildIntegrationUnifiedDisplayOpts(options);
225
289
  try {
226
290
  if (options.watch) {
227
- const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
228
- await runDatasourceValidationWatchLoop({
229
- appKey,
230
- extraPaths: options.watchPath || [],
231
- includeApplicationYaml: options.watchApplicationYaml === true,
232
- watchCi: options.watchCi === true,
233
- watchFullDiff: options.watchFullDiff === true,
234
- runOnce: () => runIntegrationOnceForWatch(datasourceKey, integOpts, options, unifiedDisplayOpts)
235
- });
236
- return;
237
- }
238
- const result = await runDatasourceTestIntegration(datasourceKey, integOpts);
239
- const unifiedModes =
240
- options.json || options.summary || options.warningsAsErrors || options.requireCert;
241
- if (unifiedModes) {
242
- const exitCode = finalizeUnifiedValidationResult(
243
- unifiedCliResultFromIntegrationReturn(result),
244
- unifiedDisplayOpts
245
- );
246
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-integration');
247
- process.exit(exitCode);
291
+ await integrationTestCommandActionWatch(datasourceKey, options, integOpts, unifiedDisplayOpts);
248
292
  return;
249
293
  }
250
- displayIntegrationTestResults(
251
- {
252
- systemKey: result.systemKey || 'unknown',
253
- datasourceResults: [result],
254
- success: result.success
255
- },
256
- options.verbose,
257
- { debug: options.debug, runType: 'integration' }
258
- );
259
- const integExit = finalizeAfterIntegrationDisplay(result, {
260
- warningsAsErrors: unifiedDisplayOpts.warningsAsErrors === true,
261
- requireCert: unifiedDisplayOpts.requireCert === true
262
- });
263
- await afterUnifiedValidationCertSync(integExit, datasourceKey, options, 'datasource test-integration');
264
- process.exit(integExit);
294
+ await integrationTestCommandActionNonWatch(datasourceKey, integOpts, options, unifiedDisplayOpts);
265
295
  } catch (error) {
266
296
  logger.error(formatBlockingError('Integration test failed:'), error.message);
267
297
  process.exit(4);
@@ -296,9 +326,8 @@ async function runDatasourceTestE2ECliOnce(datasourceKey, options) {
296
326
  debug: options.debug,
297
327
  verbose: options.verbose,
298
328
  async: options.async !== false,
299
- testCrud: options.testCrud,
300
- recordId: options.recordId,
301
329
  cleanup: options.cleanup,
330
+ runScenarios: options.runScenarios,
302
331
  primaryKeyValue: options.primaryKeyValue,
303
332
  minVectorHits: options.minVectorHits,
304
333
  minProcessed: options.minProcessed,
@@ -340,8 +369,8 @@ async function runDatasourceTestE2ECliOnce(datasourceKey, options) {
340
369
  }
341
370
 
342
371
  async function runDatasourceTestE2ECliAction(datasourceKey, options) {
343
- const { exitCode } = await runDatasourceTestE2ECliOnce(datasourceKey, options);
344
- await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-e2e');
372
+ const { exitCode, envelope } = await runDatasourceTestE2ECliOnce(datasourceKey, options);
373
+ await afterUnifiedValidationCertSync(exitCode, datasourceKey, options, 'datasource test-e2e', envelope);
345
374
  process.exit(exitCode);
346
375
  }
347
376
 
@@ -371,7 +400,13 @@ async function e2eTestCommandAction(datasourceKey, capabilityKey, options) {
371
400
  runOnce: async() => {
372
401
  try {
373
402
  const r = await runDatasourceTestE2ECliOnce(datasourceKey, mergedOptions);
374
- await afterUnifiedValidationCertSync(r.exitCode, datasourceKey, mergedOptions, 'datasource test-e2e (watch)');
403
+ await afterUnifiedValidationCertSync(
404
+ r.exitCode,
405
+ datasourceKey,
406
+ mergedOptions,
407
+ 'datasource test-e2e (watch)',
408
+ r.envelope
409
+ );
375
410
  return r;
376
411
  } catch (err) {
377
412
  logger.error(formatBlockingError('E2E test failed:'), err.message);
@@ -166,8 +166,10 @@ function attachDatasourceTestE2eExclusiveOptions(cmd) {
166
166
  'Minimum record count assertion (e2eOptions.minRecordCount)',
167
167
  (v) => parseInt(String(v), 10)
168
168
  )
169
- .option('--test-crud', 'Enable CRUD lifecycle test (e2eOptions.testCrud)')
170
- .option('--record-id <id>', 'Record ID for test (e2eOptions.recordId)')
169
+ .option(
170
+ '--no-run-scenarios',
171
+ 'Do not expand testPayload.scenarios in capacity step (e2eOptions.runScenarios: false)'
172
+ )
171
173
  .option('--no-cleanup', 'Disable cleanup after test (e2eOptions.cleanup: false)')
172
174
  .option(
173
175
  '--primary-key-value <value|@path>',
@@ -10,10 +10,10 @@
10
10
  */
11
11
 
12
12
  const path = require('path');
13
- const chalk = require('chalk');
14
13
  const logger = require('../utils/logger');
15
- const { sectionTitle, headerKeyValue, metadata, formatSuccessLine, formatBlockingError } = require('../utils/cli-test-layout-chalk');
14
+ const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
16
15
  const { validateDatasourceFile } = require('../datasource/validate');
16
+ const { logDatasourceValidateOutcome } = require('../datasource/datasource-validate-display');
17
17
  const { listDatasources } = require('../datasource/list');
18
18
  const { compareDatasources } = require('../datasource/diff');
19
19
  const { deployDatasource } = require('../datasource/deploy');
@@ -53,34 +53,6 @@ Examples:
53
53
  $ af ds upload ../integration/hubspot/hubspot-datasource-deals.json
54
54
  `;
55
55
 
56
- /**
57
- * TTY layout for local datasource JSON validation (aligned with cli-test-layout-chalk).
58
- * @param {{ valid: boolean, errors: string[], resolvedPath: string }} result
59
- * @param {string} trimmed - original CLI argument
60
- * @param {boolean} showMapping - show Key + File when key resolved to a path
61
- */
62
- function logDatasourceValidateOutcome(result, trimmed, showMapping) {
63
- logger.log('');
64
- logger.log(sectionTitle('Datasource validation'));
65
- logger.log(metadata('Offline — JSON schema and integration wiring'));
66
- logger.log('');
67
- if (!result.valid) {
68
- logger.log(headerKeyValue('File:', result.resolvedPath));
69
- logger.log('');
70
- logger.log(formatBlockingError('Datasource file has errors:'));
71
- result.errors.forEach(error => logger.log(chalk.red(` • ${error}`)));
72
- return;
73
- }
74
- if (showMapping) {
75
- logger.log(headerKeyValue('Key:', trimmed));
76
- logger.log(headerKeyValue('File:', result.resolvedPath));
77
- } else {
78
- logger.log(headerKeyValue('File:', result.resolvedPath));
79
- }
80
- logger.log('');
81
- logger.log(formatSuccessLine('Datasource file is valid.'));
82
- }
83
-
84
56
  function setupDatasourceValidateCommand(datasource) {
85
57
  datasource.command('validate <file-or-key>')
86
58
  .description('Validate datasource JSON (file path or datasource key under integration/<app>/)')
@@ -97,7 +69,7 @@ function setupDatasourceValidateCommand(datasource) {
97
69
  process.exit(1);
98
70
  }
99
71
  } catch (error) {
100
- logger.error(formatBlockingError('Validation failed:'), error.message);
72
+ logger.error(formatBlockingError(`Validation failed: ${error.message}`));
101
73
  process.exit(1);
102
74
  }
103
75
  });
@@ -193,7 +193,7 @@ function normalizeDatasourceKeysAndFilenames(appPath, datasourceFiles, systemKey
193
193
  }
194
194
 
195
195
  if (updated && variables.externalIntegration && Array.isArray(variables.externalIntegration.dataSources)) {
196
- variables.externalIntegration.dataSources = [...newDatasourceFiles].sort();
196
+ variables.externalIntegration.dataSources = [...newDatasourceFiles];
197
197
  }
198
198
 
199
199
  return { updated, datasourceFiles: newDatasourceFiles };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @fileoverview Repair OpenAPI block on external datasource JSON.
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ function hasNonEmptyObject(v) {
10
+ return !!v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length > 0;
11
+ }
12
+
13
+ /**
14
+ * Repair `openapi` section:
15
+ * - If operations exist, ensure `openapi.enabled=true`
16
+ * - If enabled/operations and missing documentKey/fileId, default documentKey to datasource key (common convention)
17
+ * - If enabled+operations and autoRbac missing, default to true (wizard/fixtures expectation)
18
+ *
19
+ * @param {Object} parsed - Parsed datasource (mutated)
20
+ * @param {string[]} changes - Change log
21
+ * @returns {boolean} True if updated
22
+ */
23
+ function repairOpenapiSection(parsed, changes) {
24
+ const openapi = parsed?.openapi;
25
+ if (!openapi || typeof openapi !== 'object') {
26
+ return false;
27
+ }
28
+
29
+ const hasOps = hasNonEmptyObject(openapi.operations);
30
+ const enabled = openapi.enabled === true;
31
+ let updated = false;
32
+
33
+ if (hasOps && openapi.enabled !== true) {
34
+ openapi.enabled = true;
35
+ changes.push('Set openapi.enabled=true (operations present)');
36
+ updated = true;
37
+ }
38
+
39
+ if ((enabled || hasOps) && !openapi.documentKey && !openapi.fileId) {
40
+ if (typeof parsed.key === 'string' && parsed.key.trim()) {
41
+ openapi.documentKey = parsed.key.trim();
42
+ changes.push(`Set openapi.documentKey=${openapi.documentKey}`);
43
+ updated = true;
44
+ }
45
+ }
46
+
47
+ if ((enabled || hasOps) && hasOps && openapi.autoRbac === undefined) {
48
+ openapi.autoRbac = true;
49
+ changes.push('Set openapi.autoRbac=true (enabled operations, default)');
50
+ updated = true;
51
+ }
52
+
53
+ return updated;
54
+ }
55
+
56
+ module.exports = { repairOpenapiSection };
57
+
@@ -9,6 +9,8 @@
9
9
 
10
10
  'use strict';
11
11
 
12
+ const { repairOpenapiSection } = require('./repair-datasource-openapi');
13
+
12
14
  const DEFAULT_SYNC = {
13
15
  mode: 'pull',
14
16
  batchSize: 500
@@ -446,6 +448,8 @@ function repairDatasourceFile(parsed, options = {}, changes = []) {
446
448
  updated = repairMetadataSchemaFromAttributes(parsed, out) || updated;
447
449
  }
448
450
 
451
+ updated = repairOpenapiSection(parsed, out) || updated;
452
+
449
453
  if (options.expose) {
450
454
  updated = repairExposeFromAttributes(parsed, out) || updated;
451
455
  }
@@ -472,6 +476,7 @@ module.exports = {
472
476
  repairExposeFromAttributes,
473
477
  repairSyncSection,
474
478
  repairTestPayload,
479
+ repairOpenapiSection,
475
480
  sanitizeTestPayloadTopLevel,
476
481
  repairDatasourceFile,
477
482
  DEFAULT_SYNC,
@@ -40,7 +40,6 @@ function discoverIntegrationFiles(appPath) {
40
40
  }
41
41
  }
42
42
  systemFiles.sort();
43
- datasourceFiles.sort();
44
43
  return { systemFiles, datasourceFiles };
45
44
  }
46
45
 
@@ -48,8 +47,8 @@ function discoverIntegrationFiles(appPath) {
48
47
  * Builds the effective datasource file list from application.yaml and discovered files.
49
48
  * @param {string} appPath - Application directory path
50
49
  * @param {string[]} discoveredDatasourceFiles - Filenames from discoverIntegrationFiles
51
- * @param {string[]} [existingDataSources] - externalIntegration.dataSources from application.yaml
52
- * @returns {string[]} Sorted, deduplicated list of datasource filenames
50
+ * @param {string[]} [existingDataSources] - externalIntegration.dataSources from application config
51
+ * @returns {string[]} Deduplicated list of datasource filenames (application order first, then remaining discovered files in directory order)
53
52
  */
54
53
  function buildEffectiveDatasourceFiles(appPath, discoveredDatasourceFiles, existingDataSources) {
55
54
  const existing = Array.isArray(existingDataSources) ? existingDataSources : [];
@@ -75,7 +74,6 @@ function buildEffectiveDatasourceFiles(appPath, discoveredDatasourceFiles, exist
75
74
  seen.add(name);
76
75
  }
77
76
  }
78
- result.sort();
79
77
  return result;
80
78
  }
81
79
 
@@ -204,8 +204,7 @@ function alignSystemFileDataSources(appPath, systemParsed, datasourceFiles, syst
204
204
  keys.push(deriveDatasourceKeyFromFileName(fileName, systemKey));
205
205
  }
206
206
  }
207
- keys.sort();
208
- const prev = Array.isArray(systemParsed.dataSources) ? [...systemParsed.dataSources].sort() : [];
207
+ const prev = Array.isArray(systemParsed.dataSources) ? [...systemParsed.dataSources] : [];
209
208
  if (JSON.stringify(prev) === JSON.stringify(keys)) return false;
210
209
  systemParsed.dataSources = keys;
211
210
  changes.push(`dataSources: [${prev.join(', ') || '(none)'}] → [${keys.join(', ')}] (keys from each datasource file's "key" or filename)`);
@@ -42,8 +42,8 @@ function deriveDatasourceKeyFromFileName(fileName, systemKey) {
42
42
  * @param {Object} variables - Loaded application variables (externalIntegration.dataSources = filenames)
43
43
  * @param {string} systemKey - System key from system file
44
44
  * @param {Object} systemParsed - Parsed system config (may have dataSources array of keys)
45
- * @param {string[]} datasourceFiles - Discovered datasource filenames
46
- * @returns {string[]} Sorted list of datasource keys
45
+ * @param {string[]} datasourceFiles - Discovered datasource filenames (application order when built via repair)
46
+ * @returns {string[]} Datasource keys in declaration order (system JSON or file list never sort; FK targets must run first)
47
47
  */
48
48
  function getDatasourceKeys(appPath, configPath, variables, systemKey, systemParsed, datasourceFiles) {
49
49
  const fromSystem = Array.isArray(systemParsed.dataSources) && systemParsed.dataSources.length > 0
@@ -54,11 +54,11 @@ function getDatasourceKeys(appPath, configPath, variables, systemKey, systemPars
54
54
  if (fromSystem) {
55
55
  fromSystem.forEach(k => {
56
56
  if (k && typeof k === 'string' && !seen.has(k)) {
57
- keys.push(k.trim());
58
- seen.add(k.trim());
57
+ const trimmed = k.trim();
58
+ keys.push(trimmed);
59
+ seen.add(trimmed);
59
60
  }
60
61
  });
61
- keys.sort();
62
62
  return keys;
63
63
  }
64
64
  for (const fileName of datasourceFiles) {
@@ -81,7 +81,6 @@ function getDatasourceKeys(appPath, configPath, variables, systemKey, systemPars
81
81
  }
82
82
  }
83
83
  }
84
- keys.sort();
85
84
  return keys;
86
85
  }
87
86
 
@@ -17,7 +17,8 @@ const { getIntegrationPath } = require('../utils/paths');
17
17
  const { pushCredentialSecrets } = require('../utils/credential-secrets-env');
18
18
  const {
19
19
  buildResolvedEnvMapForIntegration,
20
- resolveConfigurationValues
20
+ resolveConfigurationValues,
21
+ collectResolvedVariableConfigurationNames
21
22
  } = require('../utils/configuration-env-resolver');
22
23
  const { validateExternalSystemComplete } = require('../validation/validate');
23
24
  const { displayValidationResults } = require('../validation/validate-display');
@@ -70,9 +71,11 @@ function buildUploadPayload(manifest) {
70
71
  /**
71
72
  * Resolves dataplane URL and auth (same pattern as download).
72
73
  * @param {string} systemKey - System key
74
+ * @param {{ silent?: boolean }} [opts] - silent: omit “Resolving dataplane URL…” and discovery success lines
73
75
  * @returns {Promise<{ dataplaneUrl: string, authConfig: Object, environment: string }>}
74
76
  */
75
- async function resolveDataplaneAndAuth(systemKey) {
77
+ async function resolveDataplaneAndAuth(systemKey, opts = {}) {
78
+ const silent = opts.silent === true;
76
79
  const { resolveEnvironment } = require('../core/config');
77
80
  const environment = await resolveEnvironment();
78
81
  const controllerUrl = await resolveControllerUrl();
@@ -82,8 +85,10 @@ async function resolveDataplaneAndAuth(systemKey) {
82
85
  throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register <systemKey>" first.');
83
86
  }
84
87
 
85
- logger.log(chalk.gray('Resolving dataplane URL...'));
86
- const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
88
+ if (!silent) {
89
+ logger.log(chalk.gray('Resolving dataplane URL...'));
90
+ }
91
+ const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig, { silent });
87
92
  return { dataplaneUrl, authConfig, environment };
88
93
  }
89
94
 
@@ -167,6 +172,15 @@ async function pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey,
167
172
  } else {
168
173
  logger.log(chalk.yellow('Secret push skipped'));
169
174
  }
175
+ const resolvedParamNames = collectResolvedVariableConfigurationNames(payload);
176
+ if (resolvedParamNames.length > 0) {
177
+ const nameList = ` (${resolvedParamNames.join(', ')})`;
178
+ logger.log(
179
+ formatSuccessLine(
180
+ `Resolved ${resolvedParamNames.length} integration parameter(s) from .env for publish${nameList}.`
181
+ )
182
+ );
183
+ }
170
184
  if (pushResult.warning) {
171
185
  logger.log(chalk.yellow(`Warning: ${pushResult.warning}`));
172
186
  }
@@ -73,10 +73,12 @@ function createDataplaneNotFoundError() {
73
73
  * @returns {Promise<string>} Dataplane URL
74
74
  * @throws {Error} If dataplane URL cannot be retrieved
75
75
  */
76
- async function tryFallbackDataplaneUrl(controllerUrl, environment, authConfig) {
76
+ async function tryFallbackDataplaneUrl(controllerUrl, environment, authConfig, silent) {
77
77
  try {
78
78
  const fallbackUrl = await getDataplaneUrl(controllerUrl, 'dataplane', environment, authConfig);
79
- logger.log(formatSuccessLine(`Dataplane URL: ${fallbackUrl}`));
79
+ if (!silent) {
80
+ logger.log(formatSuccessLine(`Dataplane URL: ${fallbackUrl}`));
81
+ }
80
82
  return fallbackUrl;
81
83
  } catch (fallbackError) {
82
84
  if (isNotFoundError(fallbackError)) {
@@ -93,19 +95,25 @@ async function tryFallbackDataplaneUrl(controllerUrl, environment, authConfig) {
93
95
  * @param {string} controllerUrl - Controller URL
94
96
  * @param {string} environment - Environment key
95
97
  * @param {Object} authConfig - Authentication configuration
98
+ * @param {{ silent?: boolean }} [opts] - When silent, skip progress/success lines (e.g. cert sync right after a run).
96
99
  * @returns {Promise<string>} Dataplane URL
97
100
  * @throws {Error} If dataplane URL cannot be discovered
98
101
  */
99
- async function discoverDataplaneUrl(controllerUrl, environment, authConfig) {
100
- logger.log(infoLine('🌐 Getting dataplane URL from controller...'));
102
+ async function discoverDataplaneUrl(controllerUrl, environment, authConfig, opts = {}) {
103
+ const silent = opts.silent === true;
104
+ if (!silent) {
105
+ logger.log(infoLine('🌐 Getting dataplane URL from controller...'));
106
+ }
101
107
  try {
102
108
  const dataplaneAppKey = await findDataplaneServiceAppKey(controllerUrl, environment, authConfig);
103
109
  if (dataplaneAppKey) {
104
110
  const dataplaneUrl = await getDataplaneUrl(controllerUrl, dataplaneAppKey, environment, authConfig);
105
- logger.log(formatSuccessLine(`Dataplane URL: ${dataplaneUrl}`));
111
+ if (!silent) {
112
+ logger.log(formatSuccessLine(`Dataplane URL: ${dataplaneUrl}`));
113
+ }
106
114
  return dataplaneUrl;
107
115
  }
108
- return await tryFallbackDataplaneUrl(controllerUrl, environment, authConfig);
116
+ return await tryFallbackDataplaneUrl(controllerUrl, environment, authConfig, silent);
109
117
  } catch (error) {
110
118
  if (error.message.includes('Could not discover dataplane URL')) {
111
119
  throw error;