@aifabrix/builder 2.45.0 → 2.46.0
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/cli-layout.mdc +2 -0
- package/.cursor/rules/cli-product-neutral.mdc +43 -0
- package/.cursor/rules/docs-rules.mdc +3 -1
- package/.cursor/rules/project-rules.mdc +1 -0
- package/README.md +43 -17
- package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
- package/jest.config.default.js +2 -1
- package/jest.config.isolated.js +2 -1
- package/jest.global-live-fabrix-hooks.js +36 -0
- package/jest.isolated-projects.js +130 -0
- package/jest.projects.js +69 -121
- package/lib/api/agent-metadata-validation.api.js +93 -0
- package/lib/api/api-response-errors.js +124 -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-scenario-pack.api.js +159 -0
- package/lib/api/governance-scenarios.api.js +39 -0
- package/lib/api/groups.api.js +79 -0
- package/lib/api/index.js +88 -5
- package/lib/api/lifecycle.api.js +77 -0
- package/lib/api/pipeline.api.js +7 -5
- package/lib/api/protection.api.js +157 -0
- package/lib/api/records-bulk.api.js +43 -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 +5 -3
- package/lib/api/wizard-platform.api.js +3 -3
- package/lib/api/wizard.api.js +21 -21
- package/lib/app/deploy-config.js +4 -1
- package/lib/app/deploy.js +34 -19
- package/lib/app/down.js +20 -1
- package/lib/app/push.js +4 -5
- package/lib/app/register.js +7 -4
- package/lib/app/run-env-compose.js +84 -30
- package/lib/build/index.js +5 -0
- package/lib/certification/sync-system-certification.js +13 -6
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided-auth.js +109 -0
- package/lib/cli/infra-guided.js +91 -69
- package/lib/cli/setup-app.help.js +19 -2
- package/lib/cli/setup-app.js +58 -23
- package/lib/cli/setup-app.test-commands.js +28 -21
- package/lib/cli/setup-auth.js +55 -3
- package/lib/cli/setup-certification-commands.js +147 -0
- package/lib/cli/setup-certification.help.js +141 -0
- package/lib/cli/setup-environment.js +19 -2
- package/lib/cli/setup-external-system-help.js +62 -0
- package/lib/cli/setup-external-system.js +62 -6
- package/lib/cli/setup-infra.js +9 -1
- package/lib/cli/setup-platform.js +6 -1
- package/lib/cli/setup-secrets.js +33 -1
- package/lib/cli/setup-utility-help-after.js +65 -0
- package/lib/cli/setup-utility-repair.js +198 -0
- package/lib/cli/setup-utility-resolve.js +42 -6
- package/lib/cli/setup-utility-validate.js +197 -0
- package/lib/cli/setup-utility.js +41 -258
- package/lib/commands/auth-status-client-tokens.js +214 -0
- package/lib/commands/auth-status-dataplane-version.js +269 -0
- package/lib/commands/auth-status-display.js +94 -0
- package/lib/commands/auth-status.js +111 -53
- 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 +118 -0
- package/lib/commands/datasource-unified-test-cli.js +31 -20
- package/lib/commands/datasource-unified-test-cli.options.js +22 -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/dev-show-display.js +8 -1
- package/lib/commands/dimension-value.js +43 -13
- package/lib/commands/dimension.js +38 -7
- package/lib/commands/governance-cli-shared.js +94 -0
- package/lib/commands/governance-command-action.js +132 -0
- package/lib/commands/governance-download-external.js +34 -0
- package/lib/commands/governance-generate-external.js +71 -0
- package/lib/commands/governance-upload-external.js +36 -0
- package/lib/commands/governance-verify-external.js +64 -0
- package/lib/commands/governance.js +144 -0
- 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/lifecycle-command-action.js +123 -0
- package/lib/commands/lifecycle-external.js +268 -0
- package/lib/commands/login.js +56 -17
- package/lib/commands/protection-cli-leaves.js +286 -0
- package/lib/commands/protection.js +50 -0
- package/lib/commands/repair-auth-apply.js +127 -0
- package/lib/commands/repair-changed-files.js +41 -0
- package/lib/commands/repair-datasource-keys.js +4 -1
- package/lib/commands/repair-datasource-run.js +59 -0
- package/lib/commands/repair-datasource-test-payload.js +259 -0
- package/lib/commands/repair-datasource.js +1 -51
- package/lib/commands/repair-env-template.js +64 -13
- package/lib/commands/repair-external-run.js +216 -0
- package/lib/commands/repair-openapi-sync.js +23 -0
- package/lib/commands/repair-persist.js +4 -0
- package/lib/commands/repair-rbac.js +52 -1
- package/lib/commands/repair-steps.js +145 -0
- package/lib/commands/repair.js +29 -286
- package/lib/commands/secrets-get.js +184 -0
- package/lib/commands/secrets-remove-all.js +3 -1
- package/lib/commands/setup-image-refresh.js +146 -0
- package/lib/commands/setup-modes.js +158 -162
- package/lib/commands/setup-platform-auth.js +76 -0
- package/lib/commands/setup-prompts-admin.js +244 -0
- package/lib/commands/setup-prompts-platform-mode.js +67 -0
- package/lib/commands/setup-prompts-validators.js +36 -0
- package/lib/commands/setup-prompts.js +24 -76
- package/lib/commands/setup-run-context.js +45 -0
- package/lib/commands/setup-spinners.js +39 -0
- package/lib/commands/setup.js +27 -5
- package/lib/commands/teardown.js +39 -0
- package/lib/commands/test-e2e-external.js +61 -14
- package/lib/commands/test-governance-cli-options.js +47 -0
- package/lib/commands/test-governance-external.js +67 -0
- package/lib/commands/test-trust-cli-options.js +32 -0
- package/lib/commands/test-trust-external.js +126 -0
- package/lib/commands/up-common.js +25 -13
- package/lib/commands/up-dataplane.js +50 -13
- package/lib/commands/upload-publish-context.js +75 -0
- package/lib/commands/upload-sidecar-followups.js +89 -0
- package/lib/commands/upload.js +108 -105
- package/lib/commands/verify-governance-combined-external.js +61 -0
- package/lib/commands/verify-governance-command-action.js +64 -0
- package/lib/commands/verify-governance-db.js +102 -0
- package/lib/commands/verify-governance-external.js +77 -0
- package/lib/commands/verify-governance-result-builders.js +71 -0
- package/lib/commands/verify-operations-command-action.js +68 -0
- package/lib/commands/verify-operations-external.js +268 -0
- package/lib/commands/verify-operations-persist.js +75 -0
- package/lib/commands/verify-operations-step-plan.js +68 -0
- package/lib/commands/verify-operations-steps.js +111 -0
- package/lib/commands/verify-trust-command-action.js +70 -0
- package/lib/commands/verify-trust-external.js +67 -0
- package/lib/commands/wizard-config-normalizer.js +43 -23
- package/lib/commands/wizard-core.js +5 -0
- package/lib/core/admin-password-generator.js +38 -0
- package/lib/core/admin-secrets-bundle.js +111 -0
- package/lib/core/admin-secrets-env-overlay.js +62 -0
- package/lib/core/admin-secrets.js +32 -4
- package/lib/core/config-attach-extensions.js +3 -0
- package/lib/core/config-device-dataplane.js +185 -0
- package/lib/core/config-platform-controller.js +44 -0
- package/lib/core/config-setup-installation-profile.js +42 -0
- package/lib/core/config-setup-platform-mode.js +46 -0
- package/lib/core/config.js +46 -36
- package/lib/core/secrets-admin-env.js +6 -2
- package/lib/core/secrets-ensure.js +4 -0
- package/lib/core/secrets-env-content.js +54 -48
- package/lib/core/secrets-env-write.js +10 -1
- package/lib/core/secrets-infra-placeholder-sync.js +2 -0
- package/lib/core/setup-installation-profile.js +43 -0
- package/lib/core/setup-platform-mode.js +55 -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 +95 -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 +115 -0
- package/lib/datasource/audit-evidence-extract.js +180 -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 +205 -0
- package/lib/datasource/audit-evidence-verify.js +108 -0
- package/lib/datasource/bulk-loader-run-batches.js +100 -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 +95 -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 +29 -2
- package/lib/datasource/unified-validation-run-body.js +3 -1
- package/lib/datasource/unified-validation-run-resolve.js +45 -12
- package/lib/datasource/unified-validation-run.js +47 -18
- package/lib/datasource/validate.js +37 -20
- package/lib/deployment/environment-poll-status.js +138 -0
- package/lib/deployment/environment.js +42 -34
- package/lib/external-system/download.js +21 -20
- package/lib/external-system/generator.js +5 -1
- package/lib/external-system/integration-auth-context.js +45 -0
- package/lib/external-system/test-auth.js +12 -5
- package/lib/external-system/test-system-level.js +25 -4
- package/lib/external-system/test.js +9 -7
- package/lib/generator/external-controller-manifest.js +15 -0
- package/lib/governance/governance-cli-errors.js +96 -0
- package/lib/governance/governance-download-display.js +38 -0
- package/lib/governance/governance-pack-loader.js +70 -0
- package/lib/governance/governance-run-display.js +309 -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/infrastructure/admin-secrets-apply.js +84 -0
- package/lib/infrastructure/admin-secrets-ensure.js +130 -0
- package/lib/infrastructure/admin-secrets-resolve.js +95 -0
- package/lib/infrastructure/admin-secrets-sync.js +91 -0
- package/lib/infrastructure/helpers.js +23 -129
- package/lib/infrastructure/index.js +5 -6
- package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
- package/lib/internal/fs-real-sync.js +29 -1
- package/lib/lifecycle/certification-progress.js +231 -0
- package/lib/lifecycle/dataplane-ui-url.js +145 -0
- package/lib/lifecycle/exit-codes.js +24 -0
- package/lib/lifecycle/json-envelope.js +25 -0
- package/lib/lifecycle/lifecycle-governance-options.js +51 -0
- package/lib/lifecycle/logger-quiet.js +29 -0
- package/lib/lifecycle/open-api-published.js +59 -0
- package/lib/lifecycle/operations-readiness-metrics.js +101 -0
- package/lib/lifecycle/product-model.js +196 -0
- package/lib/lifecycle/report-display.js +471 -0
- package/lib/lifecycle/scenario-upload.js +191 -0
- package/lib/lifecycle/verify-step-progress.js +87 -0
- package/lib/parameters/infra-kv-discovery.js +27 -2
- package/lib/protection/auth-context.js +51 -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/application-schema.json +1 -1
- package/lib/schema/datasource-test-run.schema.json +89 -6
- package/lib/schema/external-datasource.schema.json +2636 -2588
- package/lib/schema/external-system.schema.json +1089 -761
- package/lib/schema/flag-map-validation-run.json +2 -2
- package/lib/schema/infra.parameter.yaml +45 -0
- package/lib/schema/protection.schema.json +374 -0
- package/lib/schema/type/credential-field-registry.json +642 -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/aifabrix-test-runtime-guard.js +125 -0
- package/lib/utils/api.js +55 -31
- package/lib/utils/app-relative-path-env.js +112 -0
- package/lib/utils/cli-exit-codes.js +30 -0
- package/lib/utils/cli-secrets-error-format.js +83 -21
- package/lib/utils/cli-sync-options.js +33 -0
- package/lib/utils/cli-utils.js +16 -4
- package/lib/utils/compose-generate-docker-compose.js +4 -0
- package/lib/utils/compose-traefik-ingress-base.js +16 -1
- package/lib/utils/config-secrets-path.js +46 -0
- package/lib/utils/controller-url.js +88 -1
- package/lib/utils/credential-push-ui.js +90 -0
- package/lib/utils/credential-secrets-env.js +137 -9
- package/lib/utils/dataplane-cli-version-gate.js +202 -0
- package/lib/utils/dataplane-cli-version-help.js +74 -0
- package/lib/utils/dataplane-resolver.js +129 -2
- 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/declarative-url-ports.js +3 -3
- package/lib/utils/deployment-auth-mode.js +41 -0
- package/lib/utils/developer-volume-id.js +33 -0
- package/lib/utils/env-copy.js +14 -9
- package/lib/utils/env-kv-orphan-lines.js +43 -0
- package/lib/utils/error-formatters/network-errors.js +13 -4
- package/lib/utils/external-env-template.js +1 -0
- package/lib/utils/external-system-readiness-core.js +10 -0
- package/lib/utils/external-system-readiness-display.js +80 -23
- package/lib/utils/health-check.js +4 -1
- package/lib/utils/help-builder.js +35 -26
- package/lib/utils/keycloak-realm-ready.js +101 -0
- package/lib/utils/load-cip-capacity-display-config.js +61 -15
- package/lib/utils/manifest-source-emit.js +24 -0
- package/lib/utils/pipeline-upload-auth-retry.js +82 -0
- package/lib/utils/platform-controller-url.js +156 -0
- package/lib/utils/postgres-platform-bootstrap.js +199 -0
- package/lib/utils/postgres-wipe.js +9 -2
- package/lib/utils/resolve-env-mode.js +47 -0
- package/lib/utils/secrets-generator.js +31 -25
- package/lib/utils/secrets-missing-error.js +164 -0
- package/lib/utils/semver-compare.js +127 -0
- package/lib/utils/service-url-label.js +81 -0
- package/lib/utils/setup-quiet-output.js +25 -0
- package/lib/utils/template-helpers.js +15 -0
- package/lib/utils/token-manager-client-credentials.js +201 -0
- package/lib/utils/token-manager-messages.js +8 -0
- package/lib/utils/token-manager-refresh.js +12 -2
- package/lib/utils/token-manager.js +86 -105
- package/lib/utils/upload-pipeline-ui.js +98 -0
- package/lib/utils/upload-sync-options.js +42 -0
- package/lib/utils/url-declarative-public-base.js +21 -3
- package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
- package/lib/utils/validation-poll-ui.js +22 -7
- package/lib/utils/validation-run-stage.js +45 -0
- package/lib/validation/wizard-config-validator.js +58 -19
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +2 -1
- package/templates/applications/dataplane/env.template +36 -1
- package/templates/applications/dataplane/rbac.yaml +4 -0
- package/templates/applications/miso-controller/env.template +16 -0
- package/templates/external-system/README.md.hbs +116 -11
- package/templates/python/docker-compose.hbs +19 -18
- package/templates/typescript/docker-compose.hbs +18 -17
|
@@ -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 / verify-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('verify-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('verify-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 verify-trust --debug` → `verify-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'|'verify-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 === 'verify-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'|'verify-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 === 'verify-trust') {
|
|
437
|
+
logPath = await getLatestLogPath(logsDir, 'verify-trust-');
|
|
438
|
+
if (!logPath) {
|
|
439
|
+
throw new Error(
|
|
440
|
+
`No verify-trust log found in ${logsDir}. Run: aifabrix datasource verify-trust <key> --debug`
|
|
441
|
+
);
|
|
442
|
+
}
|
|
339
443
|
} else {
|
|
340
444
|
const pattern = logType === 'test-e2e' ? 'test-e2e-' : 'test-integration-';
|
|
341
445
|
logPath = await getLatestLogPath(logsDir, pattern);
|
|
@@ -360,6 +464,7 @@ module.exports = {
|
|
|
360
464
|
formatE2ELog,
|
|
361
465
|
formatIntegrationLog,
|
|
362
466
|
formatStructuralTestLog,
|
|
467
|
+
formatTrustLog,
|
|
363
468
|
parseDatasourceLogJson,
|
|
364
469
|
runLogViewer
|
|
365
470
|
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Parse JSON array and NDJSON record fixture files (plan 144)
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} content
|
|
13
|
+
* @returns {Object[]}
|
|
14
|
+
*/
|
|
15
|
+
function parseNdjsonContent(content) {
|
|
16
|
+
const lines = String(content).split(/\r?\n/);
|
|
17
|
+
const records = [];
|
|
18
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
19
|
+
const line = lines[i].trim();
|
|
20
|
+
if (!line) continue;
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = JSON.parse(line);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
throw new Error(`NDJSON parse error at line ${i + 1}: ${err.message}`);
|
|
26
|
+
}
|
|
27
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
28
|
+
throw new Error(`NDJSON line ${i + 1}: expected a JSON object`);
|
|
29
|
+
}
|
|
30
|
+
records.push(parsed);
|
|
31
|
+
}
|
|
32
|
+
return records;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} content
|
|
37
|
+
* @returns {Object[]}
|
|
38
|
+
*/
|
|
39
|
+
function parseJsonArrayContent(content) {
|
|
40
|
+
let parsed;
|
|
41
|
+
try {
|
|
42
|
+
parsed = JSON.parse(content);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
throw new Error(`JSON parse error: ${err.message}`);
|
|
45
|
+
}
|
|
46
|
+
if (!Array.isArray(parsed)) {
|
|
47
|
+
throw new Error('JSON file must be a top-level array of objects');
|
|
48
|
+
}
|
|
49
|
+
parsed.forEach((row, index) => {
|
|
50
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) {
|
|
51
|
+
throw new Error(`JSON array index ${index}: expected an object`);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return parsed;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Parse a local record fixture file.
|
|
59
|
+
* @param {string} filePath
|
|
60
|
+
* @param {'json'|'ndjson'} format
|
|
61
|
+
* @returns {Object[]}
|
|
62
|
+
*/
|
|
63
|
+
function parseRecordFile(filePath, format) {
|
|
64
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
65
|
+
if (format === 'ndjson') {
|
|
66
|
+
return parseNdjsonContent(content);
|
|
67
|
+
}
|
|
68
|
+
return parseJsonArrayContent(content);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Rough payload size estimate for dry-run (bytes).
|
|
73
|
+
* @param {Object[]} records
|
|
74
|
+
* @returns {number}
|
|
75
|
+
*/
|
|
76
|
+
function estimatePayloadBytes(records) {
|
|
77
|
+
return Buffer.byteLength(JSON.stringify(records), 'utf8');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
parseRecordFile,
|
|
82
|
+
parseNdjsonContent,
|
|
83
|
+
parseJsonArrayContent,
|
|
84
|
+
estimatePayloadBytes
|
|
85
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Map fixture payloads to canonical bulk sync records (plan 144)
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {Object} obj
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
function isCanonicalBulkRecord(obj) {
|
|
14
|
+
return (
|
|
15
|
+
obj &&
|
|
16
|
+
typeof obj === 'object' &&
|
|
17
|
+
typeof obj.key === 'string' &&
|
|
18
|
+
obj.key.trim().length > 0 &&
|
|
19
|
+
typeof obj.recordType === 'string' &&
|
|
20
|
+
obj.recordType.trim().length > 0 &&
|
|
21
|
+
obj.metadata !== undefined &&
|
|
22
|
+
obj.metadata !== null &&
|
|
23
|
+
typeof obj.metadata === 'object' &&
|
|
24
|
+
!Array.isArray(obj.metadata)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string[]} primaryKeyFields
|
|
30
|
+
* @param {Object} payload
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function buildKeyFromPrimaryKey(primaryKeyFields, payload) {
|
|
34
|
+
if (!primaryKeyFields.length) {
|
|
35
|
+
throw new Error('Datasource primaryKey is empty; provide records with a key field');
|
|
36
|
+
}
|
|
37
|
+
if (primaryKeyFields.length === 1) {
|
|
38
|
+
const field = primaryKeyFields[0];
|
|
39
|
+
const value = payload[field];
|
|
40
|
+
if (value === undefined || value === null || String(value).trim() === '') {
|
|
41
|
+
throw new Error(`Missing primary key field '${field}' in record payload`);
|
|
42
|
+
}
|
|
43
|
+
return String(value);
|
|
44
|
+
}
|
|
45
|
+
const parts = [];
|
|
46
|
+
for (const field of primaryKeyFields) {
|
|
47
|
+
const value = payload[field];
|
|
48
|
+
if (value === undefined || value === null || String(value).trim() === '') {
|
|
49
|
+
throw new Error(`Missing composite primary key field '${field}' in record payload`);
|
|
50
|
+
}
|
|
51
|
+
parts.push(`${field}=${String(value)}`);
|
|
52
|
+
}
|
|
53
|
+
return parts.join(',');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {Object} datasource
|
|
58
|
+
* @param {Object} payload
|
|
59
|
+
* @returns {import('../api/types/records-bulk.types').ExternalBulkRecord}
|
|
60
|
+
*/
|
|
61
|
+
function mapPayloadToBulkRecord(datasource, payload) {
|
|
62
|
+
const primaryKey = Array.isArray(datasource.primaryKey) ? datasource.primaryKey : [];
|
|
63
|
+
const recordType =
|
|
64
|
+
typeof datasource.resourceType === 'string' && datasource.resourceType.trim()
|
|
65
|
+
? datasource.resourceType.trim()
|
|
66
|
+
: 'record';
|
|
67
|
+
const key = buildKeyFromPrimaryKey(primaryKey, payload);
|
|
68
|
+
let displayName = '';
|
|
69
|
+
const labelKey = datasource.labelKey;
|
|
70
|
+
if (Array.isArray(labelKey) && labelKey.length > 0) {
|
|
71
|
+
displayName = labelKey
|
|
72
|
+
.map(field => payload[field])
|
|
73
|
+
.filter(v => v !== undefined && v !== null && String(v).trim() !== '')
|
|
74
|
+
.map(v => String(v))
|
|
75
|
+
.join(' ');
|
|
76
|
+
} else if (typeof labelKey === 'string' && labelKey.trim() && payload[labelKey] !== undefined && payload[labelKey] !== null) {
|
|
77
|
+
displayName = String(payload[labelKey]);
|
|
78
|
+
}
|
|
79
|
+
const metadata = { ...payload };
|
|
80
|
+
if (metadata.externalId === undefined) {
|
|
81
|
+
metadata.externalId = key;
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
key,
|
|
85
|
+
displayName: displayName || '',
|
|
86
|
+
recordType,
|
|
87
|
+
metadata
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {Object[]} rawRecords
|
|
93
|
+
* @param {Object} datasource
|
|
94
|
+
* @returns {import('../api/types/records-bulk.types').ExternalBulkRecord[]}
|
|
95
|
+
*/
|
|
96
|
+
function normalizeRecordsForBulk(rawRecords, datasource) {
|
|
97
|
+
if (!Array.isArray(rawRecords) || rawRecords.length === 0) {
|
|
98
|
+
throw new Error('Record file is empty after parse');
|
|
99
|
+
}
|
|
100
|
+
return rawRecords.map((row, index) => {
|
|
101
|
+
try {
|
|
102
|
+
if (isCanonicalBulkRecord(row)) {
|
|
103
|
+
const meta = { ...row.metadata };
|
|
104
|
+
if (meta.externalId === undefined) {
|
|
105
|
+
meta.externalId = row.key;
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
key: String(row.key).trim(),
|
|
109
|
+
displayName:
|
|
110
|
+
row.displayName !== undefined && row.displayName !== null
|
|
111
|
+
? String(row.displayName)
|
|
112
|
+
: '',
|
|
113
|
+
recordType: String(row.recordType).trim(),
|
|
114
|
+
metadata: meta
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return mapPayloadToBulkRecord(datasource, row);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
throw new Error(`Record ${index + 1}: ${err.message}`);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
isCanonicalBulkRecord,
|
|
126
|
+
buildKeyFromPrimaryKey,
|
|
127
|
+
mapPayloadToBulkRecord,
|
|
128
|
+
normalizeRecordsForBulk
|
|
129
|
+
};
|