@aifabrix/builder 2.44.3 → 2.44.5

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 (72) 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 +31 -15
  5. package/lib/api/certificates.api.js +21 -3
  6. package/lib/api/types/wizard.types.js +2 -1
  7. package/lib/certification/post-unified-cert-sync.js +13 -2
  8. package/lib/certification/sync-after-external-command.js +6 -3
  9. package/lib/certification/sync-system-certification.js +60 -14
  10. package/lib/cli/setup-app.help.js +1 -1
  11. package/lib/cli/setup-app.test-commands.js +75 -39
  12. package/lib/cli/setup-infra.js +6 -2
  13. package/lib/cli/setup-utility.js +20 -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-rbac.js +25 -2
  22. package/lib/commands/repair.js +2 -19
  23. package/lib/commands/test-e2e-external.js +9 -9
  24. package/lib/commands/up-common.js +25 -0
  25. package/lib/commands/upload.js +18 -4
  26. package/lib/commands/wizard-core.js +53 -11
  27. package/lib/commands/wizard-dataplane.js +14 -6
  28. package/lib/commands/wizard-entity-selection.js +71 -14
  29. package/lib/commands/wizard-headless.js +5 -2
  30. package/lib/commands/wizard-helpers.js +13 -1
  31. package/lib/commands/wizard.js +208 -60
  32. package/lib/datasource/datasource-validate-display.js +162 -0
  33. package/lib/datasource/datasource-validate-summary.js +194 -0
  34. package/lib/datasource/test-e2e.js +65 -37
  35. package/lib/datasource/unified-validation-run-body.js +1 -2
  36. package/lib/datasource/validate.js +14 -6
  37. package/lib/external-system/test.js +12 -8
  38. package/lib/generator/external-controller-manifest.js +12 -2
  39. package/lib/generator/wizard-prompts.js +7 -1
  40. package/lib/generator/wizard.js +34 -0
  41. package/lib/schema/cip-capacity-display.fallback.json +7 -0
  42. package/lib/schema/datasource-test-run.schema.json +79 -1
  43. package/lib/schema/external-datasource.schema.json +94 -2
  44. package/lib/schema/flag-map-validation-run.json +1 -2
  45. package/lib/schema/type/document-storage.json +83 -3
  46. package/lib/schema/wizard-config.schema.json +1 -1
  47. package/lib/utils/configuration-env-resolver.js +38 -0
  48. package/lib/utils/dataplane-resolver.js +3 -2
  49. package/lib/utils/datasource-test-run-capacity-operations.js +149 -0
  50. package/lib/utils/datasource-test-run-debug-display.js +143 -1
  51. package/lib/utils/datasource-test-run-display.js +46 -33
  52. package/lib/utils/datasource-test-run-tty-log.js +6 -2
  53. package/lib/utils/datasource-test-run-tty-meta-lines.js +123 -0
  54. package/lib/utils/error-formatter.js +32 -2
  55. package/lib/utils/external-readme.js +47 -3
  56. package/lib/utils/external-system-readiness-core.js +39 -0
  57. package/lib/utils/external-system-readiness-deploy-display.js +2 -3
  58. package/lib/utils/external-system-readiness-display-internals.js +3 -2
  59. package/lib/utils/external-system-system-test-tty.js +33 -9
  60. package/lib/utils/external-system-validators.js +62 -5
  61. package/lib/utils/load-cip-capacity-display-config.js +130 -0
  62. package/lib/utils/paths.js +10 -3
  63. package/lib/utils/schema-resolver.js +98 -2
  64. package/lib/utils/urls-local-registry.js +52 -10
  65. package/lib/utils/validation-run-poll.js +15 -4
  66. package/lib/utils/validation-run-request.js +4 -6
  67. package/lib/validation/dimension-display-helpers.js +60 -0
  68. package/lib/validation/validate-display-log-helpers.js +39 -0
  69. package/lib/validation/validate-display.js +89 -83
  70. package/package.json +1 -1
  71. package/templates/applications/miso-controller/env.template +6 -6
  72. package/templates/external-system/README.md.hbs +58 -32
package/.npmrc.token CHANGED
@@ -1 +1 @@
1
- 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
1
+ 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
@@ -123,8 +123,7 @@ Options:
123
123
  -e, --env <env> Environment: dev, tst, or pro
124
124
  -v, --verbose Show detailed step output and poll progress
125
125
  --debug Include debug output and write log to integration/roundtrip-test-local/logs/
126
- --test-crud Enable CRUD lifecycle test (body testCrud: true)
127
- --record-id <id> Record ID for test (body recordId)
126
+ --no-run-scenarios Skip expanding testPayload.scenarios in capacity step
128
127
  --no-cleanup Disable cleanup after test (body cleanup: false)
129
128
  --primary-key-value <value|@path> Primary key value or path to JSON file (e.g. @pk.json) for body primaryKeyValue
130
129
  --no-async Use sync mode (no polling); single POST, no asyncRun
@@ -123,8 +123,7 @@ Options:
123
123
  -e, --env <env> Environment: dev, tst, or pro
124
124
  -v, --verbose Show detailed step output and poll progress
125
125
  --debug Include debug output and write log to integration/roundtrip-test-local2/logs/
126
- --test-crud Enable CRUD lifecycle test (body testCrud: true)
127
- --record-id <id> Record ID for test (body recordId)
126
+ --no-run-scenarios Skip expanding testPayload.scenarios in capacity step
128
127
  --no-cleanup Disable cleanup after test (body cleanup: false)
129
128
  --primary-key-value <value|@path> Primary key value or path to JSON file (e.g. @pk.json) for body primaryKeyValue
130
129
  --no-async Use sync mode (no polling); single POST, no asyncRun
package/jest.projects.js CHANGED
@@ -57,21 +57,28 @@ const defaultProject = {
57
57
  '/tests/lib/utils/cli-utils.test.js',
58
58
  '/tests/lib/utils/external-system-display.test.js',
59
59
  '/tests/lib/utils/dev-hosts-helper.test.js',
60
- '/tests/lib/utils/declarative-url-matrix-d-reload.test.js',
60
+ '/tests/lib/utils/register-aifabrix-shell-env.test.js',
61
61
  '/tests/lib/utils/datasource-validation-watch.test.js',
62
62
  '\\\\tests\\\\lib\\\\utils\\\\cli-utils.test.js',
63
63
  '\\\\tests\\\\lib\\\\utils\\\\external-system-display.test.js',
64
64
  '\\\\tests\\\\lib\\\\utils\\\\dev-hosts-helper.test.js',
65
- '\\\\tests\\\\lib\\\\utils\\\\declarative-url-matrix-d-reload.test.js',
65
+ '\\\\tests\\\\lib\\\\utils\\\\register-aifabrix-shell-env.test.js',
66
66
  '\\\\tests\\\\lib\\\\utils\\\\datasource-validation-watch.test.js',
67
67
  'lib/utils/dev-hosts-helper.test.js',
68
- 'lib/utils/declarative-url-matrix-d-reload.test.js',
68
+ 'lib/utils/register-aifabrix-shell-env.test.js',
69
69
  'lib/utils/datasource-validation-watch.test.js',
70
70
  'dev-hosts-helper\\.test\\.js',
71
- 'declarative-url-matrix-d-reload\\.test\\.js',
71
+ 'register-aifabrix-shell-env\\.test\\.js',
72
72
  '/tests/lib/datasource/log-viewer.test.js',
73
73
  '\\\\tests\\\\lib\\\\datasource\\\\log-viewer.test.js',
74
74
  'lib/datasource/log-viewer.test.js',
75
+ '/tests/lib/datasource/log-viewer-structural.test.js',
76
+ '/tests/lib/datasource/log-viewer-run.test.js',
77
+ '\\\\tests\\\\lib\\\\datasource\\\\log-viewer-structural.test.js',
78
+ '\\\\tests\\\\lib\\\\datasource\\\\log-viewer-run.test.js',
79
+ 'lib/datasource/log-viewer-structural.test.js',
80
+ 'lib/datasource/log-viewer-run.test.js',
81
+ 'log-viewer-run\\.test\\.js',
75
82
  '/tests/lib/commands/parameters-validate.test.js',
76
83
  '\\\\tests\\\\lib\\\\commands\\\\parameters-validate.test.js',
77
84
  'lib/commands/parameters-validate.test.js',
@@ -150,10 +157,6 @@ const defaultProject = {
150
157
  '\\\\tests\\\\lib\\\\utils\\\\paths-app-listing.test.js',
151
158
  'lib/utils/paths-app-listing.test.js',
152
159
  'paths-app-listing\\.test\\.js',
153
- '/tests/lib/utils/url-declarative-truth-table-124.test.js',
154
- '\\\\tests\\\\lib\\\\utils\\\\url-declarative-truth-table-124.test.js',
155
- 'lib/utils/url-declarative-truth-table-124.test.js',
156
- 'url-declarative-truth-table-124\\.test\\.js',
157
160
  '/tests/lib/generator/generator-external-rbac.test.js',
158
161
  '\\\\tests\\\\lib\\\\generator\\\\generator-external-rbac.test.js',
159
162
  'lib/generator/generator-external-rbac.test.js',
@@ -190,9 +193,17 @@ const defaultProject = {
190
193
  '\\\\tests\\\\lib\\\\validation\\\\schema-241-alignment.test.js',
191
194
  'lib/validation/schema-241-alignment.test.js',
192
195
  'schema-241-alignment\\.test\\.js',
196
+ '/tests/lib/utils/schema-resolver-order.test.js',
197
+ '\\\\tests\\\\lib\\\\utils\\\\schema-resolver-order.test.js',
198
+ 'lib/utils/schema-resolver-order.test.js',
199
+ 'schema-resolver-order\\.test\\.js',
193
200
  '/tests/lib/app/app.test.js',
194
201
  '\\\\tests\\\\lib\\\\app\\\\app.test.js',
195
202
  'lib/app/app.test.js',
203
+ '/tests/lib/templates/application-frontdoor-paths.contract.test.js',
204
+ '\\\\tests\\\\lib\\\\templates\\\\application-frontdoor-paths.contract.test.js',
205
+ 'lib/templates/application-frontdoor-paths.contract.test.js',
206
+ 'application-frontdoor-paths\\.contract\\.test\\.js',
196
207
  '/tests/lib/core/admin-secrets.test.js',
197
208
  '\\\\tests\\\\lib\\\\core\\\\admin-secrets.test.js',
198
209
  'lib/core/admin-secrets.test.js'
@@ -214,21 +225,28 @@ const isolatedProjects = [
214
225
  makeIsolatedProject('cli-utils', ['**/tests/lib/utils/cli-utils.test.js']),
215
226
  makeIsolatedProject('external-system-display', ['**/tests/lib/utils/external-system-display.test.js']),
216
227
  makeIsolatedProject('dev-hosts-helper', ['**/tests/lib/utils/dev-hosts-helper.test.js']),
217
- makeIsolatedProject('declarative-url-matrix-d-reload', [
218
- '**/tests/lib/utils/declarative-url-matrix-d-reload.test.js'
219
- ]),
220
228
  makeIsolatedProject('parameters-validate', ['**/tests/lib/commands/parameters-validate.test.js']),
221
229
  makeIsolatedProject('paths-app-listing', ['**/tests/lib/utils/paths-app-listing.test.js']),
222
230
  makeIsolatedProject('datasource-validation-watch', [
223
231
  '**/tests/lib/utils/datasource-validation-watch.test.js'
224
232
  ]),
225
- makeIsolatedProject('log-viewer', ['**/tests/lib/datasource/log-viewer.test.js']),
233
+ makeIsolatedProject('log-viewer', [
234
+ '**/tests/lib/datasource/log-viewer.test.js',
235
+ '**/tests/lib/datasource/log-viewer-structural.test.js',
236
+ '**/tests/lib/datasource/log-viewer-run.test.js'
237
+ ]),
238
+ makeIsolatedProject('register-aifabrix-shell-env', [
239
+ '**/tests/lib/utils/register-aifabrix-shell-env.test.js'
240
+ ]),
226
241
  makeIsolatedProject('datasource-test-run-schema-sync', [
227
242
  '**/tests/lib/utils/datasource-test-run-schema-sync.test.js'
228
243
  ]),
229
244
  makeIsolatedProject('infra-platform-contract', [
230
245
  '**/tests/lib/parameters/infra-platform-contract.test.js'
231
246
  ]),
247
+ makeIsolatedProject('application-frontdoor-paths-contract', [
248
+ '**/tests/lib/templates/application-frontdoor-paths.contract.test.js'
249
+ ]),
232
250
  makeIsolatedProject('database-secret-values', [
233
251
  '**/tests/lib/parameters/database-secret-values.test.js'
234
252
  ]),
@@ -273,9 +291,6 @@ const isolatedProjects = [
273
291
  makeIsolatedProject('helpers-ensure-admin-secrets', [
274
292
  '**/tests/lib/infrastructure/helpers-ensure-admin-secrets.test.js'
275
293
  ]),
276
- makeIsolatedProject('url-declarative-truth-table-124', [
277
- '**/tests/lib/utils/url-declarative-truth-table-124.test.js'
278
- ]),
279
294
  makeIsolatedProject('secrets-generator', ['**/tests/lib/utils/secrets-generator.test.js']),
280
295
  makeIsolatedProject('app-uncovered-lines', ['**/tests/lib/app/app-uncovered-lines.test.js']),
281
296
  makeIsolatedProject('ensure-dev-certs-for-remote-docker', [
@@ -285,6 +300,7 @@ const isolatedProjects = [
285
300
  makeIsolatedProject('generator-validation', ['**/tests/lib/generator/generator-validation.test.js']),
286
301
  makeIsolatedProject('secrets-databaselog', ['**/tests/lib/core/secrets-databaselog.test.js']),
287
302
  makeIsolatedProject('schema-241-alignment', ['**/tests/lib/validation/schema-241-alignment.test.js']),
303
+ makeIsolatedProject('schema-resolver-order', ['**/tests/lib/utils/schema-resolver-order.test.js']),
288
304
  makeIsolatedProject('app-module', ['**/tests/lib/app/app.test.js']),
289
305
  makeIsolatedProject('admin-secrets', ['**/tests/lib/core/admin-secrets.test.js'])
290
306
  ];
@@ -5,6 +5,24 @@
5
5
  */
6
6
 
7
7
  const { ApiClient } = require('./index');
8
+ const { normalizeDataplaneAuth } = require('./validation-run.api');
9
+
10
+ /**
11
+ * @param {Object} authConfig
12
+ * @returns {Object}
13
+ */
14
+ function dataplaneAuthForBearerGet(authConfig) {
15
+ if (!authConfig || typeof authConfig !== 'object') {
16
+ return authConfig;
17
+ }
18
+ if (authConfig.token || authConfig.clientId) {
19
+ return authConfig;
20
+ }
21
+ if (authConfig.apiKey) {
22
+ return { ...authConfig, token: authConfig.apiKey, type: authConfig.type || 'bearer' };
23
+ }
24
+ return normalizeDataplaneAuth(authConfig);
25
+ }
8
26
 
9
27
  /**
10
28
  * Get active trusted integration certificate for a datasource.
@@ -18,7 +36,7 @@ const { ApiClient } = require('./index');
18
36
  * @returns {Promise<Object>} API envelope `{ success, data?, status, ... }`
19
37
  */
20
38
  async function getActiveIntegrationCertificate(dataplaneUrl, authConfig, systemKey, datasourceKey) {
21
- const client = new ApiClient(dataplaneUrl, authConfig);
39
+ const client = new ApiClient(dataplaneUrl, dataplaneAuthForBearerGet(authConfig));
22
40
  const path = `/api/v1/systems/${encodeURIComponent(systemKey)}/datasources/${encodeURIComponent(
23
41
  datasourceKey
24
42
  )}/certificates/active`;
@@ -36,7 +54,7 @@ async function getActiveIntegrationCertificate(dataplaneUrl, authConfig, systemK
36
54
  * @returns {Promise<Object>}
37
55
  */
38
56
  async function listIntegrationCertificates(dataplaneUrl, authConfig, params = {}) {
39
- const client = new ApiClient(dataplaneUrl, authConfig);
57
+ const client = new ApiClient(dataplaneUrl, dataplaneAuthForBearerGet(authConfig));
40
58
  return await client.get('/api/v1/certificates', { params });
41
59
  }
42
60
 
@@ -51,7 +69,7 @@ async function listIntegrationCertificates(dataplaneUrl, authConfig, params = {}
51
69
  * @returns {Promise<Object>}
52
70
  */
53
71
  async function verifyIntegrationCertificate(dataplaneUrl, authConfig, body) {
54
- const client = new ApiClient(dataplaneUrl, authConfig);
72
+ const client = new ApiClient(dataplaneUrl, dataplaneAuthForBearerGet(authConfig));
55
73
  return await client.post('/api/v1/certificates/verify', { body: body || {} });
56
74
  }
57
75
 
@@ -260,12 +260,13 @@
260
260
  * @property {string} [source.serverUrl] - MCP server URL (for mcp-server)
261
261
  * @property {string} [source.token] - MCP token (for mcp-server, supports ${ENV_VAR})
262
262
  * @property {string} [source.platform] - Known platform (for known-platform)
263
+ * @property {string} [source.entityName] - Entity from discover-entities (openapi-file / openapi-url); skips interactive Step 4.5 when valid
263
264
  * @property {Object} [credential] - Credential configuration
264
265
  * @property {string} credential.action - Action ('create' | 'select' | 'skip')
265
266
  * @property {string} [credential.credentialIdOrKey] - Credential ID/key (for select)
266
267
  * @property {Object} [credential.config] - Credential config (for create)
267
268
  * @property {Object} [preferences] - Generation preferences
268
- * @property {string} [preferences.intent] - User intent (any descriptive text)
269
+ * @property {string} [preferences.intent] - User intent (any descriptive text, max 1000 chars)
269
270
  * @property {string} [preferences.fieldOnboardingLevel] - Field level ('full' | 'standard' | 'minimal')
270
271
  * @property {boolean} [preferences.enableOpenAPIGeneration] - Enable OpenAPI generation
271
272
  * @property {boolean} [preferences.enableMCP] - Enable MCP
@@ -10,6 +10,7 @@ const chalk = require('chalk');
10
10
  const logger = require('../utils/logger');
11
11
  const { trySyncCertificationFromDataplaneForExternalApp } = require('./sync-after-external-command');
12
12
  const { cliOptsSkipCertSync } = require('./cli-cert-sync-skip');
13
+ const { resolveDebugDisplayMode } = require('../utils/datasource-test-run-debug-display');
13
14
 
14
15
  /**
15
16
  * @async
@@ -17,14 +18,24 @@ const { cliOptsSkipCertSync } = require('./cli-cert-sync-skip');
17
18
  * @param {string} datasourceKey
18
19
  * @param {Object} options - CLI flags (app, noCertSync)
19
20
  * @param {string} label - Log label
21
+ * @param {Object|null} [envelope] - Last DatasourceTestRun (optional); used for certificateIssuance failed hint
20
22
  * @returns {Promise<void>}
21
23
  */
22
- async function afterUnifiedValidationCertSync(exitCode, datasourceKey, options, label) {
24
+ async function afterUnifiedValidationCertSync(exitCode, datasourceKey, options, label, envelope = null) {
23
25
  if (exitCode !== 0 || cliOptsSkipCertSync(options)) return;
24
26
  try {
25
27
  const { resolveAppKeyForDatasource } = require('../datasource/resolve-app');
26
28
  const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
27
- await trySyncCertificationFromDataplaneForExternalApp(appKey, label);
29
+ let issuanceFailureHint = null;
30
+ if (envelope && envelope.certificateIssuance && envelope.certificateIssuance.status === 'failed') {
31
+ const ci = envelope.certificateIssuance;
32
+ issuanceFailureHint = [ci.reasonCode, ci.message].filter(Boolean).join(': ');
33
+ }
34
+ const verboseCertHints = resolveDebugDisplayMode(options.debug) !== null;
35
+ await trySyncCertificationFromDataplaneForExternalApp(appKey, label, {
36
+ issuanceFailureHint,
37
+ verboseCertHints
38
+ });
28
39
  } catch (e) {
29
40
  logger.log(chalk.yellow(`⚠ Certification sync (${label}) skipped: ${e.message}`));
30
41
  }
@@ -16,9 +16,10 @@ const logger = require('../utils/logger');
16
16
  * @async
17
17
  * @param {string} appKey - Integration / system key
18
18
  * @param {string} label - Short label for logs (e.g. "validate", "datasource test")
19
+ * @param {Object} [extra] - Optional `{ issuanceFailureHint }` from last DatasourceTestRun.certificateIssuance (failed)
19
20
  * @returns {Promise<void>}
20
21
  */
21
- async function trySyncCertificationFromDataplaneForExternalApp(appKey, label) {
22
+ async function trySyncCertificationFromDataplaneForExternalApp(appKey, label, extra = {}) {
22
23
  try {
23
24
  const { detectAppType } = require('../utils/paths');
24
25
  const t = await detectAppType(appKey).catch(() => null);
@@ -29,7 +30,7 @@ async function trySyncCertificationFromDataplaneForExternalApp(appKey, label) {
29
30
  const { maybeSyncSystemCertificationFromDataplane } = require('./sync-system-certification');
30
31
 
31
32
  validateSystemKeyFormat(appKey);
32
- const { dataplaneUrl, authConfig } = await resolveDataplaneAndAuth(appKey);
33
+ const { dataplaneUrl, authConfig } = await resolveDataplaneAndAuth(appKey, { silent: true });
33
34
  if (!authConfig.token) {
34
35
  logger.log(chalk.gray(`Certification sync (${label}) skipped: no Bearer token (run aifabrix login).`));
35
36
  return;
@@ -42,7 +43,9 @@ async function trySyncCertificationFromDataplaneForExternalApp(appKey, label) {
42
43
  systemKey: manifest.key,
43
44
  dataplaneUrl,
44
45
  authConfig,
45
- datasourceKeys: dsKeys
46
+ datasourceKeys: dsKeys,
47
+ issuanceFailureHint: extra && extra.issuanceFailureHint ? String(extra.issuanceFailureHint).trim() : null,
48
+ verboseCertHints: extra && extra.verboseCertHints === true
46
49
  });
47
50
  } catch (e) {
48
51
  logger.log(chalk.yellow(`⚠ Certification sync (${label}) skipped: ${e.message}`));
@@ -84,15 +84,43 @@ function tryWriteSystemCertification(systemFilePath, systemObj, nextCert) {
84
84
  /**
85
85
  * @param {Object|null} chosen
86
86
  * @param {'no_active'|'no_public_key'} detail
87
+ * @param {string|null} [issuanceFailureHint] - From last DatasourceTestRun.certificateIssuance when status failed
88
+ * @param {boolean} [verboseCertHints] - Extra gray lines and auto-issue detail (CLI `--debug`)
87
89
  */
88
- function logSkippedCertification(chosen, detail) {
90
+ function logSkippedCertification(chosen, detail, issuanceFailureHint, verboseCertHints = false) {
89
91
  if (detail === 'no_active' || !chosen) {
90
- logger.log(
91
- chalk.yellow(
92
- '⚠ Certification not written: dataplane has no active trusted certificate for this system/datasource scope yet. ' +
93
- 'Run a successful validation or E2E that issues a certificate, then upload or run cert sync again.'
94
- )
95
- );
92
+ const hint = issuanceFailureHint && String(issuanceFailureHint).trim();
93
+ const certNotPassed =
94
+ hint &&
95
+ (hint.includes('CERTIFICATION_NOT_PASSED') ||
96
+ hint.toLowerCase().includes('certification did not pass'));
97
+ if (certNotPassed) {
98
+ logger.log(
99
+ chalk.yellow(
100
+ '⚠ Certification not written: no active integration certificate because certification did not pass on the dataplane issuance validation pass.'
101
+ )
102
+ );
103
+ if (verboseCertHints) {
104
+ logger.log(
105
+ chalk.gray(
106
+ ' E2E can be green for every datasource while auto-issue still fails: issuance re-validates the whole system before signing.'
107
+ )
108
+ );
109
+ if (hint) {
110
+ logger.log(chalk.gray(` Auto-issue detail: ${hint}`));
111
+ }
112
+ }
113
+ } else {
114
+ logger.log(
115
+ chalk.yellow(
116
+ '⚠ Certification not written: dataplane has no active trusted certificate for this system/datasource scope yet. ' +
117
+ 'Fix auto-issue or signing (see hint), or run unified validation then upload or run cert sync again.'
118
+ )
119
+ );
120
+ if (hint && verboseCertHints) {
121
+ logger.log(chalk.gray(` Auto-issue detail: ${hint}`));
122
+ }
123
+ }
96
124
  return;
97
125
  }
98
126
  logger.log(
@@ -111,13 +139,19 @@ function logSkippedCertification(chosen, detail) {
111
139
  * @param {string} params.dataplaneUrl
112
140
  * @param {Object} params.authConfig
113
141
  * @param {string[]} params.datasourceKeys
142
+ * @param {boolean} [params.verboseCertHints]
114
143
  * @returns {Promise<{ written: boolean, reason?: string }>}
115
144
  */
116
145
  async function syncSystemCertificationFromDataplane(params) {
117
- const { systemKey, dataplaneUrl, authConfig, datasourceKeys } = params;
146
+ const { systemKey, dataplaneUrl, authConfig, datasourceKeys, issuanceFailureHint, verboseCertHints } = params;
118
147
  const resolved = resolvePrimarySystemFilePath(systemKey);
119
148
  if (!resolved) return { written: false, reason: 'no_system_file' };
120
- if (!dataplaneUrl || !authConfig || !authConfig.token) {
149
+ const hasBearerLike =
150
+ authConfig &&
151
+ typeof authConfig === 'object' &&
152
+ ((typeof authConfig.token === 'string' && authConfig.token.trim()) ||
153
+ (typeof authConfig.apiKey === 'string' && authConfig.apiKey.trim()));
154
+ if (!dataplaneUrl || !authConfig || !hasBearerLike) {
121
155
  return { written: false, reason: 'no_auth' };
122
156
  }
123
157
 
@@ -137,7 +171,7 @@ async function syncSystemCertificationFromDataplane(params) {
137
171
  const nextCert = buildCertificationFromArtifact(chosen, existing);
138
172
  if (!nextCert) {
139
173
  const detail = chosen ? 'no_public_key' : 'no_active';
140
- logSkippedCertification(chosen, detail);
174
+ logSkippedCertification(chosen, detail, issuanceFailureHint, verboseCertHints === true);
141
175
  return { written: false, reason: 'incomplete_certification', detail };
142
176
  }
143
177
 
@@ -154,7 +188,8 @@ async function syncSystemCertificationFromDataplane(params) {
154
188
  * @param {boolean} [params.noCertSync]
155
189
  * @returns {Promise<void>}
156
190
  */
157
- function logCertificationSyncNotWritten(r, label) {
191
+ function logCertificationSyncNotWritten(r, label, verboseCertHints = false) {
192
+ if (r && r.reason === 'incomplete_certification' && !verboseCertHints) return;
158
193
  const prefix = label ? ` (${label})` : '';
159
194
  const map = {
160
195
  no_system_file: `No *-system* file found under integration folder for this key${prefix}.`,
@@ -168,21 +203,32 @@ function logCertificationSyncNotWritten(r, label) {
168
203
  }
169
204
 
170
205
  async function maybeSyncSystemCertificationFromDataplane(params) {
171
- const { label, noCertSync, systemKey, dataplaneUrl, authConfig, datasourceKeys } = params;
206
+ const {
207
+ label,
208
+ noCertSync,
209
+ systemKey,
210
+ dataplaneUrl,
211
+ authConfig,
212
+ datasourceKeys,
213
+ issuanceFailureHint,
214
+ verboseCertHints
215
+ } = params;
172
216
  if (noCertSync === true) return;
173
217
  try {
174
218
  const r = await syncSystemCertificationFromDataplane({
175
219
  systemKey,
176
220
  dataplaneUrl,
177
221
  authConfig,
178
- datasourceKeys
222
+ datasourceKeys,
223
+ issuanceFailureHint,
224
+ verboseCertHints
179
225
  });
180
226
  if (r.written) {
181
227
  logger.log(
182
228
  chalk.gray(`Updated certification block from dataplane${label ? ` (${label})` : ''} in system file.`)
183
229
  );
184
230
  } else if (r.reason) {
185
- logCertificationSyncNotWritten(r, label);
231
+ logCertificationSyncNotWritten(r, label, verboseCertHints === true);
186
232
  }
187
233
  } catch (e) {
188
234
  logger.log(chalk.yellow(`⚠ Certification sync skipped: ${e.message}`));
@@ -56,7 +56,7 @@ Examples:
56
56
  Notes:
57
57
  - To run E2E for one datasource, use:
58
58
  aifabrix datasource test-e2e <datasourceKey>
59
- - Optional --sync publishes local files to the dataplane first (external integration only).
59
+ - External integration: local system and datasource files are published to the dataplane before E2E (same as upload). Use --no-sync to exercise only config already deployed. The old --sync flag is a no-op (kept for scripts).
60
60
  `;
61
61
 
62
62
  module.exports = {
@@ -54,50 +54,81 @@ function setupTestCommand(program) {
54
54
  });
55
55
  }
56
56
 
57
+ /**
58
+ * First failed `certificateIssuance` across E2E datasource rows (for cert-sync hint).
59
+ * @param {Array<{ key?: string, datasourceTestRun?: { certificateIssuance?: { status?: string, reasonCode?: string, message?: string } } }>} results
60
+ * @returns {string|null}
61
+ */
62
+ function firstIssuanceFailureHintFromE2eResults(results) {
63
+ const failed = [];
64
+ for (const row of results || []) {
65
+ const ci = row.datasourceTestRun && row.datasourceTestRun.certificateIssuance;
66
+ if (ci && ci.status === 'failed') {
67
+ failed.push({ key: row.key, ci });
68
+ }
69
+ }
70
+ if (failed.length === 0) return null;
71
+ const firstRc = failed[0].ci.reasonCode;
72
+ const sameAll = failed.every(f => f.ci.reasonCode === firstRc);
73
+ if (sameAll && failed.length > 1) {
74
+ const parts = [firstRc, failed[0].ci.message].filter(Boolean);
75
+ return `${parts.join(': ')} (${failed.length} datasource scopes; first: ${failed[0].key})`;
76
+ }
77
+ const parts = [failed[0].ci.reasonCode, failed[0].ci.message].filter(Boolean);
78
+ return `[${failed[0].key}] ${parts.join(': ')}`.trim();
79
+ }
80
+
81
+ /**
82
+ * External integration E2E: run, display, exit on aggregate, optional cert sync.
83
+ * @param {string} appName
84
+ * @param {Object} options
85
+ * @returns {Promise<void>}
86
+ */
87
+ async function runExternalIntegrationE2EAndCertSync(appName, options) {
88
+ const { runTestE2EForExternalSystem } = require('../commands/test-e2e-external');
89
+ const { success, results } = await runTestE2EForExternalSystem(appName, {
90
+ env: options.env,
91
+ debug: options.debug,
92
+ verbose: options.verbose,
93
+ async: options.async !== false,
94
+ noSync: options.noSync === true
95
+ });
96
+ const { displayIntegrationTestResults } = require('../utils/external-system-display');
97
+ const datasourceResults = results.map(r => ({
98
+ key: r.key,
99
+ success: r.success,
100
+ error: r.error,
101
+ skipped: false,
102
+ datasourceTestRun: r.datasourceTestRun
103
+ }));
104
+ displayIntegrationTestResults(
105
+ { systemKey: appName, success, datasourceResults },
106
+ options.verbose,
107
+ { debug: options.debug, runType: 'e2e' }
108
+ );
109
+ const { computeSystemExitCodeFromDatasourceRows } = require('../utils/datasource-test-run-exit');
110
+ const exitCode = computeSystemExitCodeFromDatasourceRows(datasourceResults, {
111
+ warningsAsErrors: options.warningsAsErrors === true,
112
+ requireCert: options.requireCert === true
113
+ });
114
+ if (exitCode !== 0) process.exit(exitCode);
115
+ if (cliOptsSkipCertSync(options)) return;
116
+ const { trySyncCertificationFromDataplaneForExternalApp } = require('../certification/sync-after-external-command');
117
+ const issuanceFailureHint = firstIssuanceFailureHintFromE2eResults(results);
118
+ await trySyncCertificationFromDataplaneForExternalApp(appName, 'test-e2e', { issuanceFailureHint });
119
+ }
120
+
57
121
  async function runTestE2ECommand(appName, options) {
58
122
  const pathsUtil = require('../utils/paths');
59
123
  const appType = await pathsUtil.detectAppType(appName).catch(() => null);
60
- if (options.sync === true && appType && appType.baseDir === 'builder') {
124
+ if (options.noSync === true && appType && appType.baseDir === 'builder') {
61
125
  throw new Error(
62
- 'Option --sync applies only to external integration E2E (integration/<systemKey>/). ' +
63
- 'Remove --sync for builder app E2E, or use aifabrix upload from the integration folder first.'
126
+ 'Option --no-sync applies only to external integration E2E (integration/<systemKey>/). ' +
127
+ 'Remove --no-sync for builder app E2E.'
64
128
  );
65
129
  }
66
130
  if (appType && appType.baseDir === 'integration') {
67
- const { runTestE2EForExternalSystem } = require('../commands/test-e2e-external');
68
- const { success, results } = await runTestE2EForExternalSystem(appName, {
69
- env: options.env,
70
- debug: options.debug,
71
- verbose: options.verbose,
72
- async: options.async !== false,
73
- sync: options.sync === true
74
- });
75
- const { displayIntegrationTestResults } = require('../utils/external-system-display');
76
- const datasourceResults = results.map(r => ({
77
- key: r.key,
78
- success: r.success,
79
- error: r.error,
80
- skipped: false,
81
- datasourceTestRun: r.datasourceTestRun
82
- }));
83
- displayIntegrationTestResults(
84
- {
85
- systemKey: appName,
86
- success,
87
- datasourceResults
88
- },
89
- options.verbose,
90
- { debug: options.debug, runType: 'e2e' }
91
- );
92
- const { computeSystemExitCodeFromDatasourceRows } = require('../utils/datasource-test-run-exit');
93
- const exitCode = computeSystemExitCodeFromDatasourceRows(datasourceResults, {
94
- warningsAsErrors: options.warningsAsErrors === true,
95
- requireCert: options.requireCert === true
96
- });
97
- if (exitCode !== 0) process.exit(exitCode);
98
- if (cliOptsSkipCertSync(options)) return;
99
- const { trySyncCertificationFromDataplaneForExternalApp } = require('../certification/sync-after-external-command');
100
- await trySyncCertificationFromDataplaneForExternalApp(appName, 'test-e2e');
131
+ await runExternalIntegrationE2EAndCertSync(appName, options);
101
132
  return;
102
133
  }
103
134
  const { runAppTestE2e } = require('../commands/app-test');
@@ -135,7 +166,11 @@ function setupTestE2eCommand(program) {
135
166
  .option('-d, --debug', 'Include debug output and write log to integration/<systemKey>/logs/')
136
167
  .option(
137
168
  '--sync',
138
- 'Publish local system and datasource files to the dataplane before running E2E (same as aifabrix upload <systemKey>; external integration only)'
169
+ '(Deprecated; no-op.) Local integration files are published before E2E by default; use --no-sync to skip.'
170
+ )
171
+ .option(
172
+ '--no-sync',
173
+ 'Skip publishing local integration files; E2E uses the system config already on the dataplane (external integration only)'
139
174
  )
140
175
  .option('--warnings-as-errors', 'Treat aggregate warn as failure (exit 1)')
141
176
  .option('--require-cert', 'Require certification passed on every datasource (exit 2 if not)')
@@ -190,6 +225,7 @@ function setupInstallTestE2eLintCommands(program) {
190
225
  }
191
226
 
192
227
  module.exports = {
193
- setupInstallTestE2eLintCommands
228
+ setupInstallTestE2eLintCommands,
229
+ firstIssuanceFailureHintFromE2eResults
194
230
  };
195
231
 
@@ -18,7 +18,7 @@ const { resolveControllerUrl } = require('../utils/controller-url');
18
18
  const { handleLogin } = require('../commands/login');
19
19
  const { handleUpMiso } = require('../commands/up-miso');
20
20
  const { handleUpDataplane } = require('../commands/up-dataplane');
21
- const { cleanBuilderAppDirs } = require('../commands/up-common');
21
+ const { applyUpPlatformForceConfig, cleanBuilderAppDirs } = require('../commands/up-common');
22
22
  const {
23
23
  loadInfraStatusSummary,
24
24
  formatInfraStatusTitleLine,
@@ -184,10 +184,14 @@ function setupUpPlatformCommand(program) {
184
184
  .option('-r, --registry <url>', 'Override registry for all apps (e.g. myacr.azurecr.io)')
185
185
  .option('--registry-mode <mode>', 'Override registry mode (acr|external)')
186
186
  .option('-i, --image <key>=<value>', 'Override image (e.g. keycloak=myreg/k:v1, miso-controller=myreg/m:v1, dataplane=myreg/d:v1); can be repeated', (v, prev) => (prev || []).concat([v]))
187
- .option('-f, --force', 'Clean builder/keycloak, builder/miso-controller, builder/dataplane and re-fetch from templates')
187
+ .option(
188
+ '-f, --force',
189
+ 'Reset CLI auth (clear all device/client tokens, set environment to dev, set default controller URL from developer-id), clean builder/keycloak, builder/miso-controller, builder/dataplane, then re-fetch from templates'
190
+ )
188
191
  .action(async(options) => {
189
192
  try {
190
193
  if (options.force) {
194
+ await applyUpPlatformForceConfig();
191
195
  await cleanBuilderAppDirs(['keycloak', 'miso-controller', 'dataplane']);
192
196
  }
193
197
  await handleUpMiso(options);
@@ -30,6 +30,24 @@ Examples:
30
30
  $ aifabrix validate --builder
31
31
  `;
32
32
 
33
+ const REPAIR_HELP_AFTER = `
34
+ Examples:
35
+ $ aifabrix repair hubspot-demo
36
+ Align manifest, system/datasource files, RBAC extract, env.template, and deploy JSON under integration/<systemKey>/.
37
+
38
+ $ aifabrix repair hubspot-demo --dry-run
39
+ Show what would change without writing files.
40
+
41
+ $ aifabrix repair hubspot-demo --auth oauth2
42
+ Set authentication method; updates the system file and env.template (oauth2, aad, apikey, basic, …).
43
+
44
+ $ aifabrix repair hubspot-demo --rbac --expose --sync --test
45
+ Optional datasource fixes: RBAC roles/permissions, exposed.schema from attributes, default sync block, testPayload stubs.
46
+
47
+ $ aifabrix repair hubspot-demo --doc
48
+ Regenerate README.md from the current deployment manifest only (other drift fixes unchanged).
49
+ `;
50
+
33
51
  /**
34
52
  * Resolve app path and type for split-json (integration first, then builder).
35
53
  *
@@ -118,7 +136,7 @@ function setupResolveCommand(program) {
118
136
  );
119
137
  logger.log(`✔ Generated .env file: ${envPath}`);
120
138
  if (envOnly) {
121
- logger.log(chalk.gray(' (env-only mode: validation skipped; no application.yaml)'));
139
+ logger.log(chalk.gray(' (env-only mode: validation skipped; no application config file)'));
122
140
  } else if (!options.skipValidation) {
123
141
  const validate = require('../validation/validate');
124
142
  const result = await validate.validateAppOrFile(appName);
@@ -185,6 +203,7 @@ function setupRepairCommand(program) {
185
203
  .option('--expose', 'Set exposed.schema on each datasource from all fieldMappings.attributes keys (metadata.<key>); removes deprecated exposed.attributes if present')
186
204
  .option('--sync', 'Add default sync section to datasources that lack it')
187
205
  .option('--test', 'Generate testPayload.payloadTemplate and testPayload.expectedResult from attributes')
206
+ .addHelpText('after', REPAIR_HELP_AFTER)
188
207
  .action(async(appName, options) => {
189
208
  try {
190
209
  const { repairExternalIntegration } = require('../commands/repair');