@aifabrix/builder 2.45.0 → 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 (180) hide show
  1. package/.cursor/rules/docs-rules.mdc +3 -1
  2. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  3. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  4. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
  5. package/jest.projects.js +44 -1
  6. package/lib/api/agent-metadata-validation.api.js +93 -0
  7. package/lib/api/audit-api-helpers.js +68 -0
  8. package/lib/api/audit.api.js +119 -0
  9. package/lib/api/auth-cache.api.js +44 -0
  10. package/lib/api/certificates.api.js +4 -4
  11. package/lib/api/credential.api.js +2 -2
  12. package/lib/api/dataplane-health.api.js +137 -0
  13. package/lib/api/dataplane-sync.api.js +27 -0
  14. package/lib/api/datasources-core.api.js +21 -18
  15. package/lib/api/datasources-extended.api.js +22 -22
  16. package/lib/api/dimensions.api.js +3 -0
  17. package/lib/api/external-systems.api.js +13 -13
  18. package/lib/api/governance-scenarios.api.js +39 -0
  19. package/lib/api/groups.api.js +79 -0
  20. package/lib/api/index.js +45 -2
  21. package/lib/api/pipeline.api.js +4 -4
  22. package/lib/api/protection.api.js +157 -0
  23. package/lib/api/records-bulk.api.js +30 -0
  24. package/lib/api/records-search-parse.js +49 -0
  25. package/lib/api/records-search.api.js +29 -0
  26. package/lib/api/types/agent-metadata-validation.types.js +67 -0
  27. package/lib/api/types/audit.types.js +72 -0
  28. package/lib/api/types/dataplane-health.types.js +27 -0
  29. package/lib/api/types/dimensions.types.js +2 -0
  30. package/lib/api/types/governance-scenarios.types.js +39 -0
  31. package/lib/api/types/protection.types.js +69 -0
  32. package/lib/api/types/records-bulk.types.js +35 -0
  33. package/lib/api/types/records-search.types.js +28 -0
  34. package/lib/api/user-groups.api.js +50 -0
  35. package/lib/api/users.api.js +95 -0
  36. package/lib/api/validation-run.api.js +3 -3
  37. package/lib/api/validation-runner.js +3 -3
  38. package/lib/api/wizard-platform.api.js +3 -3
  39. package/lib/api/wizard.api.js +21 -21
  40. package/lib/app/deploy.js +34 -19
  41. package/lib/cli/index.js +4 -2
  42. package/lib/cli/infra-guided.js +22 -3
  43. package/lib/cli/setup-app.help.js +50 -2
  44. package/lib/cli/setup-app.js +33 -24
  45. package/lib/cli/setup-app.test-commands.js +80 -21
  46. package/lib/cli/setup-auth.js +30 -3
  47. package/lib/cli/setup-external-system.js +37 -3
  48. package/lib/cli/setup-utility-help-after.js +45 -0
  49. package/lib/cli/setup-utility-resolve.js +2 -2
  50. package/lib/cli/setup-utility.js +99 -81
  51. package/lib/commands/auth-status-dataplane-version.js +231 -0
  52. package/lib/commands/auth-status-display.js +94 -0
  53. package/lib/commands/auth-status.js +70 -6
  54. package/lib/commands/credential-push.js +10 -5
  55. package/lib/commands/datasource-load-export-cli.js +178 -0
  56. package/lib/commands/datasource-test-trust-cli.js +114 -0
  57. package/lib/commands/datasource-unified-test-cli.js +25 -17
  58. package/lib/commands/datasource-unified-test-cli.options.js +18 -3
  59. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
  60. package/lib/commands/datasource-verify-audit-cli.js +96 -0
  61. package/lib/commands/datasource.js +68 -1
  62. package/lib/commands/dimension-value.js +43 -13
  63. package/lib/commands/dimension.js +38 -7
  64. package/lib/commands/identity-apply-sync.js +131 -0
  65. package/lib/commands/identity-group-cli.js +146 -0
  66. package/lib/commands/identity-membership-role-cache.js +175 -0
  67. package/lib/commands/identity-shared.js +94 -0
  68. package/lib/commands/identity-user-cli.js +158 -0
  69. package/lib/commands/identity.js +43 -0
  70. package/lib/commands/login.js +46 -16
  71. package/lib/commands/protection-cli-leaves.js +286 -0
  72. package/lib/commands/protection.js +50 -0
  73. package/lib/commands/repair.js +13 -3
  74. package/lib/commands/test-e2e-external.js +40 -9
  75. package/lib/commands/test-governance-cli-options.js +47 -0
  76. package/lib/commands/test-governance-command-action.js +55 -0
  77. package/lib/commands/test-governance-external.js +88 -0
  78. package/lib/commands/test-trust-cli-options.js +32 -0
  79. package/lib/commands/test-trust-command-action.js +118 -0
  80. package/lib/commands/test-trust-external.js +127 -0
  81. package/lib/commands/upload.js +34 -13
  82. package/lib/commands/wizard-core.js +3 -0
  83. package/lib/core/config-device-dataplane.js +185 -0
  84. package/lib/datasource/agent-trust-debug-log.js +45 -0
  85. package/lib/datasource/agent-trust-fetch.js +115 -0
  86. package/lib/datasource/agent-trust-map.js +101 -0
  87. package/lib/datasource/agent-trust-mock.js +44 -0
  88. package/lib/datasource/agent-trust-run.js +93 -0
  89. package/lib/datasource/agent-trust-timeout.js +25 -0
  90. package/lib/datasource/audit-evidence-display.js +80 -0
  91. package/lib/datasource/audit-evidence-extract-debug.js +103 -0
  92. package/lib/datasource/audit-evidence-extract.js +177 -0
  93. package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
  94. package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
  95. package/lib/datasource/audit-evidence-poll.js +109 -0
  96. package/lib/datasource/audit-evidence-run.js +177 -0
  97. package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
  98. package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
  99. package/lib/datasource/audit-evidence-verify.js +108 -0
  100. package/lib/datasource/bulk-loader-run-batches.js +88 -0
  101. package/lib/datasource/bulk-loader-service-retry.js +52 -0
  102. package/lib/datasource/bulk-loader-service.js +127 -0
  103. package/lib/datasource/capability/run-capability-dimension.js +2 -1
  104. package/lib/datasource/datasource-exporter-resolve.js +82 -0
  105. package/lib/datasource/datasource-exporter-service.js +144 -0
  106. package/lib/datasource/datasource-identifier.js +148 -0
  107. package/lib/datasource/datasource-load-export-context.js +92 -0
  108. package/lib/datasource/integration-context.js +6 -4
  109. package/lib/datasource/local-data-paths.js +145 -0
  110. package/lib/datasource/log-cleaner.js +241 -0
  111. package/lib/datasource/log-viewer.js +111 -6
  112. package/lib/datasource/record-file-parser.js +85 -0
  113. package/lib/datasource/record-mapper.js +129 -0
  114. package/lib/datasource/resolve-app.js +74 -55
  115. package/lib/datasource/test-e2e.js +26 -2
  116. package/lib/datasource/unified-validation-run-resolve.js +45 -12
  117. package/lib/datasource/unified-validation-run.js +43 -17
  118. package/lib/datasource/validate.js +37 -20
  119. package/lib/external-system/test-system-level.js +25 -4
  120. package/lib/external-system/test.js +3 -1
  121. package/lib/governance/governance-pack-loader.js +70 -0
  122. package/lib/governance/governance-run-display.js +119 -0
  123. package/lib/identity/identity-apply-core.js +108 -0
  124. package/lib/identity/identity-apply-phases.js +244 -0
  125. package/lib/identity/identity-apply-service.js +77 -0
  126. package/lib/identity/identity-csv-parser.js +114 -0
  127. package/lib/internal/fs-real-sync.js +29 -1
  128. package/lib/protection/auth-context.js +45 -0
  129. package/lib/protection/convert-batch.js +90 -0
  130. package/lib/protection/load.js +41 -0
  131. package/lib/protection/paths.js +158 -0
  132. package/lib/protection/preflight-datasource-ready.js +46 -0
  133. package/lib/protection/protection-create-probe-lines.js +142 -0
  134. package/lib/protection/protection-create-probes.js +95 -0
  135. package/lib/protection/protection-create-scaffold.js +281 -0
  136. package/lib/protection/protection-display-helpers.js +445 -0
  137. package/lib/protection/protection-display.js +215 -0
  138. package/lib/protection/protection-preset-registry.js +97 -0
  139. package/lib/protection/report-exit.js +62 -0
  140. package/lib/protection/resolve.js +202 -0
  141. package/lib/protection/run-commands-validate.js +130 -0
  142. package/lib/protection/run-commands.js +216 -0
  143. package/lib/protection/run-protection-create-helpers.js +134 -0
  144. package/lib/protection/run-protection-create-probes-flow.js +43 -0
  145. package/lib/protection/run-protection-create.js +191 -0
  146. package/lib/protection/scope.js +22 -0
  147. package/lib/protection/sync-after-upload.js +64 -0
  148. package/lib/protection/upload-batch.js +127 -0
  149. package/lib/protection/validate-batch.js +113 -0
  150. package/lib/protection/validate-local.js +65 -0
  151. package/lib/resolvers/dimension-file.js +29 -4
  152. package/lib/schema/external-datasource.schema.json +47 -13
  153. package/lib/schema/flag-map-validation-run.json +2 -2
  154. package/lib/schema/protection.schema.json +374 -0
  155. package/lib/schema/type/credential-field-registry.json +636 -0
  156. package/lib/schema/wizard-config.schema.json +1 -1
  157. package/lib/utils/agent-trust-run-display.js +231 -0
  158. package/lib/utils/agent-trust-run-exit.js +59 -0
  159. package/lib/utils/agent-trust-wait-ui.js +94 -0
  160. package/lib/utils/cli-exit-codes.js +30 -0
  161. package/lib/utils/cli-sync-options.js +33 -0
  162. package/lib/utils/credential-push-ui.js +90 -0
  163. package/lib/utils/dataplane-cli-version-gate.js +202 -0
  164. package/lib/utils/dataplane-cli-version-help.js +74 -0
  165. package/lib/utils/datasource-export-display.js +137 -0
  166. package/lib/utils/datasource-load-display.js +174 -0
  167. package/lib/utils/datasource-load-export-ui.js +120 -0
  168. package/lib/utils/datasource-test-run-capability-scope.js +3 -3
  169. package/lib/utils/datasource-test-run-display.js +3 -3
  170. package/lib/utils/datasource-test-run-tty-log.js +5 -1
  171. package/lib/utils/help-builder.js +30 -24
  172. package/lib/utils/manifest-source-emit.js +22 -0
  173. package/lib/utils/semver-compare.js +127 -0
  174. package/lib/utils/upload-pipeline-ui.js +91 -0
  175. package/lib/utils/validation-poll-ui.js +14 -7
  176. package/lib/validation/wizard-config-validator.js +58 -19
  177. package/package.json +1 -1
  178. package/templates/applications/dataplane/env.template +13 -1
  179. package/templates/applications/dataplane/rbac.yaml +4 -0
  180. package/templates/external-system/README.md.hbs +56 -8
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @fileoverview Load local fixtures via dataplane bulk record sync (plan 144)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { resolveDataFilePath } = require('./local-data-paths');
10
+ const { parseRecordFile, estimatePayloadBytes } = require('./record-file-parser');
11
+ const { normalizeRecordsForBulk } = require('./record-mapper');
12
+ const { resolveLoadExportContext } = require('./datasource-load-export-context');
13
+ const { uploadRecordBatches } = require('./bulk-loader-run-batches');
14
+ const { runWithDatasourceLoadExportSpinner } = require('../utils/datasource-load-export-ui');
15
+
16
+ const DEFAULT_BATCH_SIZE = 100;
17
+
18
+ /**
19
+ * @param {Object[]} records
20
+ * @param {number} batchSize
21
+ * @returns {Object[][]}
22
+ */
23
+ function chunkRecords(records, batchSize) {
24
+ const size = Math.max(1, batchSize);
25
+ const chunks = [];
26
+ for (let i = 0; i < records.length; i += size) {
27
+ chunks.push(records.slice(i, i + size));
28
+ }
29
+ return chunks;
30
+ }
31
+
32
+ /**
33
+ * @param {Object} params
34
+ * @returns {Object}
35
+ */
36
+ function buildLoadResultBase(params) {
37
+ const { ctx, filePath, format, records, batchSize, syncType, dryRun } = params;
38
+ return {
39
+ datasourceKey: ctx.datasourceKey,
40
+ systemKey: ctx.systemKey,
41
+ appKey: ctx.appKey,
42
+ dryRun,
43
+ file: filePath,
44
+ format,
45
+ recordCount: records.length,
46
+ batchSize,
47
+ syncType,
48
+ estimatedPayloadBytes: estimatePayloadBytes(records),
49
+ batches: [],
50
+ totals: { insertedCount: 0, updatedCount: 0, failedCount: 0 }
51
+ };
52
+ }
53
+
54
+ /**
55
+ * @async
56
+ * @param {Object} params
57
+ * @returns {Promise<Object>}
58
+ */
59
+ async function executeDatasourceLoadWork(params) {
60
+ const { ctx, options, batchSize, syncType, dryRun } = params;
61
+ const { filePath, format } = resolveDataFilePath({
62
+ systemKey: ctx.systemKey,
63
+ entitySuffix: ctx.entitySuffix,
64
+ file: options.file,
65
+ format: options.format
66
+ });
67
+
68
+ const raw = parseRecordFile(filePath, format);
69
+ const records = normalizeRecordsForBulk(raw, ctx.datasource);
70
+ const result = buildLoadResultBase({
71
+ ctx,
72
+ filePath,
73
+ format,
74
+ records,
75
+ batchSize,
76
+ syncType,
77
+ dryRun
78
+ });
79
+
80
+ if (dryRun) {
81
+ return { ...result, context: ctx };
82
+ }
83
+
84
+ const { batches, totals } = await uploadRecordBatches({
85
+ chunks: chunkRecords(records, batchSize),
86
+ syncType,
87
+ dataplaneUrl: ctx.dataplaneUrl,
88
+ datasourceKey: ctx.datasourceKey,
89
+ authConfig: ctx.authConfig
90
+ });
91
+
92
+ return { ...result, batches, totals, context: ctx };
93
+ }
94
+
95
+ /**
96
+ * @async
97
+ * @param {string} datasourceKey
98
+ * @param {Object} options
99
+ * @returns {Promise<Object>}
100
+ */
101
+ async function runDatasourceLoad(datasourceKey, options = {}) {
102
+ const ctx = await resolveLoadExportContext(datasourceKey, options);
103
+ const batchSize = options.batchSize
104
+ ? parseInt(String(options.batchSize), 10)
105
+ : DEFAULT_BATCH_SIZE;
106
+ const syncType = options.syncType || 'incremental';
107
+ const dryRun = options.dryRun === true;
108
+ const startedAt = Date.now();
109
+
110
+ const payload = await runWithDatasourceLoadExportSpinner(
111
+ () => executeDatasourceLoadWork({ ctx, options, batchSize, syncType, dryRun }),
112
+ {
113
+ operation: dryRun ? 'load-dry-run' : 'load',
114
+ datasourceKey: ctx.datasourceKey,
115
+ json: options.json === true
116
+ }
117
+ );
118
+
119
+ return { ...payload, durationMs: Date.now() - startedAt };
120
+ }
121
+
122
+ module.exports = {
123
+ runDatasourceLoad,
124
+ chunkRecords,
125
+ buildLoadResultBase,
126
+ DEFAULT_BATCH_SIZE
127
+ };
@@ -43,7 +43,8 @@ async function maybeLoadDimensionCatalogKeys() {
43
43
  page: 1,
44
44
  pageSize: 500
45
45
  });
46
- const items = res?.data?.items || res?.data?.data?.items || res?.items || [];
46
+ const raw = res?.data?.items ?? res?.data?.data ?? res?.items;
47
+ const items = Array.isArray(raw) ? raw : raw?.items || [];
47
48
  const keys = new Set(
48
49
  Array.isArray(items) ? items.map((d) => String(d?.key || '').trim()).filter(Boolean) : []
49
50
  );
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @fileoverview Export path and search body helpers (plan 144)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const path = require('path');
10
+ const { resolveDefaultDataOutputPath, inferFormatFromPath } = require('./local-data-paths');
11
+
12
+ /**
13
+ * @param {string|undefined} raw
14
+ * @returns {Object|undefined}
15
+ */
16
+ function parseFilterJson(raw) {
17
+ if (!raw || typeof raw !== 'string' || !raw.trim()) {
18
+ return undefined;
19
+ }
20
+ try {
21
+ const parsed = JSON.parse(raw);
22
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
23
+ return parsed;
24
+ }
25
+ throw new Error('filter must be a JSON object');
26
+ } catch (err) {
27
+ throw new Error(`Invalid --filter JSON: ${err.message}`);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * @param {string|undefined} raw
33
+ * @returns {string[]|undefined}
34
+ */
35
+ function parseFieldsCsv(raw) {
36
+ if (!raw || typeof raw !== 'string' || !raw.trim()) {
37
+ return undefined;
38
+ }
39
+ return raw
40
+ .split(',')
41
+ .map(s => s.trim())
42
+ .filter(Boolean);
43
+ }
44
+
45
+ /**
46
+ * @param {Object} opts
47
+ * @returns {{ outputPath: string, format: 'json'|'ndjson', limit: number, intent: string, filters: Object|undefined, fields: string[]|undefined }}
48
+ */
49
+ function resolveExportOptions(opts) {
50
+ let limit = opts.limit ? parseInt(String(opts.limit), 10) : 1000;
51
+ if (Number.isNaN(limit) || limit < 1) {
52
+ limit = 1000;
53
+ }
54
+ limit = Math.min(limit, 10000);
55
+
56
+ const intent = opts.intent || 'validation';
57
+ const filters = parseFilterJson(opts.filter);
58
+ const fields = parseFieldsCsv(opts.fields);
59
+
60
+ let outputPath;
61
+ let format = opts.format;
62
+ if (opts.file && String(opts.file).trim()) {
63
+ outputPath = path.resolve(String(opts.file).trim());
64
+ format = format || inferFormatFromPath(outputPath);
65
+ } else {
66
+ const resolved = resolveDefaultDataOutputPath({
67
+ systemKey: opts.systemKey,
68
+ entitySuffix: opts.entitySuffix,
69
+ format: format || 'json'
70
+ });
71
+ outputPath = resolved.filePath;
72
+ format = opts.format || resolved.format;
73
+ }
74
+
75
+ return { outputPath, format, limit, intent, filters, fields };
76
+ }
77
+
78
+ module.exports = {
79
+ parseFilterJson,
80
+ parseFieldsCsv,
81
+ resolveExportOptions
82
+ };
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @fileoverview Export governed records via Records Search (plan 144)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { searchRecords } = require('../api/records-search.api');
12
+ const { resolveLoadExportContext } = require('./datasource-load-export-context');
13
+ const { resolveExportOptions } = require('./datasource-exporter-resolve');
14
+ const { runWithDatasourceLoadExportSpinner } = require('../utils/datasource-load-export-ui');
15
+
16
+ const DEFAULT_LIMIT = 1000;
17
+ const MAX_LIMIT = 10000;
18
+
19
+ /**
20
+ * @param {Object[]} data
21
+ * @param {string[]|undefined} fields
22
+ * @returns {Object[]}
23
+ */
24
+ function projectExportRows(data, fields) {
25
+ if (!fields || !fields.length) {
26
+ return data;
27
+ }
28
+ return data.map(row => {
29
+ if (!row || typeof row !== 'object') {
30
+ return row;
31
+ }
32
+ const meta =
33
+ row.metadata && typeof row.metadata === 'object' && !Array.isArray(row.metadata)
34
+ ? row.metadata
35
+ : row;
36
+ const out = {};
37
+ fields.forEach(field => {
38
+ if (Object.prototype.hasOwnProperty.call(meta, field)) {
39
+ out[field] = meta[field];
40
+ }
41
+ });
42
+ return out;
43
+ });
44
+ }
45
+
46
+ /**
47
+ * @param {string} filePath
48
+ * @param {'json'|'ndjson'} format
49
+ * @param {Object[]} rows
50
+ */
51
+ function writeExportFile(filePath, format, rows) {
52
+ const dir = path.dirname(filePath);
53
+ fs.mkdirSync(dir, { recursive: true });
54
+ if (format === 'ndjson') {
55
+ const body = rows.map(row => JSON.stringify(row)).join('\n');
56
+ fs.writeFileSync(filePath, body.length ? `${body}\n` : '', 'utf8');
57
+ return;
58
+ }
59
+ fs.writeFileSync(filePath, `${JSON.stringify(rows, null, 2)}\n`, 'utf8');
60
+ }
61
+
62
+ /**
63
+ * @async
64
+ * @param {Object} ctx
65
+ * @param {Object} resolved
66
+ * @returns {Promise<Object>}
67
+ */
68
+ async function executeDatasourceExportWork(ctx, resolved) {
69
+ const searchBody = {
70
+ intent: resolved.intent,
71
+ datasourceKeys: [ctx.datasourceKey],
72
+ searchMode: 'full',
73
+ limit: resolved.limit
74
+ };
75
+ if (resolved.filters) {
76
+ searchBody.filters = resolved.filters;
77
+ }
78
+
79
+ const response = await searchRecords(ctx.dataplaneUrl, ctx.authConfig, searchBody);
80
+ if (!response || response.success === false) {
81
+ const msg = response?.formattedError || response?.error || 'Records search request failed';
82
+ throw new Error(msg);
83
+ }
84
+
85
+ const data = Array.isArray(response.data) ? response.data : [];
86
+ const projected = projectExportRows(data, resolved.fields);
87
+ writeExportFile(resolved.outputPath, resolved.format, projected);
88
+
89
+ const meta = response.meta && typeof response.meta === 'object' ? response.meta : {};
90
+ const excluded = meta.excluded && typeof meta.excluded === 'object' ? meta.excluded : {};
91
+
92
+ return {
93
+ datasourceKey: ctx.datasourceKey,
94
+ systemKey: ctx.systemKey,
95
+ appKey: ctx.appKey,
96
+ outputFile: resolved.outputPath,
97
+ format: resolved.format,
98
+ recordCount: projected.length,
99
+ limit: resolved.limit,
100
+ intent: resolved.intent,
101
+ filters: resolved.filters || null,
102
+ fields: resolved.fields || null,
103
+ meta: { excluded, auditRef: meta.auditRef },
104
+ excludedAbac: Number(excluded.abac) || 0,
105
+ excludedFilter: Number(excluded.filter) || 0,
106
+ hitLimitCap: projected.length >= resolved.limit,
107
+ context: ctx
108
+ };
109
+ }
110
+
111
+ /**
112
+ * @async
113
+ * @param {string} datasourceKey
114
+ * @param {Object} options
115
+ * @returns {Promise<Object>}
116
+ */
117
+ async function runDatasourceExport(datasourceKey, options = {}) {
118
+ const ctx = await resolveLoadExportContext(datasourceKey, options);
119
+ const resolved = resolveExportOptions({
120
+ ...options,
121
+ systemKey: ctx.systemKey,
122
+ entitySuffix: ctx.entitySuffix
123
+ });
124
+ const startedAt = Date.now();
125
+
126
+ const payload = await runWithDatasourceLoadExportSpinner(
127
+ () => executeDatasourceExportWork(ctx, resolved),
128
+ {
129
+ operation: 'export',
130
+ datasourceKey: ctx.datasourceKey,
131
+ json: options.json === true
132
+ }
133
+ );
134
+
135
+ return { ...payload, durationMs: Date.now() - startedAt };
136
+ }
137
+
138
+ module.exports = {
139
+ runDatasourceExport,
140
+ projectExportRows,
141
+ writeExportFile,
142
+ DEFAULT_LIMIT,
143
+ MAX_LIMIT
144
+ };
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @fileoverview Match datasource CLI identifiers to integration files (key or filename stem).
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const path = require('path');
10
+ const { getIntegrationPath } = require('../utils/paths');
11
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
12
+ const { loadConfigFile } = require('../utils/config-format');
13
+
14
+ /**
15
+ * @param {string} fileRef
16
+ * @returns {string}
17
+ */
18
+ function datasourceFileStem(fileRef) {
19
+ return path.basename(String(fileRef || ''), path.extname(String(fileRef || '')));
20
+ }
21
+
22
+ /**
23
+ * True when identifier matches parsed `key` or the datasource JSON filename (with or without .json).
24
+ * @param {string} identifier
25
+ * @param {string} fileRef - Manifest-relative datasource path
26
+ * @param {Object|null} parsed
27
+ * @returns {boolean}
28
+ */
29
+ function datasourceIdentifierMatchesFile(identifier, fileRef, parsed) {
30
+ const id = String(identifier || '').trim();
31
+ if (!id || !parsed || typeof parsed !== 'object') {
32
+ return false;
33
+ }
34
+ if (parsed.key === id) {
35
+ return true;
36
+ }
37
+ const stem = datasourceFileStem(fileRef);
38
+ if (stem === id) {
39
+ return true;
40
+ }
41
+ if (`${stem}.json` === id.toLowerCase()) {
42
+ return true;
43
+ }
44
+ return false;
45
+ }
46
+
47
+ /**
48
+ * Pick integration app folder for a datasource identifier (longest matching prefix wins).
49
+ * @param {string} identifier
50
+ * @param {string[]} appNames
51
+ * @returns {string|null}
52
+ */
53
+ function pickIntegrationAppForDatasourceIdentifier(identifier, appNames) {
54
+ const id = String(identifier || '').trim();
55
+ if (!id) {
56
+ return null;
57
+ }
58
+ const matches = appNames.filter(app => id === app || id.startsWith(`${app}-`));
59
+ if (matches.length === 0) {
60
+ return null;
61
+ }
62
+ matches.sort((a, b) => b.length - a.length);
63
+ return matches[0];
64
+ }
65
+
66
+ /**
67
+ * List manifest datasource files for an integration app.
68
+ * @param {string} appKey
69
+ * @returns {{ appPath: string, schemaBasePath: string, datasourceFiles: string[] }|null}
70
+ */
71
+ function listDatasourceFilesForApp(appKey) {
72
+ const appPath = getIntegrationPath(appKey);
73
+ let config;
74
+ try {
75
+ const configPath = resolveApplicationConfigPath(appPath);
76
+ config = loadConfigFile(configPath);
77
+ } catch {
78
+ return null;
79
+ }
80
+ const schemaBasePath = config.externalIntegration?.schemaBasePath || './';
81
+ const datasourceFiles = config.externalIntegration?.dataSources || [];
82
+ return { appPath, schemaBasePath, datasourceFiles };
83
+ }
84
+
85
+ /**
86
+ * @param {string} appPath
87
+ * @param {string} schemaBasePath
88
+ * @param {string} fileRef
89
+ * @returns {string}
90
+ */
91
+ function resolveDatasourceFilePath(appPath, schemaBasePath, fileRef) {
92
+ if (path.isAbsolute(schemaBasePath)) {
93
+ return path.join(schemaBasePath, fileRef);
94
+ }
95
+ return path.join(appPath, schemaBasePath, fileRef);
96
+ }
97
+
98
+ /**
99
+ * Collect datasource file refs in one app that match the identifier.
100
+ * @param {string} appKey
101
+ * @param {string} identifier
102
+ * @returns {string[]}
103
+ */
104
+ function collectDatasourceFileRefsMatchingIdentifier(appKey, identifier) {
105
+ const listed = listDatasourceFilesForApp(appKey);
106
+ if (!listed) {
107
+ return [];
108
+ }
109
+ const fsSync = require('fs');
110
+ const hits = [];
111
+ for (const fileRef of listed.datasourceFiles) {
112
+ if (!fileRef || typeof fileRef !== 'string') {
113
+ continue;
114
+ }
115
+ const fullPath = resolveDatasourceFilePath(listed.appPath, listed.schemaBasePath, fileRef);
116
+ if (!fsSync.existsSync(fullPath)) {
117
+ continue;
118
+ }
119
+ try {
120
+ const parsed = loadConfigFile(fullPath);
121
+ if (datasourceIdentifierMatchesFile(identifier, fileRef, parsed)) {
122
+ hits.push(fileRef);
123
+ }
124
+ } catch {
125
+ // skip unreadable or invalid files
126
+ }
127
+ }
128
+ return hits;
129
+ }
130
+
131
+ /**
132
+ * @param {string} appKey
133
+ * @param {string} identifier
134
+ * @returns {boolean}
135
+ */
136
+ function appHasDatasourceIdentifier(appKey, identifier) {
137
+ return collectDatasourceFileRefsMatchingIdentifier(appKey, identifier).length > 0;
138
+ }
139
+
140
+ module.exports = {
141
+ datasourceFileStem,
142
+ datasourceIdentifierMatchesFile,
143
+ pickIntegrationAppForDatasourceIdentifier,
144
+ listDatasourceFilesForApp,
145
+ resolveDatasourceFilePath,
146
+ collectDatasourceFileRefsMatchingIdentifier,
147
+ appHasDatasourceIdentifier
148
+ };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * @fileoverview Shared context resolution for datasource load/export (plan 144)
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { getConfig } = require('../core/config');
10
+ const { setupIntegrationTestAuth } = require('../external-system/test-auth');
11
+ const { resolveAppKeyForDatasource } = require('./resolve-app');
12
+ const { getSystemKeyFromAppKey } = require('./integration-context');
13
+ const { loadDatasourceForApp } = require('./unified-validation-run-resolve');
14
+ const { findDatasourceFileByKey } = require('./integration-context');
15
+ const { getIntegrationPath } = require('../utils/paths');
16
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
17
+ const { computeEntitySuffix } = require('./local-data-paths');
18
+
19
+ /**
20
+ * @param {string} appKey
21
+ * @throws {Error}
22
+ */
23
+ async function assertExternalIntegrationApp(appKey) {
24
+ const pathsUtil = require('../utils/paths');
25
+ const appType = await pathsUtil.detectAppType(appKey).catch(() => null);
26
+ if (!appType || appType.baseDir !== 'integration') {
27
+ throw new Error(
28
+ 'datasource load/export apply to external integration folders only (integration/<systemKey>/).'
29
+ );
30
+ }
31
+ }
32
+
33
+ /**
34
+ * @async
35
+ * @param {string} datasourceKey
36
+ * @param {Object} options
37
+ * @returns {Promise<Object>}
38
+ */
39
+ async function resolveLoadExportContext(datasourceKey, options = {}) {
40
+ const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
41
+ await assertExternalIntegrationApp(appKey);
42
+ const systemKey = await getSystemKeyFromAppKey(appKey);
43
+ const {
44
+ datasource,
45
+ datasourcePath,
46
+ datasourceKey: canonicalDatasourceKey
47
+ } = loadDatasourceForApp(appKey, datasourceKey, findDatasourceFileByKey);
48
+ const appPath = getIntegrationPath(appKey);
49
+ const manifestPath = resolveApplicationConfigPath(appPath);
50
+ const entitySuffix = computeEntitySuffix(systemKey, canonicalDatasourceKey);
51
+
52
+ let authConfig = options.authConfig;
53
+ let dataplaneUrl = options.dataplaneUrl || options.dataplane;
54
+ if (!authConfig || !dataplaneUrl) {
55
+ const configObj = await getConfig();
56
+ const auth = await setupIntegrationTestAuth(
57
+ appKey,
58
+ { environment: options.environment || options.env, dataplane: options.dataplane },
59
+ configObj
60
+ );
61
+ authConfig = auth.authConfig;
62
+ dataplaneUrl = auth.dataplaneUrl;
63
+ }
64
+
65
+ return {
66
+ appKey,
67
+ systemKey,
68
+ datasourceKey: canonicalDatasourceKey,
69
+ datasource,
70
+ datasourcePath,
71
+ manifestPath,
72
+ entitySuffix,
73
+ authConfig,
74
+ dataplaneUrl,
75
+ environment: options.environment || options.env
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Gray manifest label segment for TTY (integration/<appKey>).
81
+ * @param {string} appKey
82
+ * @returns {string}
83
+ */
84
+ function integrationManifestLabel(appKey) {
85
+ return `integration/${appKey}`;
86
+ }
87
+
88
+ module.exports = {
89
+ assertExternalIntegrationApp,
90
+ resolveLoadExportContext,
91
+ integrationManifestLabel
92
+ };
@@ -9,6 +9,10 @@ const fs = require('fs').promises;
9
9
  const { getIntegrationPath } = require('../utils/paths');
10
10
  const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
11
11
  const { loadConfigFile } = require('../utils/config-format');
12
+ const {
13
+ datasourceIdentifierMatchesFile,
14
+ resolveDatasourceFilePath
15
+ } = require('./datasource-identifier');
12
16
 
13
17
  /**
14
18
  * @param {string} appKey - Integration app key
@@ -43,13 +47,11 @@ function findDatasourceFileByKey(appPath, schemaBasePath, datasourceFiles, datas
43
47
  const fsSync = require('fs');
44
48
  for (const f of datasourceFiles) {
45
49
  if (!f || typeof f !== 'string') continue;
46
- const fullPath = path.isAbsolute(schemaBasePath)
47
- ? path.join(schemaBasePath, f)
48
- : path.join(appPath, schemaBasePath, f);
50
+ const fullPath = resolveDatasourceFilePath(appPath, schemaBasePath, f);
49
51
  if (!fsSync.existsSync(fullPath)) continue;
50
52
  try {
51
53
  const parsed = loadConfigFile(fullPath);
52
- if (parsed && parsed.key === datasourceKey) return f;
54
+ if (datasourceIdentifierMatchesFile(datasourceKey, f, parsed)) return f;
53
55
  } catch {
54
56
  // skip unreadable or invalid files
55
57
  }