@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,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Resolve integration/.data/ paths for datasource load/export (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 fs = require('fs');
|
|
11
|
+
const { getCwdIntegrationRoot, getIntegrationRoot } = require('../utils/paths');
|
|
12
|
+
|
|
13
|
+
const DATA_DIR_NAME = '.data';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Absolute path to integration/.data (cwd integration root preferred).
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
function getIntegrationDataDir() {
|
|
20
|
+
const cwdRoot = getCwdIntegrationRoot();
|
|
21
|
+
const base = cwdRoot || getIntegrationRoot();
|
|
22
|
+
return path.join(base, DATA_DIR_NAME);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Entity suffix from datasource key and system key.
|
|
27
|
+
* @param {string} systemKey
|
|
28
|
+
* @param {string} datasourceKey
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
function computeEntitySuffix(systemKey, datasourceKey) {
|
|
32
|
+
const sys = String(systemKey || '').trim();
|
|
33
|
+
const key = String(datasourceKey || '').trim();
|
|
34
|
+
const prefix = `${sys}-`;
|
|
35
|
+
if (sys && key.startsWith(prefix)) {
|
|
36
|
+
return key.slice(prefix.length) || key;
|
|
37
|
+
}
|
|
38
|
+
const lastHyphen = key.lastIndexOf('-');
|
|
39
|
+
if (lastHyphen > 0) {
|
|
40
|
+
return key.slice(lastHyphen + 1);
|
|
41
|
+
}
|
|
42
|
+
return key;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Default fixture basename (without extension).
|
|
47
|
+
* @param {string} systemKey
|
|
48
|
+
* @param {string} entitySuffix
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
function defaultDataBasename(systemKey, entitySuffix) {
|
|
52
|
+
return `${systemKey}-data-${entitySuffix}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} dir
|
|
57
|
+
* @param {string} basename
|
|
58
|
+
* @param {'json'|'ndjson'} format
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
function dataFilePath(dir, basename, format) {
|
|
62
|
+
const ext = format === 'ndjson' ? '.ndjson' : '.json';
|
|
63
|
+
return path.join(dir, `${basename}${ext}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Infer format from file extension.
|
|
68
|
+
* @param {string} filePath
|
|
69
|
+
* @returns {'json'|'ndjson'}
|
|
70
|
+
*/
|
|
71
|
+
function inferFormatFromPath(filePath) {
|
|
72
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
73
|
+
if (ext === '.ndjson' || ext === '.jsonl') {
|
|
74
|
+
return 'ndjson';
|
|
75
|
+
}
|
|
76
|
+
return 'json';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Default integration/.data output path (file need not exist).
|
|
81
|
+
* @param {Object} opts
|
|
82
|
+
* @param {string} opts.systemKey
|
|
83
|
+
* @param {string} opts.entitySuffix
|
|
84
|
+
* @param {'json'|'ndjson'} [opts.format]
|
|
85
|
+
* @returns {{ filePath: string, format: 'json'|'ndjson' }}
|
|
86
|
+
*/
|
|
87
|
+
function resolveDefaultDataOutputPath(opts) {
|
|
88
|
+
const dir = getIntegrationDataDir();
|
|
89
|
+
const basename = defaultDataBasename(opts.systemKey, opts.entitySuffix);
|
|
90
|
+
const format = opts.format === 'ndjson' ? 'ndjson' : 'json';
|
|
91
|
+
return { filePath: dataFilePath(dir, basename, format), format };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Resolve data file: --file override or default integration/.data paths.
|
|
96
|
+
* @param {Object} opts
|
|
97
|
+
* @param {string} opts.systemKey
|
|
98
|
+
* @param {string} opts.entitySuffix
|
|
99
|
+
* @param {string} [opts.file] - CLI --file
|
|
100
|
+
* @param {'json'|'ndjson'} [opts.format] - explicit format
|
|
101
|
+
* @returns {{ filePath: string, format: 'json'|'ndjson' }}
|
|
102
|
+
*/
|
|
103
|
+
function resolveDataFilePath(opts) {
|
|
104
|
+
if (opts.file && String(opts.file).trim()) {
|
|
105
|
+
const filePath = path.resolve(String(opts.file).trim());
|
|
106
|
+
const format = opts.format || inferFormatFromPath(filePath);
|
|
107
|
+
return { filePath, format };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const dir = getIntegrationDataDir();
|
|
111
|
+
const basename = defaultDataBasename(opts.systemKey, opts.entitySuffix);
|
|
112
|
+
const jsonPath = dataFilePath(dir, basename, 'json');
|
|
113
|
+
const ndjsonPath = dataFilePath(dir, basename, 'ndjson');
|
|
114
|
+
|
|
115
|
+
if (opts.format === 'json' && fs.existsSync(jsonPath)) {
|
|
116
|
+
return { filePath: jsonPath, format: 'json' };
|
|
117
|
+
}
|
|
118
|
+
if (opts.format === 'ndjson' && fs.existsSync(ndjsonPath)) {
|
|
119
|
+
return { filePath: ndjsonPath, format: 'ndjson' };
|
|
120
|
+
}
|
|
121
|
+
if (!opts.format || opts.format === 'json') {
|
|
122
|
+
if (fs.existsSync(jsonPath)) {
|
|
123
|
+
return { filePath: jsonPath, format: 'json' };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!opts.format || opts.format === 'ndjson') {
|
|
127
|
+
if (fs.existsSync(ndjsonPath)) {
|
|
128
|
+
return { filePath: ndjsonPath, format: 'ndjson' };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
throw new Error(
|
|
133
|
+
`No local data file found. Expected one of:\n ${jsonPath}\n ${ndjsonPath}\n` +
|
|
134
|
+
'Use --file <path> to override.'
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = {
|
|
139
|
+
getIntegrationDataDir,
|
|
140
|
+
computeEntitySuffix,
|
|
141
|
+
defaultDataBasename,
|
|
142
|
+
inferFormatFromPath,
|
|
143
|
+
resolveDefaultDataOutputPath,
|
|
144
|
+
resolveDataFilePath
|
|
145
|
+
};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove saved datasource test debug JSON under integration/<app>/logs/.
|
|
3
|
+
* @fileoverview Local cleanup for test / test-integration / test-e2e / test-trust logs
|
|
4
|
+
* @author AI Fabrix Team
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const fsp = require('node:fs').promises;
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const { getIntegrationPath, listIntegrationAppNames } = require('../utils/paths');
|
|
13
|
+
const { isStructuralTestLogFileName } = require('./log-viewer');
|
|
14
|
+
const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
|
|
15
|
+
|
|
16
|
+
/** @type {readonly string[]} */
|
|
17
|
+
const LOG_CLEAN_TYPES = ['test', 'integration', 'e2e', 'trust', 'all'];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} [type]
|
|
21
|
+
* @returns {'test'|'integration'|'e2e'|'trust'|'all'}
|
|
22
|
+
*/
|
|
23
|
+
function normalizeLogCleanType(type) {
|
|
24
|
+
const raw = (type === undefined || type === null ? 'all' : String(type)).trim().toLowerCase();
|
|
25
|
+
if (raw === '' || raw === 'all') return 'all';
|
|
26
|
+
if (LOG_CLEAN_TYPES.includes(raw)) return raw;
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Invalid --type "${type}". Use: ${LOG_CLEAN_TYPES.join(', ')}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} fileName
|
|
34
|
+
* @param {'test'|'integration'|'e2e'|'trust'|'all'} type
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
37
|
+
function matchesLogCleanType(fileName, type) {
|
|
38
|
+
if (!fileName || !fileName.endsWith('.json')) return false;
|
|
39
|
+
if (type === 'all') return true;
|
|
40
|
+
if (type === 'test') return isStructuralTestLogFileName(fileName);
|
|
41
|
+
if (type === 'integration') {
|
|
42
|
+
return fileName.startsWith('test-integration-');
|
|
43
|
+
}
|
|
44
|
+
if (type === 'e2e') return fileName.startsWith('test-e2e-');
|
|
45
|
+
if (type === 'trust') return fileName.startsWith('test-trust-');
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} logsDir
|
|
51
|
+
* @param {'test'|'integration'|'e2e'|'trust'|'all'} type
|
|
52
|
+
* @returns {Promise<string[]>}
|
|
53
|
+
*/
|
|
54
|
+
async function listMatchingLogFiles(logsDir, type) {
|
|
55
|
+
let entries;
|
|
56
|
+
try {
|
|
57
|
+
entries = await fsp.readdir(logsDir, { withFileTypes: true });
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (err.code === 'ENOENT') return [];
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
return entries
|
|
63
|
+
.filter(e => e.isFile() && matchesLogCleanType(e.name, type))
|
|
64
|
+
.map(e => path.join(logsDir, e.name))
|
|
65
|
+
.sort((a, b) => a.localeCompare(b));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {string} appKey
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async function assertIntegrationAppExists(appKey) {
|
|
73
|
+
const appPath = getIntegrationPath(appKey);
|
|
74
|
+
try {
|
|
75
|
+
const st = await fsp.stat(appPath);
|
|
76
|
+
if (!st.isDirectory()) {
|
|
77
|
+
throw new Error(`Integration folder is not a directory: ${appPath}`);
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
if (err.code === 'ENOENT') {
|
|
81
|
+
throw new Error(`Integration folder not found: ${appPath}`);
|
|
82
|
+
}
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {Object} options
|
|
89
|
+
* @param {boolean} [options.all]
|
|
90
|
+
* @param {string} [options.app]
|
|
91
|
+
* @returns {Promise<string[]>}
|
|
92
|
+
*/
|
|
93
|
+
async function resolveTargetAppKeys(options) {
|
|
94
|
+
const useAll = Boolean(options.all);
|
|
95
|
+
const app = options.app && typeof options.app === 'string' ? options.app.trim() : '';
|
|
96
|
+
if (useAll && app) {
|
|
97
|
+
throw new Error('Use either --app <systemKey> or --all, not both');
|
|
98
|
+
}
|
|
99
|
+
if (!useAll && !app) {
|
|
100
|
+
throw new Error('Specify --app <systemKey> or --all');
|
|
101
|
+
}
|
|
102
|
+
if (useAll) {
|
|
103
|
+
return listIntegrationAppNames();
|
|
104
|
+
}
|
|
105
|
+
await assertIntegrationAppExists(app);
|
|
106
|
+
return [app];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {string[]} appKeys
|
|
111
|
+
* @param {'test'|'integration'|'e2e'|'trust'|'all'} type
|
|
112
|
+
* @returns {Promise<Array<{ appKey: string, logsDir: string, files: string[] }>>}
|
|
113
|
+
*/
|
|
114
|
+
async function collectLogFilesByApp(appKeys, type) {
|
|
115
|
+
const groups = [];
|
|
116
|
+
for (const appKey of appKeys) {
|
|
117
|
+
const logsDir = path.join(getIntegrationPath(appKey), 'logs');
|
|
118
|
+
const files = await listMatchingLogFiles(logsDir, type);
|
|
119
|
+
if (files.length > 0) {
|
|
120
|
+
groups.push({ appKey, logsDir, files });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return groups;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {Array<{ appKey: string, files: string[] }>} groups
|
|
128
|
+
* @param {boolean} dryRun
|
|
129
|
+
* @returns {Promise<{ removedCount: number, paths: string[] }>}
|
|
130
|
+
*/
|
|
131
|
+
async function removeCollectedFiles(groups, dryRun) {
|
|
132
|
+
const paths = groups.flatMap(g => g.files);
|
|
133
|
+
if (dryRun || paths.length === 0) {
|
|
134
|
+
return { removedCount: 0, paths };
|
|
135
|
+
}
|
|
136
|
+
await Promise.all(paths.map(filePath => fsp.unlink(filePath)));
|
|
137
|
+
return { removedCount: paths.length, paths };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {Object} options
|
|
142
|
+
* @param {boolean} options.dryRun
|
|
143
|
+
* @param {'test'|'integration'|'e2e'|'trust'|'all'} options.type
|
|
144
|
+
* @param {Array<{ appKey: string, files: string[] }>} groups
|
|
145
|
+
* @param {number} removedCount
|
|
146
|
+
*/
|
|
147
|
+
function printCleanLogsSummary(options, groups, removedCount) {
|
|
148
|
+
const totalFiles = groups.reduce((n, g) => n + g.files.length, 0);
|
|
149
|
+
const appCount = groups.length;
|
|
150
|
+
if (options.dryRun) {
|
|
151
|
+
if (totalFiles === 0) {
|
|
152
|
+
logger.log(chalk.gray('No matching log files found.'));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
logger.log('');
|
|
156
|
+
logger.log(
|
|
157
|
+
chalk.gray(
|
|
158
|
+
`Would remove ${totalFiles} file(s) from ${appCount} integration folder(s) (type: ${options.type}).`
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (totalFiles === 0) {
|
|
164
|
+
logger.log(chalk.gray('No matching log files to remove.'));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
logger.log('');
|
|
168
|
+
logger.log(formatSuccessLine(`Removed ${removedCount} log file(s) from ${appCount} integration folder(s).`));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {boolean} dryRun
|
|
173
|
+
* @param {Array<{ appKey: string, logsDir: string, files: string[] }>} groups
|
|
174
|
+
*/
|
|
175
|
+
function printCleanLogsFileList(dryRun, groups) {
|
|
176
|
+
const prefix = dryRun ? 'Would remove' : 'Removing';
|
|
177
|
+
for (const { appKey, files } of groups) {
|
|
178
|
+
logger.log('');
|
|
179
|
+
logger.log(chalk.bold.white(`${appKey}/logs/`));
|
|
180
|
+
for (const filePath of files) {
|
|
181
|
+
logger.log(chalk.gray(` ${prefix}: ${filePath}`));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @param {Object} result
|
|
188
|
+
* @param {boolean} result.dryRun
|
|
189
|
+
* @param {string} result.type
|
|
190
|
+
* @param {string[]} result.appKeys
|
|
191
|
+
* @param {string[]} result.paths
|
|
192
|
+
* @param {number} result.removedCount
|
|
193
|
+
*/
|
|
194
|
+
function printCleanLogsJson(result) {
|
|
195
|
+
const payload = {
|
|
196
|
+
dryRun: result.dryRun,
|
|
197
|
+
type: result.type,
|
|
198
|
+
appKeys: result.appKeys,
|
|
199
|
+
fileCount: result.paths.length,
|
|
200
|
+
paths: result.paths,
|
|
201
|
+
removedCount: result.removedCount
|
|
202
|
+
};
|
|
203
|
+
logger.log(JSON.stringify(payload, null, 2));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @async
|
|
208
|
+
* @param {Object} options
|
|
209
|
+
* @param {boolean} [options.all]
|
|
210
|
+
* @param {string} [options.app]
|
|
211
|
+
* @param {string} [options.type]
|
|
212
|
+
* @param {boolean} [options.dryRun]
|
|
213
|
+
* @param {boolean} [options.json]
|
|
214
|
+
* @returns {Promise<{ dryRun: boolean, type: string, appKeys: string[], paths: string[], removedCount: number }>}
|
|
215
|
+
*/
|
|
216
|
+
async function runCleanLogs(options = {}) {
|
|
217
|
+
const dryRun = Boolean(options.dryRun);
|
|
218
|
+
const json = Boolean(options.json);
|
|
219
|
+
const type = normalizeLogCleanType(options.type);
|
|
220
|
+
const appKeys = await resolveTargetAppKeys(options);
|
|
221
|
+
const groups = await collectLogFilesByApp(appKeys, type);
|
|
222
|
+
const { removedCount, paths } = await removeCollectedFiles(groups, dryRun);
|
|
223
|
+
const result = { dryRun, type, appKeys, paths, removedCount };
|
|
224
|
+
if (json) {
|
|
225
|
+
printCleanLogsJson(result);
|
|
226
|
+
} else {
|
|
227
|
+
printCleanLogsFileList(dryRun, groups);
|
|
228
|
+
printCleanLogsSummary({ dryRun, type }, groups, removedCount);
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
module.exports = {
|
|
234
|
+
LOG_CLEAN_TYPES,
|
|
235
|
+
normalizeLogCleanType,
|
|
236
|
+
matchesLogCleanType,
|
|
237
|
+
listMatchingLogFiles,
|
|
238
|
+
resolveTargetAppKeys,
|
|
239
|
+
collectLogFilesByApp,
|
|
240
|
+
runCleanLogs
|
|
241
|
+
};
|
|
@@ -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 {
|
|
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 (
|
|
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
|
-
|
|
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
|
+
};
|