@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.
- package/.cursor/rules/docs-rules.mdc +3 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
- package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
- package/jest.projects.js +44 -1
- package/lib/api/agent-metadata-validation.api.js +93 -0
- package/lib/api/audit-api-helpers.js +68 -0
- package/lib/api/audit.api.js +119 -0
- package/lib/api/auth-cache.api.js +44 -0
- package/lib/api/certificates.api.js +4 -4
- package/lib/api/credential.api.js +2 -2
- package/lib/api/dataplane-health.api.js +137 -0
- package/lib/api/dataplane-sync.api.js +27 -0
- package/lib/api/datasources-core.api.js +21 -18
- package/lib/api/datasources-extended.api.js +22 -22
- package/lib/api/dimensions.api.js +3 -0
- package/lib/api/external-systems.api.js +13 -13
- package/lib/api/governance-scenarios.api.js +39 -0
- package/lib/api/groups.api.js +79 -0
- package/lib/api/index.js +45 -2
- package/lib/api/pipeline.api.js +4 -4
- package/lib/api/protection.api.js +157 -0
- package/lib/api/records-bulk.api.js +30 -0
- package/lib/api/records-search-parse.js +49 -0
- package/lib/api/records-search.api.js +29 -0
- package/lib/api/types/agent-metadata-validation.types.js +67 -0
- package/lib/api/types/audit.types.js +72 -0
- package/lib/api/types/dataplane-health.types.js +27 -0
- package/lib/api/types/dimensions.types.js +2 -0
- package/lib/api/types/governance-scenarios.types.js +39 -0
- package/lib/api/types/protection.types.js +69 -0
- package/lib/api/types/records-bulk.types.js +35 -0
- package/lib/api/types/records-search.types.js +28 -0
- package/lib/api/user-groups.api.js +50 -0
- package/lib/api/users.api.js +95 -0
- package/lib/api/validation-run.api.js +3 -3
- package/lib/api/validation-runner.js +3 -3
- package/lib/api/wizard-platform.api.js +3 -3
- package/lib/api/wizard.api.js +21 -21
- package/lib/app/deploy.js +34 -19
- package/lib/cli/index.js +4 -2
- package/lib/cli/infra-guided.js +22 -3
- package/lib/cli/setup-app.help.js +50 -2
- package/lib/cli/setup-app.js +33 -24
- package/lib/cli/setup-app.test-commands.js +80 -21
- package/lib/cli/setup-auth.js +30 -3
- package/lib/cli/setup-external-system.js +37 -3
- package/lib/cli/setup-utility-help-after.js +45 -0
- package/lib/cli/setup-utility-resolve.js +2 -2
- package/lib/cli/setup-utility.js +99 -81
- package/lib/commands/auth-status-dataplane-version.js +231 -0
- package/lib/commands/auth-status-display.js +94 -0
- package/lib/commands/auth-status.js +70 -6
- package/lib/commands/credential-push.js +10 -5
- package/lib/commands/datasource-load-export-cli.js +178 -0
- package/lib/commands/datasource-test-trust-cli.js +114 -0
- package/lib/commands/datasource-unified-test-cli.js +25 -17
- package/lib/commands/datasource-unified-test-cli.options.js +18 -3
- package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
- package/lib/commands/datasource-verify-audit-cli.js +96 -0
- package/lib/commands/datasource.js +68 -1
- package/lib/commands/dimension-value.js +43 -13
- package/lib/commands/dimension.js +38 -7
- package/lib/commands/identity-apply-sync.js +131 -0
- package/lib/commands/identity-group-cli.js +146 -0
- package/lib/commands/identity-membership-role-cache.js +175 -0
- package/lib/commands/identity-shared.js +94 -0
- package/lib/commands/identity-user-cli.js +158 -0
- package/lib/commands/identity.js +43 -0
- package/lib/commands/login.js +46 -16
- package/lib/commands/protection-cli-leaves.js +286 -0
- package/lib/commands/protection.js +50 -0
- package/lib/commands/repair.js +13 -3
- package/lib/commands/test-e2e-external.js +40 -9
- package/lib/commands/test-governance-cli-options.js +47 -0
- package/lib/commands/test-governance-command-action.js +55 -0
- package/lib/commands/test-governance-external.js +88 -0
- package/lib/commands/test-trust-cli-options.js +32 -0
- package/lib/commands/test-trust-command-action.js +118 -0
- package/lib/commands/test-trust-external.js +127 -0
- package/lib/commands/upload.js +34 -13
- package/lib/commands/wizard-core.js +3 -0
- package/lib/core/config-device-dataplane.js +185 -0
- package/lib/datasource/agent-trust-debug-log.js +45 -0
- package/lib/datasource/agent-trust-fetch.js +115 -0
- package/lib/datasource/agent-trust-map.js +101 -0
- package/lib/datasource/agent-trust-mock.js +44 -0
- package/lib/datasource/agent-trust-run.js +93 -0
- package/lib/datasource/agent-trust-timeout.js +25 -0
- package/lib/datasource/audit-evidence-display.js +80 -0
- package/lib/datasource/audit-evidence-extract-debug.js +103 -0
- package/lib/datasource/audit-evidence-extract.js +177 -0
- package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
- package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
- package/lib/datasource/audit-evidence-poll.js +109 -0
- package/lib/datasource/audit-evidence-run.js +177 -0
- package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
- package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
- package/lib/datasource/audit-evidence-verify.js +108 -0
- package/lib/datasource/bulk-loader-run-batches.js +88 -0
- package/lib/datasource/bulk-loader-service-retry.js +52 -0
- package/lib/datasource/bulk-loader-service.js +127 -0
- package/lib/datasource/capability/run-capability-dimension.js +2 -1
- package/lib/datasource/datasource-exporter-resolve.js +82 -0
- package/lib/datasource/datasource-exporter-service.js +144 -0
- package/lib/datasource/datasource-identifier.js +148 -0
- package/lib/datasource/datasource-load-export-context.js +92 -0
- package/lib/datasource/integration-context.js +6 -4
- package/lib/datasource/local-data-paths.js +145 -0
- package/lib/datasource/log-cleaner.js +241 -0
- package/lib/datasource/log-viewer.js +111 -6
- package/lib/datasource/record-file-parser.js +85 -0
- package/lib/datasource/record-mapper.js +129 -0
- package/lib/datasource/resolve-app.js +74 -55
- package/lib/datasource/test-e2e.js +26 -2
- package/lib/datasource/unified-validation-run-resolve.js +45 -12
- package/lib/datasource/unified-validation-run.js +43 -17
- package/lib/datasource/validate.js +37 -20
- package/lib/external-system/test-system-level.js +25 -4
- package/lib/external-system/test.js +3 -1
- package/lib/governance/governance-pack-loader.js +70 -0
- package/lib/governance/governance-run-display.js +119 -0
- package/lib/identity/identity-apply-core.js +108 -0
- package/lib/identity/identity-apply-phases.js +244 -0
- package/lib/identity/identity-apply-service.js +77 -0
- package/lib/identity/identity-csv-parser.js +114 -0
- package/lib/internal/fs-real-sync.js +29 -1
- package/lib/protection/auth-context.js +45 -0
- package/lib/protection/convert-batch.js +90 -0
- package/lib/protection/load.js +41 -0
- package/lib/protection/paths.js +158 -0
- package/lib/protection/preflight-datasource-ready.js +46 -0
- package/lib/protection/protection-create-probe-lines.js +142 -0
- package/lib/protection/protection-create-probes.js +95 -0
- package/lib/protection/protection-create-scaffold.js +281 -0
- package/lib/protection/protection-display-helpers.js +445 -0
- package/lib/protection/protection-display.js +215 -0
- package/lib/protection/protection-preset-registry.js +97 -0
- package/lib/protection/report-exit.js +62 -0
- package/lib/protection/resolve.js +202 -0
- package/lib/protection/run-commands-validate.js +130 -0
- package/lib/protection/run-commands.js +216 -0
- package/lib/protection/run-protection-create-helpers.js +134 -0
- package/lib/protection/run-protection-create-probes-flow.js +43 -0
- package/lib/protection/run-protection-create.js +191 -0
- package/lib/protection/scope.js +22 -0
- package/lib/protection/sync-after-upload.js +64 -0
- package/lib/protection/upload-batch.js +127 -0
- package/lib/protection/validate-batch.js +113 -0
- package/lib/protection/validate-local.js +65 -0
- package/lib/resolvers/dimension-file.js +29 -4
- package/lib/schema/external-datasource.schema.json +47 -13
- package/lib/schema/flag-map-validation-run.json +2 -2
- package/lib/schema/protection.schema.json +374 -0
- package/lib/schema/type/credential-field-registry.json +636 -0
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/agent-trust-run-display.js +231 -0
- package/lib/utils/agent-trust-run-exit.js +59 -0
- package/lib/utils/agent-trust-wait-ui.js +94 -0
- package/lib/utils/cli-exit-codes.js +30 -0
- package/lib/utils/cli-sync-options.js +33 -0
- package/lib/utils/credential-push-ui.js +90 -0
- package/lib/utils/dataplane-cli-version-gate.js +202 -0
- package/lib/utils/dataplane-cli-version-help.js +74 -0
- package/lib/utils/datasource-export-display.js +137 -0
- package/lib/utils/datasource-load-display.js +174 -0
- package/lib/utils/datasource-load-export-ui.js +120 -0
- package/lib/utils/datasource-test-run-capability-scope.js +3 -3
- package/lib/utils/datasource-test-run-display.js +3 -3
- package/lib/utils/datasource-test-run-tty-log.js +5 -1
- package/lib/utils/help-builder.js +30 -24
- package/lib/utils/manifest-source-emit.js +22 -0
- package/lib/utils/semver-compare.js +127 -0
- package/lib/utils/upload-pipeline-ui.js +91 -0
- package/lib/utils/validation-poll-ui.js +14 -7
- package/lib/validation/wizard-config-validator.js +58 -19
- package/package.json +1 -1
- package/templates/applications/dataplane/env.template +13 -1
- package/templates/applications/dataplane/rbac.yaml +4 -0
- 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
|
|
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 =
|
|
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 (
|
|
54
|
+
if (datasourceIdentifierMatchesFile(datasourceKey, f, parsed)) return f;
|
|
53
55
|
} catch {
|
|
54
56
|
// skip unreadable or invalid files
|
|
55
57
|
}
|