@aifabrix/builder 2.44.6 → 2.45.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 (257) hide show
  1. package/.cursor/rules/cli-layout.mdc +7 -3
  2. package/.cursor/rules/docs-rules.mdc +3 -1
  3. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  4. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  5. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
  6. package/jest.projects.js +100 -1
  7. package/lib/api/agent-metadata-validation.api.js +93 -0
  8. package/lib/api/audit-api-helpers.js +68 -0
  9. package/lib/api/audit.api.js +119 -0
  10. package/lib/api/auth-cache.api.js +44 -0
  11. package/lib/api/certificates.api.js +4 -4
  12. package/lib/api/credential.api.js +2 -2
  13. package/lib/api/dataplane-health.api.js +137 -0
  14. package/lib/api/dataplane-sync.api.js +27 -0
  15. package/lib/api/datasources-core.api.js +21 -18
  16. package/lib/api/datasources-extended.api.js +22 -22
  17. package/lib/api/dimensions.api.js +3 -0
  18. package/lib/api/external-systems.api.js +13 -13
  19. package/lib/api/governance-scenarios.api.js +39 -0
  20. package/lib/api/groups.api.js +79 -0
  21. package/lib/api/index.js +45 -2
  22. package/lib/api/pipeline.api.js +4 -4
  23. package/lib/api/protection.api.js +157 -0
  24. package/lib/api/records-bulk.api.js +30 -0
  25. package/lib/api/records-search-parse.js +49 -0
  26. package/lib/api/records-search.api.js +29 -0
  27. package/lib/api/types/agent-metadata-validation.types.js +67 -0
  28. package/lib/api/types/audit.types.js +72 -0
  29. package/lib/api/types/dataplane-health.types.js +27 -0
  30. package/lib/api/types/dimensions.types.js +2 -0
  31. package/lib/api/types/governance-scenarios.types.js +39 -0
  32. package/lib/api/types/protection.types.js +69 -0
  33. package/lib/api/types/records-bulk.types.js +35 -0
  34. package/lib/api/types/records-search.types.js +28 -0
  35. package/lib/api/user-groups.api.js +50 -0
  36. package/lib/api/users.api.js +95 -0
  37. package/lib/api/validation-run.api.js +3 -3
  38. package/lib/api/validation-runner.js +3 -3
  39. package/lib/api/wizard-platform.api.js +3 -3
  40. package/lib/api/wizard.api.js +21 -21
  41. package/lib/app/deploy.js +34 -19
  42. package/lib/app/helpers.js +3 -3
  43. package/lib/app/index.js +3 -3
  44. package/lib/app/register.js +7 -6
  45. package/lib/app/restart-display.js +52 -21
  46. package/lib/app/rotate-secret.js +7 -6
  47. package/lib/app/run-helpers.js +15 -8
  48. package/lib/app/run.js +57 -9
  49. package/lib/app/show-display.js +7 -0
  50. package/lib/app/show.js +87 -5
  51. package/lib/build/index.js +9 -5
  52. package/lib/cli/index.js +4 -2
  53. package/lib/cli/infra-guided.js +62 -28
  54. package/lib/cli/installation-log-command.js +73 -0
  55. package/lib/cli/setup-app.help.js +50 -2
  56. package/lib/cli/setup-app.js +44 -25
  57. package/lib/cli/setup-app.test-commands.js +80 -21
  58. package/lib/cli/setup-auth.js +124 -52
  59. package/lib/cli/setup-external-system.js +37 -3
  60. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  61. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  62. package/lib/cli/setup-infra.js +60 -119
  63. package/lib/cli/setup-platform.js +1 -1
  64. package/lib/cli/setup-utility-help-after.js +45 -0
  65. package/lib/cli/setup-utility-resolve.js +132 -0
  66. package/lib/cli/setup-utility.js +148 -116
  67. package/lib/commands/app-logs.js +81 -33
  68. package/lib/commands/auth-config.js +116 -18
  69. package/lib/commands/auth-status-dataplane-version.js +231 -0
  70. package/lib/commands/auth-status-display.js +94 -0
  71. package/lib/commands/auth-status.js +70 -6
  72. package/lib/commands/credential-push.js +10 -5
  73. package/lib/commands/datasource-load-export-cli.js +178 -0
  74. package/lib/commands/datasource-test-trust-cli.js +114 -0
  75. package/lib/commands/datasource-unified-test-cli.js +25 -17
  76. package/lib/commands/datasource-unified-test-cli.options.js +18 -3
  77. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
  78. package/lib/commands/datasource-verify-audit-cli.js +96 -0
  79. package/lib/commands/datasource.js +68 -1
  80. package/lib/commands/dimension-value.js +43 -13
  81. package/lib/commands/dimension.js +38 -7
  82. package/lib/commands/identity-apply-sync.js +131 -0
  83. package/lib/commands/identity-group-cli.js +146 -0
  84. package/lib/commands/identity-membership-role-cache.js +175 -0
  85. package/lib/commands/identity-shared.js +94 -0
  86. package/lib/commands/identity-user-cli.js +158 -0
  87. package/lib/commands/identity.js +43 -0
  88. package/lib/commands/login.js +46 -16
  89. package/lib/commands/protection-cli-leaves.js +286 -0
  90. package/lib/commands/protection.js +50 -0
  91. package/lib/commands/repair.js +13 -3
  92. package/lib/commands/setup-modes.js +19 -6
  93. package/lib/commands/setup-prompts.js +41 -8
  94. package/lib/commands/setup.js +114 -9
  95. package/lib/commands/teardown.js +54 -5
  96. package/lib/commands/test-e2e-external.js +40 -9
  97. package/lib/commands/test-governance-cli-options.js +47 -0
  98. package/lib/commands/test-governance-command-action.js +55 -0
  99. package/lib/commands/test-governance-external.js +88 -0
  100. package/lib/commands/test-trust-cli-options.js +32 -0
  101. package/lib/commands/test-trust-command-action.js +118 -0
  102. package/lib/commands/test-trust-external.js +127 -0
  103. package/lib/commands/up-common.js +48 -14
  104. package/lib/commands/up-dataplane.js +21 -18
  105. package/lib/commands/up-miso.js +12 -8
  106. package/lib/commands/upload.js +38 -15
  107. package/lib/commands/wizard-core.js +3 -0
  108. package/lib/core/audit-logger.js +1 -34
  109. package/lib/core/config-admin-email.js +56 -0
  110. package/lib/core/config-device-dataplane.js +185 -0
  111. package/lib/core/config-normalize.js +60 -0
  112. package/lib/core/config-registered-controller-urls.js +54 -0
  113. package/lib/core/config.js +33 -50
  114. package/lib/core/secrets-ensure-infra.js +1 -1
  115. package/lib/core/secrets-env-content.js +86 -90
  116. package/lib/core/secrets-env-declarative-expand.js +170 -0
  117. package/lib/core/secrets-env-write.js +2 -0
  118. package/lib/core/secrets-load.js +106 -102
  119. package/lib/datasource/agent-trust-debug-log.js +45 -0
  120. package/lib/datasource/agent-trust-fetch.js +115 -0
  121. package/lib/datasource/agent-trust-map.js +101 -0
  122. package/lib/datasource/agent-trust-mock.js +44 -0
  123. package/lib/datasource/agent-trust-run.js +93 -0
  124. package/lib/datasource/agent-trust-timeout.js +25 -0
  125. package/lib/datasource/audit-evidence-display.js +80 -0
  126. package/lib/datasource/audit-evidence-extract-debug.js +103 -0
  127. package/lib/datasource/audit-evidence-extract.js +177 -0
  128. package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
  129. package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
  130. package/lib/datasource/audit-evidence-poll.js +109 -0
  131. package/lib/datasource/audit-evidence-run.js +177 -0
  132. package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
  133. package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
  134. package/lib/datasource/audit-evidence-verify.js +108 -0
  135. package/lib/datasource/bulk-loader-run-batches.js +88 -0
  136. package/lib/datasource/bulk-loader-service-retry.js +52 -0
  137. package/lib/datasource/bulk-loader-service.js +127 -0
  138. package/lib/datasource/capability/run-capability-dimension.js +2 -1
  139. package/lib/datasource/datasource-exporter-resolve.js +82 -0
  140. package/lib/datasource/datasource-exporter-service.js +144 -0
  141. package/lib/datasource/datasource-identifier.js +148 -0
  142. package/lib/datasource/datasource-load-export-context.js +92 -0
  143. package/lib/datasource/integration-context.js +6 -4
  144. package/lib/datasource/local-data-paths.js +145 -0
  145. package/lib/datasource/log-cleaner.js +241 -0
  146. package/lib/datasource/log-viewer.js +111 -6
  147. package/lib/datasource/record-file-parser.js +85 -0
  148. package/lib/datasource/record-mapper.js +129 -0
  149. package/lib/datasource/resolve-app.js +74 -55
  150. package/lib/datasource/test-e2e.js +26 -2
  151. package/lib/datasource/unified-validation-run-resolve.js +45 -12
  152. package/lib/datasource/unified-validation-run.js +43 -17
  153. package/lib/datasource/validate.js +37 -20
  154. package/lib/external-system/deploy.js +5 -1
  155. package/lib/external-system/test-system-level.js +25 -4
  156. package/lib/external-system/test.js +3 -1
  157. package/lib/governance/governance-pack-loader.js +70 -0
  158. package/lib/governance/governance-run-display.js +119 -0
  159. package/lib/identity/identity-apply-core.js +108 -0
  160. package/lib/identity/identity-apply-phases.js +244 -0
  161. package/lib/identity/identity-apply-service.js +77 -0
  162. package/lib/identity/identity-csv-parser.js +114 -0
  163. package/lib/internal/fs-real-sync.js +29 -1
  164. package/lib/internal/node-fs.js +2 -0
  165. package/lib/protection/auth-context.js +45 -0
  166. package/lib/protection/convert-batch.js +90 -0
  167. package/lib/protection/load.js +41 -0
  168. package/lib/protection/paths.js +158 -0
  169. package/lib/protection/preflight-datasource-ready.js +46 -0
  170. package/lib/protection/protection-create-probe-lines.js +142 -0
  171. package/lib/protection/protection-create-probes.js +95 -0
  172. package/lib/protection/protection-create-scaffold.js +281 -0
  173. package/lib/protection/protection-display-helpers.js +445 -0
  174. package/lib/protection/protection-display.js +215 -0
  175. package/lib/protection/protection-preset-registry.js +97 -0
  176. package/lib/protection/report-exit.js +62 -0
  177. package/lib/protection/resolve.js +202 -0
  178. package/lib/protection/run-commands-validate.js +130 -0
  179. package/lib/protection/run-commands.js +216 -0
  180. package/lib/protection/run-protection-create-helpers.js +134 -0
  181. package/lib/protection/run-protection-create-probes-flow.js +43 -0
  182. package/lib/protection/run-protection-create.js +191 -0
  183. package/lib/protection/scope.js +22 -0
  184. package/lib/protection/sync-after-upload.js +64 -0
  185. package/lib/protection/upload-batch.js +127 -0
  186. package/lib/protection/validate-batch.js +113 -0
  187. package/lib/protection/validate-local.js +65 -0
  188. package/lib/resolvers/dimension-file.js +29 -4
  189. package/lib/schema/application-schema.json +4 -0
  190. package/lib/schema/external-datasource.schema.json +47 -13
  191. package/lib/schema/flag-map-validation-run.json +2 -2
  192. package/lib/schema/infra.parameter.yaml +10 -0
  193. package/lib/schema/protection.schema.json +374 -0
  194. package/lib/schema/type/credential-field-registry.json +636 -0
  195. package/lib/schema/wizard-config.schema.json +1 -1
  196. package/lib/utils/agent-trust-run-display.js +231 -0
  197. package/lib/utils/agent-trust-run-exit.js +59 -0
  198. package/lib/utils/agent-trust-wait-ui.js +94 -0
  199. package/lib/utils/app-config-resolver.js +24 -1
  200. package/lib/utils/applications-config-defaults.js +206 -0
  201. package/lib/utils/auth-config-validator.js +2 -12
  202. package/lib/utils/bash-secret-env.js +1 -1
  203. package/lib/utils/cli-exit-codes.js +30 -0
  204. package/lib/utils/cli-sync-options.js +33 -0
  205. package/lib/utils/compose-generate-docker-compose.js +111 -6
  206. package/lib/utils/compose-generator.js +17 -8
  207. package/lib/utils/controller-url.js +50 -7
  208. package/lib/utils/credential-push-ui.js +90 -0
  209. package/lib/utils/dataplane-cli-version-gate.js +202 -0
  210. package/lib/utils/dataplane-cli-version-help.js +74 -0
  211. package/lib/utils/datasource-export-display.js +137 -0
  212. package/lib/utils/datasource-load-display.js +174 -0
  213. package/lib/utils/datasource-load-export-ui.js +120 -0
  214. package/lib/utils/datasource-test-run-capability-scope.js +3 -3
  215. package/lib/utils/datasource-test-run-display.js +3 -3
  216. package/lib/utils/datasource-test-run-tty-log.js +5 -1
  217. package/lib/utils/env-copy.js +99 -14
  218. package/lib/utils/env-template.js +5 -1
  219. package/lib/utils/health-check-url.js +18 -15
  220. package/lib/utils/health-check.js +7 -5
  221. package/lib/utils/help-builder.js +30 -24
  222. package/lib/utils/infra-optional-service-flags.js +69 -0
  223. package/lib/utils/installation-log-core.js +282 -0
  224. package/lib/utils/installation-log-record.js +237 -0
  225. package/lib/utils/installation-log.js +123 -0
  226. package/lib/utils/log-redaction.js +105 -0
  227. package/lib/utils/manifest-location.js +164 -0
  228. package/lib/utils/manifest-source-emit.js +184 -0
  229. package/lib/utils/paths.js +238 -89
  230. package/lib/utils/remote-secrets-loader.js +7 -1
  231. package/lib/utils/run-cli-flags.js +29 -0
  232. package/lib/utils/secrets-canonical.js +10 -3
  233. package/lib/utils/secrets-path.js +3 -4
  234. package/lib/utils/secrets-utils.js +20 -10
  235. package/lib/utils/semver-compare.js +127 -0
  236. package/lib/utils/system-builder-root.js +10 -2
  237. package/lib/utils/upload-pipeline-ui.js +91 -0
  238. package/lib/utils/url-declarative-public-base.js +80 -12
  239. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  240. package/lib/utils/url-declarative-resolve-build.js +24 -393
  241. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  242. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  243. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  244. package/lib/utils/url-declarative-resolve.js +47 -7
  245. package/lib/utils/url-declarative-runtime-base-path.js +21 -1
  246. package/lib/utils/urls-local-registry-scan.js +103 -0
  247. package/lib/utils/urls-local-registry.js +161 -90
  248. package/lib/utils/validation-poll-ui.js +14 -7
  249. package/lib/validation/wizard-config-validator.js +58 -19
  250. package/package.json +3 -1
  251. package/templates/applications/dataplane/application.yaml +4 -0
  252. package/templates/applications/dataplane/env.template +13 -1
  253. package/templates/applications/dataplane/rbac.yaml +4 -0
  254. package/templates/applications/miso-controller/application.yaml +2 -0
  255. package/templates/applications/miso-controller/env.template +27 -29
  256. package/templates/external-system/README.md.hbs +56 -8
  257. package/.npmrc.token +0 -1
@@ -13,7 +13,15 @@ const chalk = require('chalk');
13
13
  const logger = require('../utils/logger');
14
14
  const { resolveAppKeyForDatasource } = require('./resolve-app');
15
15
  const { getIntegrationPath } = require('../utils/paths');
16
- const { sectionTitle, headerKeyValue, formatBlockingError, successGlyph, failureGlyph } = require('../utils/cli-test-layout-chalk');
16
+ const {
17
+ sectionTitle,
18
+ headerKeyValue,
19
+ formatBlockingError,
20
+ successGlyph,
21
+ failureGlyph
22
+ } = require('../utils/cli-test-layout-chalk');
23
+ const { statusGlyph } = require('../utils/datasource-test-run-display');
24
+ const { trustDecisionToRowStatus } = require('./agent-trust-map');
17
25
 
18
26
  /**
19
27
  * Get the path to the latest log file in a directory matching a glob-like pattern
@@ -48,7 +56,13 @@ async function getLatestLogPath(logsDir, pattern) {
48
56
  */
49
57
  function isStructuralTestLogFileName(name) {
50
58
  if (!name || typeof name !== 'string' || !name.endsWith('.json')) return false;
51
- if (name.startsWith('test-e2e-') || name.startsWith('test-integration-')) return false;
59
+ if (
60
+ name.startsWith('test-e2e-') ||
61
+ name.startsWith('test-integration-') ||
62
+ name.startsWith('test-trust-')
63
+ ) {
64
+ return false;
65
+ }
52
66
  return name.startsWith('test-');
53
67
  }
54
68
 
@@ -232,6 +246,85 @@ function structuralEnvelopeStatusLine(status) {
232
246
  * @param {Object} data - Parsed JSON { request, response?, error? }
233
247
  * @param {string} [fileName] - Log file name for header
234
248
  */
249
+ function trustLogGlyph(trustDecision) {
250
+ return statusGlyph(trustDecisionToRowStatus(trustDecision));
251
+ }
252
+
253
+ function logTrustWarningsSection(run) {
254
+ const warnings = Array.isArray(run.highLevelWarnings) ? run.highLevelWarnings : [];
255
+ if (!warnings.length) return;
256
+ logger.log(sectionTitle('Warnings:'));
257
+ for (const w of warnings) {
258
+ logger.log(` ${chalk.yellow('•')} ${chalk.white(String(w))}`);
259
+ }
260
+ }
261
+
262
+ function logTrustFindingsSection(run) {
263
+ const findings = Array.isArray(run.findings) ? run.findings : [];
264
+ if (!findings.length) return;
265
+ logger.log(sectionTitle('Findings:'));
266
+ const cap = 15;
267
+ for (let i = 0; i < Math.min(cap, findings.length); i += 1) {
268
+ const f = findings[i];
269
+ const code = f?.code || f?.stableCode || 'finding';
270
+ const msg = f?.message ? String(f.message) : '';
271
+ logger.log(` ${chalk.gray(`• ${code}`)} ${msg}`.trim());
272
+ }
273
+ if (findings.length > cap) {
274
+ logger.log(chalk.gray(` … and ${findings.length - cap} more (open JSON file for full list)`));
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Format agent trust log (`datasource test-trust --debug` → `test-trust-*.json`).
280
+ * @param {Object} data - Parsed JSON { request, response?, error? }
281
+ * @param {string} [fileName] - Log file name for header
282
+ */
283
+ function formatTrustLog(data, fileName) {
284
+ logger.log('');
285
+ logger.log(sectionTitle('Agent trust log'));
286
+ if (fileName) logger.log(chalk.gray(fileName));
287
+ const req = data.request || {};
288
+ logger.log('');
289
+ logger.log(sectionTitle('Request:'));
290
+ logger.log(headerKeyValue('datasourceKey:', String(req.datasourceKey ?? '—')));
291
+ if (req.revalidate !== undefined) {
292
+ logger.log(chalk.gray(` revalidate: ${req.revalidate}`));
293
+ }
294
+ if (data.error) {
295
+ logger.log('');
296
+ logger.log(formatBlockingError(`Error: ${data.error}`));
297
+ return;
298
+ }
299
+ const run = data.response || {};
300
+ logger.log('');
301
+ logger.log(sectionTitle('Response:'));
302
+ logger.log(headerKeyValue('System:', String(run.systemKey ?? '—')));
303
+ const confPct = Math.round((Number(run.confidence) || 0) * 100);
304
+ logger.log(
305
+ headerKeyValue(
306
+ 'Trust:',
307
+ `${trustLogGlyph(run.trustDecision)} ${run.trustDecision ?? '—'} · ${confPct}% · ${run.validationStatus ?? '—'}`
308
+ )
309
+ );
310
+ if (run.validatedAt) {
311
+ logger.log(chalk.gray(` validatedAt: ${run.validatedAt}`));
312
+ }
313
+ if (run.inputHash) {
314
+ logger.log(chalk.gray(` inputHash: ${truncate(String(run.inputHash), 40)}`));
315
+ }
316
+ if (run.cacheHit !== undefined && run.cacheHit !== null) {
317
+ logger.log(chalk.gray(` cacheHit: ${run.cacheHit}`));
318
+ }
319
+ if (run.summary) {
320
+ logger.log('');
321
+ logger.log(chalk.white(' Summary'));
322
+ logger.log(` ${run.summary}`);
323
+ }
324
+ logTrustWarningsSection(run);
325
+ logTrustFindingsSection(run);
326
+ }
327
+
235
328
  function formatStructuralTestLog(data, fileName) {
236
329
  logger.log('');
237
330
  logger.log(sectionTitle('Structural validation log'));
@@ -263,7 +356,7 @@ function formatStructuralTestLog(data, fileName) {
263
356
  /**
264
357
  * Format log content by type
265
358
  * @param {Object} parsed - Parsed JSON log
266
- * @param {'test'|'test-e2e'|'test-integration'} logType - Log type
359
+ * @param {'test'|'test-e2e'|'test-integration'|'test-trust'} logType - Log type
267
360
  * @param {string} [fileName] - File name for header
268
361
  */
269
362
  function formatLogContent(parsed, logType, fileName) {
@@ -271,11 +364,15 @@ function formatLogContent(parsed, logType, fileName) {
271
364
  formatStructuralTestLog(parsed, fileName);
272
365
  return;
273
366
  }
367
+ if (logType === 'test-trust') {
368
+ formatTrustLog(parsed, fileName);
369
+ return;
370
+ }
274
371
  if (logType === 'test-e2e') {
275
372
  formatE2ELog(parsed, fileName);
276
- } else {
277
- formatIntegrationLog(parsed, fileName);
373
+ return;
278
374
  }
375
+ formatIntegrationLog(parsed, fileName);
279
376
  }
280
377
 
281
378
  /**
@@ -313,7 +410,7 @@ function parseDatasourceLogJson(content, logPath) {
313
410
  * @param {Object} options - Options
314
411
  * @param {string} [options.app] - App key (optional, resolved from key if omitted)
315
412
  * @param {string} [options.file] - Path to log file (overrides app resolution)
316
- * @param {'test'|'test-e2e'|'test-integration'} options.logType - Log type
413
+ * @param {'test'|'test-e2e'|'test-integration'|'test-trust'} options.logType - Log type
317
414
  * @throws {Error} When file not found or invalid JSON
318
415
  */
319
416
  /* eslint-disable-next-line max-statements -- Resolve path, read, parse, format */
@@ -336,6 +433,13 @@ async function runLogViewer(datasourceKey, options) {
336
433
  `No structural validation log found in ${logsDir}. Run: aifabrix datasource test <key> --debug`
337
434
  );
338
435
  }
436
+ } else if (logType === 'test-trust') {
437
+ logPath = await getLatestLogPath(logsDir, 'test-trust-');
438
+ if (!logPath) {
439
+ throw new Error(
440
+ `No test-trust log found in ${logsDir}. Run: aifabrix datasource test-trust <key> --debug`
441
+ );
442
+ }
339
443
  } else {
340
444
  const pattern = logType === 'test-e2e' ? 'test-e2e-' : 'test-integration-';
341
445
  logPath = await getLatestLogPath(logsDir, pattern);
@@ -360,6 +464,7 @@ module.exports = {
360
464
  formatE2ELog,
361
465
  formatIntegrationLog,
362
466
  formatStructuralTestLog,
467
+ formatTrustLog,
363
468
  parseDatasourceLogJson,
364
469
  runLogViewer
365
470
  };
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @fileoverview Parse JSON array and NDJSON record fixture files (plan 144)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const fs = require('fs');
10
+
11
+ /**
12
+ * @param {string} content
13
+ * @returns {Object[]}
14
+ */
15
+ function parseNdjsonContent(content) {
16
+ const lines = String(content).split(/\r?\n/);
17
+ const records = [];
18
+ for (let i = 0; i < lines.length; i += 1) {
19
+ const line = lines[i].trim();
20
+ if (!line) continue;
21
+ let parsed;
22
+ try {
23
+ parsed = JSON.parse(line);
24
+ } catch (err) {
25
+ throw new Error(`NDJSON parse error at line ${i + 1}: ${err.message}`);
26
+ }
27
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
28
+ throw new Error(`NDJSON line ${i + 1}: expected a JSON object`);
29
+ }
30
+ records.push(parsed);
31
+ }
32
+ return records;
33
+ }
34
+
35
+ /**
36
+ * @param {string} content
37
+ * @returns {Object[]}
38
+ */
39
+ function parseJsonArrayContent(content) {
40
+ let parsed;
41
+ try {
42
+ parsed = JSON.parse(content);
43
+ } catch (err) {
44
+ throw new Error(`JSON parse error: ${err.message}`);
45
+ }
46
+ if (!Array.isArray(parsed)) {
47
+ throw new Error('JSON file must be a top-level array of objects');
48
+ }
49
+ parsed.forEach((row, index) => {
50
+ if (!row || typeof row !== 'object' || Array.isArray(row)) {
51
+ throw new Error(`JSON array index ${index}: expected an object`);
52
+ }
53
+ });
54
+ return parsed;
55
+ }
56
+
57
+ /**
58
+ * Parse a local record fixture file.
59
+ * @param {string} filePath
60
+ * @param {'json'|'ndjson'} format
61
+ * @returns {Object[]}
62
+ */
63
+ function parseRecordFile(filePath, format) {
64
+ const content = fs.readFileSync(filePath, 'utf8');
65
+ if (format === 'ndjson') {
66
+ return parseNdjsonContent(content);
67
+ }
68
+ return parseJsonArrayContent(content);
69
+ }
70
+
71
+ /**
72
+ * Rough payload size estimate for dry-run (bytes).
73
+ * @param {Object[]} records
74
+ * @returns {number}
75
+ */
76
+ function estimatePayloadBytes(records) {
77
+ return Buffer.byteLength(JSON.stringify(records), 'utf8');
78
+ }
79
+
80
+ module.exports = {
81
+ parseRecordFile,
82
+ parseNdjsonContent,
83
+ parseJsonArrayContent,
84
+ estimatePayloadBytes
85
+ };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @fileoverview Map fixture payloads to canonical bulk sync records (plan 144)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * @param {Object} obj
11
+ * @returns {boolean}
12
+ */
13
+ function isCanonicalBulkRecord(obj) {
14
+ return (
15
+ obj &&
16
+ typeof obj === 'object' &&
17
+ typeof obj.key === 'string' &&
18
+ obj.key.trim().length > 0 &&
19
+ typeof obj.recordType === 'string' &&
20
+ obj.recordType.trim().length > 0 &&
21
+ obj.metadata !== undefined &&
22
+ obj.metadata !== null &&
23
+ typeof obj.metadata === 'object' &&
24
+ !Array.isArray(obj.metadata)
25
+ );
26
+ }
27
+
28
+ /**
29
+ * @param {string[]} primaryKeyFields
30
+ * @param {Object} payload
31
+ * @returns {string}
32
+ */
33
+ function buildKeyFromPrimaryKey(primaryKeyFields, payload) {
34
+ if (!primaryKeyFields.length) {
35
+ throw new Error('Datasource primaryKey is empty; provide records with a key field');
36
+ }
37
+ if (primaryKeyFields.length === 1) {
38
+ const field = primaryKeyFields[0];
39
+ const value = payload[field];
40
+ if (value === undefined || value === null || String(value).trim() === '') {
41
+ throw new Error(`Missing primary key field '${field}' in record payload`);
42
+ }
43
+ return String(value);
44
+ }
45
+ const parts = [];
46
+ for (const field of primaryKeyFields) {
47
+ const value = payload[field];
48
+ if (value === undefined || value === null || String(value).trim() === '') {
49
+ throw new Error(`Missing composite primary key field '${field}' in record payload`);
50
+ }
51
+ parts.push(`${field}=${String(value)}`);
52
+ }
53
+ return parts.join(',');
54
+ }
55
+
56
+ /**
57
+ * @param {Object} datasource
58
+ * @param {Object} payload
59
+ * @returns {import('../api/types/records-bulk.types').ExternalBulkRecord}
60
+ */
61
+ function mapPayloadToBulkRecord(datasource, payload) {
62
+ const primaryKey = Array.isArray(datasource.primaryKey) ? datasource.primaryKey : [];
63
+ const recordType =
64
+ typeof datasource.resourceType === 'string' && datasource.resourceType.trim()
65
+ ? datasource.resourceType.trim()
66
+ : 'record';
67
+ const key = buildKeyFromPrimaryKey(primaryKey, payload);
68
+ let displayName = '';
69
+ const labelKey = datasource.labelKey;
70
+ if (Array.isArray(labelKey) && labelKey.length > 0) {
71
+ displayName = labelKey
72
+ .map(field => payload[field])
73
+ .filter(v => v !== undefined && v !== null && String(v).trim() !== '')
74
+ .map(v => String(v))
75
+ .join(' ');
76
+ } else if (typeof labelKey === 'string' && labelKey.trim() && payload[labelKey] !== undefined && payload[labelKey] !== null) {
77
+ displayName = String(payload[labelKey]);
78
+ }
79
+ const metadata = { ...payload };
80
+ if (metadata.externalId === undefined) {
81
+ metadata.externalId = key;
82
+ }
83
+ return {
84
+ key,
85
+ displayName: displayName || '',
86
+ recordType,
87
+ metadata
88
+ };
89
+ }
90
+
91
+ /**
92
+ * @param {Object[]} rawRecords
93
+ * @param {Object} datasource
94
+ * @returns {import('../api/types/records-bulk.types').ExternalBulkRecord[]}
95
+ */
96
+ function normalizeRecordsForBulk(rawRecords, datasource) {
97
+ if (!Array.isArray(rawRecords) || rawRecords.length === 0) {
98
+ throw new Error('Record file is empty after parse');
99
+ }
100
+ return rawRecords.map((row, index) => {
101
+ try {
102
+ if (isCanonicalBulkRecord(row)) {
103
+ const meta = { ...row.metadata };
104
+ if (meta.externalId === undefined) {
105
+ meta.externalId = row.key;
106
+ }
107
+ return {
108
+ key: String(row.key).trim(),
109
+ displayName:
110
+ row.displayName !== undefined && row.displayName !== null
111
+ ? String(row.displayName)
112
+ : '',
113
+ recordType: String(row.recordType).trim(),
114
+ metadata: meta
115
+ };
116
+ }
117
+ return mapPayloadToBulkRecord(datasource, row);
118
+ } catch (err) {
119
+ throw new Error(`Record ${index + 1}: ${err.message}`);
120
+ }
121
+ });
122
+ }
123
+
124
+ module.exports = {
125
+ isCanonicalBulkRecord,
126
+ buildKeyFromPrimaryKey,
127
+ mapPayloadToBulkRecord,
128
+ normalizeRecordsForBulk
129
+ };
@@ -5,49 +5,82 @@
5
5
  * @version 2.0.0
6
6
  */
7
7
 
8
- const path = require('path');
9
8
  const fs = require('fs');
10
9
  const {
11
10
  getIntegrationPath,
12
11
  listIntegrationAppNames,
13
12
  resolveIntegrationAppKeyFromCwd
14
13
  } = require('../utils/paths');
15
- const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
16
- const { loadConfigFile } = require('../utils/config-format');
14
+ const {
15
+ appHasDatasourceIdentifier,
16
+ pickIntegrationAppForDatasourceIdentifier
17
+ } = require('./datasource-identifier');
18
+
19
+ /** @deprecated Use appHasDatasourceIdentifier — kept for tests and backward compatibility */
20
+ function appHasDatasourceKey(appKey, datasourceKey) {
21
+ return appHasDatasourceIdentifier(appKey, datasourceKey);
22
+ }
17
23
 
18
24
  /**
19
- * For one app, check if any of its datasource files has the given key
20
- * @param {string} appKey - Integration folder name (system key context)
21
- * @param {string} datasourceKey - Datasource key to match
22
- * @returns {boolean} True if this app has a datasource with that key
25
+ * @param {string} key
26
+ * @param {string} [explicitApp]
27
+ * @returns {{ appKey: string }|null}
23
28
  */
24
- function appHasDatasourceKey(appKey, datasourceKey) {
25
- const appPath = getIntegrationPath(appKey);
26
- let config;
27
- try {
28
- const configPath = resolveApplicationConfigPath(appPath);
29
- config = loadConfigFile(configPath);
30
- } catch {
31
- return false;
29
+ function resolveFromExplicitApp(key, explicitApp) {
30
+ if (!explicitApp || typeof explicitApp !== 'string' || !explicitApp.trim()) {
31
+ return null;
32
+ }
33
+ const appKey = explicitApp.trim();
34
+ if (fs.existsSync(getIntegrationPath(appKey))) {
35
+ return { appKey };
36
+ }
37
+ return null;
38
+ }
39
+
40
+ /**
41
+ * @param {string} key
42
+ * @param {string[]} appNames
43
+ * @returns {{ appKey: string }|null}
44
+ * @throws {Error}
45
+ */
46
+ function resolveFromAppScan(key, appNames) {
47
+ const matches = appNames.filter(appName => appHasDatasourceIdentifier(appName, key));
48
+ if (matches.length === 1) {
49
+ return { appKey: matches[0] };
50
+ }
51
+ if (matches.length > 1) {
52
+ throw new Error(
53
+ `More than one app has this datasource; add --app <app>. Apps: ${matches.join(', ')}`
54
+ );
32
55
  }
33
- const schemaBasePath = config.externalIntegration?.schemaBasePath || './';
34
- const datasourceFiles = config.externalIntegration?.dataSources || [];
35
- for (const f of datasourceFiles) {
36
- if (!f || typeof f !== 'string') continue;
37
- const fullPath = path.isAbsolute(schemaBasePath)
38
- ? path.join(schemaBasePath, f)
39
- : path.join(appPath, schemaBasePath, f);
40
- if (!fs.existsSync(fullPath)) continue;
41
- try {
42
- const parsed = loadConfigFile(fullPath);
43
- if (parsed && parsed.key === datasourceKey) return true;
44
- } catch {
45
- // skip unreadable or invalid files
46
- }
56
+ return null;
57
+ }
58
+
59
+ /**
60
+ * @param {string} key
61
+ * @param {string[]} appNames
62
+ * @returns {{ appKey: string }|null}
63
+ */
64
+ function resolveFromPrefixOrSegments(key, appNames) {
65
+ const prefixApp = pickIntegrationAppForDatasourceIdentifier(key, appNames);
66
+ if (prefixApp && appHasDatasourceIdentifier(prefixApp, key)) {
67
+ return { appKey: prefixApp };
47
68
  }
48
- return false;
69
+ const segments = key.split('-');
70
+ if (segments.length < 2) {
71
+ return null;
72
+ }
73
+ const candidate = segments.slice(0, -1).join('-');
74
+ const candidatePath = getIntegrationPath(candidate);
75
+ if (fs.existsSync(candidatePath) && appHasDatasourceIdentifier(candidate, key)) {
76
+ return { appKey: candidate };
77
+ }
78
+ return null;
49
79
  }
50
80
 
81
+ const RESOLVE_APP_HINT =
82
+ 'Could not determine app context. Use --app <app>, a datasource key, a datasource JSON filename (e.g. myapp-datasource-contacts), or run from integration/<systemKey>/ directory.';
83
+
51
84
  /**
52
85
  * Resolve app key for a datasource: explicit --app, cwd, scan by key, or parse key convention.
53
86
  * @async
@@ -56,7 +89,6 @@ function appHasDatasourceKey(appKey, datasourceKey) {
56
89
  * @returns {Promise<{appKey: string}>} Resolved app key
57
90
  * @throws {Error} When app cannot be determined or multiple apps match
58
91
  */
59
- /* eslint-disable-next-line max-statements -- Resolution order: explicit, cwd, scan, parse */
60
92
  async function resolveAppKeyForDatasource(datasourceKey, explicitApp) {
61
93
  if (!datasourceKey || typeof datasourceKey !== 'string') {
62
94
  throw new Error('Datasource key is required');
@@ -66,41 +98,28 @@ async function resolveAppKeyForDatasource(datasourceKey, explicitApp) {
66
98
  throw new Error('Datasource key cannot be empty');
67
99
  }
68
100
 
69
- if (explicitApp && typeof explicitApp === 'string' && explicitApp.trim()) {
70
- const appPath = getIntegrationPath(explicitApp.trim());
71
- if (fs.existsSync(appPath)) {
72
- return { appKey: explicitApp.trim() };
73
- }
101
+ const explicit = resolveFromExplicitApp(key, explicitApp);
102
+ if (explicit) {
103
+ return explicit;
74
104
  }
75
105
 
76
106
  const fromCwd = resolveIntegrationAppKeyFromCwd();
77
- if (fromCwd && appHasDatasourceKey(fromCwd, key)) {
107
+ if (fromCwd && appHasDatasourceIdentifier(fromCwd, key)) {
78
108
  return { appKey: fromCwd };
79
109
  }
80
110
 
81
111
  const appNames = listIntegrationAppNames();
82
- const matches = appNames.filter(appName => appHasDatasourceKey(appName, key));
83
- if (matches.length === 1) {
84
- return { appKey: matches[0] };
85
- }
86
- if (matches.length > 1) {
87
- throw new Error(
88
- `More than one app has this datasource; add --app <app>. Apps: ${matches.join(', ')}`
89
- );
112
+ const scanned = resolveFromAppScan(key, appNames);
113
+ if (scanned) {
114
+ return scanned;
90
115
  }
91
116
 
92
- const segments = key.split('-');
93
- if (segments.length >= 2) {
94
- const candidate = segments.slice(0, -1).join('-');
95
- const candidatePath = getIntegrationPath(candidate);
96
- if (fs.existsSync(candidatePath) && appHasDatasourceKey(candidate, key)) {
97
- return { appKey: candidate };
98
- }
117
+ const inferred = resolveFromPrefixOrSegments(key, appNames);
118
+ if (inferred) {
119
+ return inferred;
99
120
  }
100
121
 
101
- throw new Error(
102
- 'Could not determine app context. Use --app <app> or run from integration/<systemKey>/ directory.'
103
- );
122
+ throw new Error(RESOLVE_APP_HINT);
104
123
  }
105
124
 
106
125
  module.exports = {
@@ -11,7 +11,7 @@ const chalk = require('chalk');
11
11
  const logger = require('../utils/logger');
12
12
  const { getIntegrationPath } = require('../utils/paths');
13
13
  const { resolveAppKeyForDatasource } = require('./resolve-app');
14
- const { infoLine } = require('../utils/cli-test-layout-chalk');
14
+ const { formatProgress, formatSuccessLine, infoLine } = require('../utils/cli-test-layout-chalk');
15
15
  const { runUnifiedDatasourceValidation } = require('./unified-validation-run');
16
16
  const { includeDebugForRequest } = require('../utils/validation-run-request');
17
17
  const { e2eShapeFromEnvelope } = require('../utils/datasource-test-run-legacy-adapter');
@@ -41,7 +41,9 @@ function buildUnifiedE2eRunOptions(options, timeoutMs, pk) {
41
41
  minRecordCount: options.minRecordCount,
42
42
  capabilityKey: options.capabilityKey,
43
43
  timeout: timeoutMs,
44
- sync: options.sync === true
44
+ sync: options.sync === true,
45
+ authConfig: options.authConfig,
46
+ dataplaneUrl: options.dataplaneUrl || options.dataplane
45
47
  };
46
48
  }
47
49
 
@@ -51,6 +53,25 @@ function logE2eDatasourceBanner(datasourceKey, verbose) {
51
53
  logger.log(infoLine(`🧪 Running E2E test for datasource: ${datasourceKey}`));
52
54
  }
53
55
 
56
+ /**
57
+ * Compact per-datasource progress (default test-e2e / system rollup when not --verbose).
58
+ * @param {string} datasourceKey
59
+ * @param {boolean} verbose
60
+ */
61
+ function logE2eDatasourceProgress(datasourceKey, verbose) {
62
+ if (verbose) return;
63
+ logger.log(formatProgress(`Running E2E validation for ${datasourceKey}…`));
64
+ }
65
+
66
+ /**
67
+ * @param {string} datasourceKey
68
+ * @param {boolean} verbose
69
+ */
70
+ function logE2eDatasourceValidated(datasourceKey, verbose) {
71
+ if (verbose) return;
72
+ logger.log(formatSuccessLine(`${datasourceKey} validated`));
73
+ }
74
+
54
75
  /**
55
76
  * Resolve primaryKeyValue for request body: string as-is, or read and parse JSON from @path
56
77
  * @param {string} [value]
@@ -174,6 +195,7 @@ async function runDatasourceTestE2E(datasourceKey, options = {}) {
174
195
  const cliWallStartedAt = Date.now();
175
196
  const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
176
197
  logE2eDatasourceBanner(datasourceKey, options.verbose);
198
+ logE2eDatasourceProgress(datasourceKey, options.verbose);
177
199
  const pk = await resolvePrimaryKeyValue(options.primaryKeyValue);
178
200
  const timeoutMs = resolveE2ePollTimeoutMs(options);
179
201
  const requestMeta = buildE2eRequestMeta(datasourceKey, options, pk);
@@ -184,6 +206,8 @@ async function runDatasourceTestE2E(datasourceKey, options = {}) {
184
206
 
185
207
  await throwIfUnifiedE2EBlocked(unifiedResult, appKey, options, requestMeta);
186
208
 
209
+ logE2eDatasourceValidated(datasourceKey, options.verbose);
210
+
187
211
  const display = e2eShapeFromEnvelope(unifiedResult.envelope);
188
212
  Object.assign(display, { datasourceTestRun: unifiedResult.envelope });
189
213