@aifabrix/builder 2.45.5 → 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/project-rules.mdc +1 -0
- package/README.md +43 -17
- 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/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 +41 -136
- package/lib/api/api-response-errors.js +124 -0
- package/lib/api/governance-scenario-pack.api.js +159 -0
- package/lib/api/index.js +43 -3
- package/lib/api/lifecycle.api.js +77 -0
- package/lib/api/pipeline.api.js +3 -1
- package/lib/api/records-bulk.api.js +14 -1
- package/lib/api/validation-runner.js +3 -1
- package/lib/app/deploy-config.js +4 -1
- 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 +4 -0
- package/lib/cli/infra-guided-auth.js +109 -0
- package/lib/cli/infra-guided.js +84 -81
- package/lib/cli/setup-app.help.js +0 -31
- package/lib/cli/setup-app.js +28 -2
- package/lib/cli/setup-app.test-commands.js +5 -57
- package/lib/cli/setup-auth.js +25 -0
- 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 +25 -3
- 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 +22 -2
- package/lib/cli/setup-utility-repair.js +198 -0
- package/lib/cli/setup-utility-resolve.js +40 -4
- package/lib/cli/setup-utility-validate.js +197 -0
- package/lib/cli/setup-utility.js +4 -239
- package/lib/commands/auth-status-client-tokens.js +214 -0
- package/lib/commands/auth-status-dataplane-version.js +58 -20
- package/lib/commands/auth-status.js +41 -47
- package/lib/commands/datasource-test-trust-cli.js +27 -23
- package/lib/commands/datasource-unified-test-cli.js +6 -3
- package/lib/commands/datasource-unified-test-cli.options.js +5 -1
- package/lib/commands/datasource.js +7 -7
- package/lib/commands/dev-show-display.js +8 -1
- 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/lifecycle-command-action.js +123 -0
- package/lib/commands/lifecycle-external.js +268 -0
- package/lib/commands/login.js +11 -2
- 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 -296
- 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 +24 -8
- package/lib/commands/test-governance-external.js +3 -24
- package/lib/commands/test-trust-external.js +20 -21
- 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 +90 -108
- 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 +2 -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-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 +1 -1
- package/lib/datasource/agent-trust-run.js +5 -3
- package/lib/datasource/audit-evidence-extract-debug.js +30 -18
- package/lib/datasource/audit-evidence-extract.js +7 -4
- package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
- package/lib/datasource/bulk-loader-run-batches.js +15 -3
- package/lib/datasource/datasource-load-export-context.js +3 -0
- package/lib/datasource/log-cleaner.js +2 -2
- package/lib/datasource/log-viewer.js +8 -8
- package/lib/datasource/test-e2e.js +4 -1
- package/lib/datasource/unified-validation-run-body.js +3 -1
- package/lib/datasource/unified-validation-run.js +5 -2
- 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.js +6 -6
- 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-run-display.js +215 -25
- 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/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 +10 -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 -2622
- package/lib/schema/external-system.schema.json +1089 -761
- package/lib/schema/infra.parameter.yaml +45 -0
- package/lib/schema/type/credential-field-registry.json +103 -97
- 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-secrets-error-format.js +83 -21
- 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-secrets-env.js +137 -9
- package/lib/utils/dataplane-resolver.js +129 -2
- 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 +11 -8
- 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 +2 -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/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 +12 -5
- 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 +8 -0
- package/lib/utils/validation-run-stage.js +45 -0
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +2 -1
- package/templates/applications/dataplane/env.template +24 -1
- package/templates/applications/miso-controller/env.template +16 -0
- package/templates/external-system/README.md.hbs +74 -17
- package/templates/python/docker-compose.hbs +19 -18
- package/templates/typescript/docker-compose.hbs +18 -17
- package/lib/commands/test-governance-command-action.js +0 -55
- package/lib/commands/test-trust-command-action.js +0 -118
package/lib/commands/login.js
CHANGED
|
@@ -85,7 +85,7 @@ async function normalizeControllerUrl(options) {
|
|
|
85
85
|
if (!controllerUrl) {
|
|
86
86
|
controllerUrl = await resolveControllerUrl();
|
|
87
87
|
}
|
|
88
|
-
controllerUrl = String(controllerUrl).replace(/\/+$/, '');
|
|
88
|
+
controllerUrl = String(controllerUrl).trim().replace(/\/+$/, '');
|
|
89
89
|
// Save controller URL to config
|
|
90
90
|
await setControllerUrl(controllerUrl);
|
|
91
91
|
if (!options.compact) {
|
|
@@ -170,6 +170,14 @@ async function handleDeviceCodeLoginFlow(controllerUrl, environment, options) {
|
|
|
170
170
|
*/
|
|
171
171
|
async function refreshDataplaneVersionIfNeeded(controllerUrl, options, skipRefresh) {
|
|
172
172
|
if (skipRefresh) return;
|
|
173
|
+
if (!options.compact) {
|
|
174
|
+
const { LOGIN_POST_REFRESH_BUDGET_MS } = require('./auth-status-dataplane-version');
|
|
175
|
+
logger.log(
|
|
176
|
+
chalk.gray(
|
|
177
|
+
`Syncing dataplane version cache (optional, up to ${Math.round(LOGIN_POST_REFRESH_BUDGET_MS / 1000)}s)...`
|
|
178
|
+
)
|
|
179
|
+
);
|
|
180
|
+
}
|
|
173
181
|
await tryRefreshDataplaneVersionAfterLogin(controllerUrl, options);
|
|
174
182
|
}
|
|
175
183
|
|
|
@@ -213,7 +221,8 @@ async function handleLogin(options) {
|
|
|
213
221
|
const method = await determineAuthMethod(options.method);
|
|
214
222
|
validateScopeOptions(method, options);
|
|
215
223
|
|
|
216
|
-
const skipRefresh =
|
|
224
|
+
const skipRefresh =
|
|
225
|
+
options.skipDataplaneVersionRefresh === true || options.skipVersionCache === true;
|
|
217
226
|
const flow = await executeLoginMethod(method, controllerUrl, environment, options, skipRefresh);
|
|
218
227
|
if (flow === 'device') return;
|
|
219
228
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repair --auth: canonical authentication blocks and bearerToken preset.
|
|
3
|
+
*
|
|
4
|
+
* Applies canonical authentication blocks for repair --auth.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Repair authentication method application
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const { buildAuthenticationFromMethod } = require('../external-system/generator');
|
|
14
|
+
|
|
15
|
+
/** Repair CLI --auth values (normalized lowercase). */
|
|
16
|
+
const ALLOWED_REPAIR_AUTH = [
|
|
17
|
+
'oauth2',
|
|
18
|
+
'aad',
|
|
19
|
+
'apikey',
|
|
20
|
+
'bearertoken',
|
|
21
|
+
'bearerkey',
|
|
22
|
+
'basic',
|
|
23
|
+
'queryparam',
|
|
24
|
+
'oidc',
|
|
25
|
+
'hmac',
|
|
26
|
+
'none'
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const PRESERVED_VARIABLE_KEYS = ['baseUrl', 'testEndpoint'];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} authOption - Raw --auth value
|
|
33
|
+
* @returns {string} Normalized lowercase auth option
|
|
34
|
+
*/
|
|
35
|
+
function normalizeRepairAuthOption(authOption) {
|
|
36
|
+
return String(authOption || '').trim().toLowerCase();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} authOption - Normalized --auth value
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
function isAllowedRepairAuth(authOption) {
|
|
44
|
+
return ALLOWED_REPAIR_AUTH.includes(normalizeRepairAuthOption(authOption));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Builds authentication block for repair --auth.
|
|
49
|
+
* @param {string} systemKey - System key
|
|
50
|
+
* @param {string} authOption - Normalized repair auth (e.g. bearertoken, apikey)
|
|
51
|
+
* @returns {{ method: string, variables: Object, security: Object }}
|
|
52
|
+
*/
|
|
53
|
+
function buildAuthenticationForRepair(systemKey, authOption) {
|
|
54
|
+
const normalized = normalizeRepairAuthOption(authOption);
|
|
55
|
+
const schemaMethod =
|
|
56
|
+
normalized === 'queryparam'
|
|
57
|
+
? 'queryParam'
|
|
58
|
+
: normalized === 'bearertoken' || normalized === 'bearerkey'
|
|
59
|
+
? 'bearerToken'
|
|
60
|
+
: normalized;
|
|
61
|
+
return buildAuthenticationFromMethod(systemKey, schemaMethod);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Merges preserved connectivity fields from existing auth into template variables.
|
|
66
|
+
* @param {Object} mergedVariables - Target variables object (mutated)
|
|
67
|
+
* @param {Object} existingVars - Prior authentication.variables
|
|
68
|
+
*/
|
|
69
|
+
function mergePreservedAuthVariables(mergedVariables, existingVars) {
|
|
70
|
+
if (!existingVars || typeof existingVars !== 'object') return;
|
|
71
|
+
for (const key of PRESERVED_VARIABLE_KEYS) {
|
|
72
|
+
if (existingVars[key] !== undefined && existingVars[key] !== null) {
|
|
73
|
+
mergedVariables[key] = existingVars[key];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Human-readable label for repair change log.
|
|
80
|
+
* @param {string} authOption - Normalized repair auth
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
function repairAuthChangeLabel(authOption) {
|
|
84
|
+
const normalized = normalizeRepairAuthOption(authOption);
|
|
85
|
+
if (normalized === 'bearertoken' || normalized === 'bearerkey') return 'bearerToken';
|
|
86
|
+
return normalized;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const MISSING_TEST_ENDPOINT_WARNING =
|
|
90
|
+
'authentication.variables.testEndpoint is missing for apikey/bearerToken auth — add a GET URL for credential/E2E tests (e.g. /crm/v3/objects/contacts?limit=1, or a path resolved against baseUrl).';
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Warn when apikey auth (including bearerToken preset) has no testEndpoint.
|
|
94
|
+
* @param {Object} authentication - authentication block on system file
|
|
95
|
+
* @param {string[]} warnings - Mutable warnings list
|
|
96
|
+
*/
|
|
97
|
+
function appendTestEndpointWarningIfMissing(authentication, warnings) {
|
|
98
|
+
const method = String(authentication?.method || '').toLowerCase();
|
|
99
|
+
if (method !== 'apikey' && method !== 'bearertoken') return;
|
|
100
|
+
const testEndpoint = String(authentication?.variables?.testEndpoint || '').trim();
|
|
101
|
+
if (testEndpoint) return;
|
|
102
|
+
if (warnings.includes(MISSING_TEST_ENDPOINT_WARNING)) return;
|
|
103
|
+
warnings.push(MISSING_TEST_ENDPOINT_WARNING);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Audit system authentication after repair (with or without --auth).
|
|
108
|
+
* @param {Object} systemParsed - Parsed system config
|
|
109
|
+
* @param {string[]} warnings - Mutable warnings list
|
|
110
|
+
*/
|
|
111
|
+
function auditRepairAuthenticationWarnings(systemParsed, warnings) {
|
|
112
|
+
const authentication = systemParsed?.authentication || systemParsed?.auth;
|
|
113
|
+
if (!authentication || typeof authentication !== 'object') return;
|
|
114
|
+
appendTestEndpointWarningIfMissing(authentication, warnings);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
ALLOWED_REPAIR_AUTH,
|
|
119
|
+
MISSING_TEST_ENDPOINT_WARNING,
|
|
120
|
+
normalizeRepairAuthOption,
|
|
121
|
+
isAllowedRepairAuth,
|
|
122
|
+
buildAuthenticationForRepair,
|
|
123
|
+
mergePreservedAuthVariables,
|
|
124
|
+
repairAuthChangeLabel,
|
|
125
|
+
appendTestEndpointWarningIfMissing,
|
|
126
|
+
auditRepairAuthenticationWarnings
|
|
127
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track integration files touched during repair for CLI summary output.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Repair changed-file tracking
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Ensures backup/repair context has changed-file buckets.
|
|
15
|
+
* @param {Object} ctx - Repair backup context
|
|
16
|
+
* @returns {{ changedFiles: string[], wouldChangeFiles: string[] }}
|
|
17
|
+
*/
|
|
18
|
+
function ensureChangedFileBuckets(ctx) {
|
|
19
|
+
if (!ctx) return { changedFiles: [], wouldChangeFiles: [] };
|
|
20
|
+
if (!Array.isArray(ctx.changedFiles)) ctx.changedFiles = [];
|
|
21
|
+
if (!Array.isArray(ctx.wouldChangeFiles)) ctx.wouldChangeFiles = [];
|
|
22
|
+
return { changedFiles: ctx.changedFiles, wouldChangeFiles: ctx.wouldChangeFiles };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Records a file path that repair wrote or would write (dry-run).
|
|
27
|
+
* @param {string} filePath - Path to file
|
|
28
|
+
* @param {Object} ctx - Repair context with dryRun, changedFiles, wouldChangeFiles
|
|
29
|
+
*/
|
|
30
|
+
function trackRepairWrite(filePath, ctx) {
|
|
31
|
+
if (!filePath || !ctx) return;
|
|
32
|
+
const abs = path.resolve(filePath);
|
|
33
|
+
const { changedFiles, wouldChangeFiles } = ensureChangedFileBuckets(ctx);
|
|
34
|
+
const bucket = ctx.dryRun ? wouldChangeFiles : changedFiles;
|
|
35
|
+
if (!bucket.includes(abs)) bucket.push(abs);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
ensureChangedFileBuckets,
|
|
40
|
+
trackRepairWrite
|
|
41
|
+
};
|
|
@@ -16,6 +16,7 @@ const path = require('path');
|
|
|
16
16
|
const fs = require('fs');
|
|
17
17
|
const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
|
|
18
18
|
const { backupIntegrationFile } = require('../utils/integration-file-backup');
|
|
19
|
+
const { trackRepairWrite } = require('./repair-changed-files');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Returns suffix from a canonical-format filename: <systemKey>-datasource-<suffix>.<ext>.
|
|
@@ -174,8 +175,9 @@ function normalizeDatasourceKeysAndFilenames(appPath, datasourceFiles, systemKey
|
|
|
174
175
|
|
|
175
176
|
if (parsed.key !== canonicalKey) {
|
|
176
177
|
parsed.key = canonicalKey;
|
|
178
|
+
const fullPath = path.join(appPath, fileName);
|
|
179
|
+
trackRepairWrite(fullPath, backupCtx);
|
|
177
180
|
if (!dryRun) {
|
|
178
|
-
const fullPath = path.join(appPath, fileName);
|
|
179
181
|
backupIntegrationFile(fullPath, backupCtx);
|
|
180
182
|
writeConfigFile(fullPath, parsed);
|
|
181
183
|
}
|
|
@@ -185,6 +187,7 @@ function normalizeDatasourceKeysAndFilenames(appPath, datasourceFiles, systemKey
|
|
|
185
187
|
if (fileName !== canonicalFileName) {
|
|
186
188
|
const oldPath = path.join(appPath, fileName);
|
|
187
189
|
const newPath = path.join(appPath, canonicalFileName);
|
|
190
|
+
trackRepairWrite(newPath, backupCtx);
|
|
188
191
|
if (!dryRun && fs.existsSync(oldPath) && !fs.existsSync(newPath)) {
|
|
189
192
|
backupIntegrationFile(oldPath, backupCtx);
|
|
190
193
|
fs.renameSync(oldPath, newPath);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch datasource file repair during external integration repair.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Run repairDatasourceFile across integration datasource files
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const logger = require('../utils/logger');
|
|
15
|
+
const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
|
|
16
|
+
const { backupIntegrationFile } = require('../utils/integration-file-backup');
|
|
17
|
+
const { trackRepairWrite } = require('./repair-changed-files');
|
|
18
|
+
const { repairDatasourceFile } = require('./repair-datasource');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} appPath
|
|
22
|
+
* @param {string[]} datasourceFiles
|
|
23
|
+
* @param {Object} options
|
|
24
|
+
* @param {boolean} dryRun
|
|
25
|
+
* @param {string[]} changes
|
|
26
|
+
* @returns {boolean}
|
|
27
|
+
*/
|
|
28
|
+
function runDatasourceRepairs(appPath, datasourceFiles, options, dryRun, changes) {
|
|
29
|
+
if (!datasourceFiles || datasourceFiles.length === 0) return false;
|
|
30
|
+
let updated = false;
|
|
31
|
+
for (const fileName of datasourceFiles) {
|
|
32
|
+
const filePath = path.join(appPath, fileName);
|
|
33
|
+
if (!fs.existsSync(filePath)) continue;
|
|
34
|
+
try {
|
|
35
|
+
const parsed = loadConfigFile(filePath);
|
|
36
|
+
const { updated: fileUpdated, changes: fileChanges } = repairDatasourceFile(parsed, {
|
|
37
|
+
expose: options.expose,
|
|
38
|
+
sync: options.sync,
|
|
39
|
+
test: options.test
|
|
40
|
+
});
|
|
41
|
+
if (fileUpdated) {
|
|
42
|
+
updated = true;
|
|
43
|
+
fileChanges.forEach(c => changes.push(`${fileName}: ${c}`));
|
|
44
|
+
if (!dryRun) {
|
|
45
|
+
backupIntegrationFile(filePath, options.backupCtx);
|
|
46
|
+
writeConfigFile(filePath, parsed);
|
|
47
|
+
}
|
|
48
|
+
trackRepairWrite(filePath, options.backupCtx);
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
logger.log(chalk.yellow(`⚠ Could not repair datasource ${fileName}: ${err.message}`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return updated;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
runDatasourceRepairs
|
|
59
|
+
};
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema-aware testPayload repair (payloadTemplate / expectedResult).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Helpers for repair-datasource testPayload generation
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string|string[]|undefined} type - JSON Schema type
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
function primarySchemaType(type) {
|
|
16
|
+
if (Array.isArray(type)) {
|
|
17
|
+
const nonNull = type.filter(t => t !== 'null');
|
|
18
|
+
return nonNull[0] || 'string';
|
|
19
|
+
}
|
|
20
|
+
return type || 'string';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {Object|null|undefined} schema - JSON Schema node
|
|
25
|
+
* @param {string} key - Property name
|
|
26
|
+
* @returns {Object|null}
|
|
27
|
+
*/
|
|
28
|
+
function getSchemaProperty(schema, key) {
|
|
29
|
+
if (!schema || typeof schema !== 'object') return null;
|
|
30
|
+
const props = schema.properties;
|
|
31
|
+
if (props && typeof props === 'object' && Object.prototype.hasOwnProperty.call(props, key)) {
|
|
32
|
+
return props[key];
|
|
33
|
+
}
|
|
34
|
+
if (schema.additionalProperties === true) {
|
|
35
|
+
return { type: 'string' };
|
|
36
|
+
}
|
|
37
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
|
|
38
|
+
return schema.additionalProperties;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {Object|null|undefined} rootSchema - metadataSchema root
|
|
45
|
+
* @param {string[]} pathParts - Dot path under raw payload (no raw. prefix)
|
|
46
|
+
* @returns {Object|null}
|
|
47
|
+
*/
|
|
48
|
+
function getSchemaNodeAtPath(rootSchema, pathParts) {
|
|
49
|
+
if (!rootSchema || !pathParts.length) return rootSchema;
|
|
50
|
+
let node = rootSchema;
|
|
51
|
+
for (const part of pathParts) {
|
|
52
|
+
if (!part) continue;
|
|
53
|
+
const next = getSchemaProperty(node, part);
|
|
54
|
+
if (!next) return null;
|
|
55
|
+
node = next;
|
|
56
|
+
}
|
|
57
|
+
return node;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {Object|null|undefined} node - JSON Schema node
|
|
62
|
+
* @returns {*}
|
|
63
|
+
*/
|
|
64
|
+
function placeholderFromSchemaNode(node) {
|
|
65
|
+
if (!node || typeof node !== 'object') return '';
|
|
66
|
+
const type = primarySchemaType(node.type);
|
|
67
|
+
if (node.format === 'date-time') return '2025-01-15T10:30:00Z';
|
|
68
|
+
if (node.format === 'date') return '2025-01-15';
|
|
69
|
+
if (node.format === 'uuid') return '00000000-0000-4000-8000-000000000001';
|
|
70
|
+
if (type === 'boolean') return false;
|
|
71
|
+
if (type === 'integer' || type === 'number') return 0;
|
|
72
|
+
if (type === 'array') return [];
|
|
73
|
+
if (type === 'object') {
|
|
74
|
+
if (node.properties && typeof node.properties === 'object') {
|
|
75
|
+
const out = {};
|
|
76
|
+
for (const [k, sub] of Object.entries(node.properties)) {
|
|
77
|
+
out[k] = placeholderFromSchemaNode(sub);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {string} type - Legacy attribute type
|
|
88
|
+
* @returns {*}
|
|
89
|
+
*/
|
|
90
|
+
function placeholderForType(type) {
|
|
91
|
+
if (type === 'number' || type === 'integer') return 0;
|
|
92
|
+
if (type === 'boolean') return false;
|
|
93
|
+
if (type === 'array') return [];
|
|
94
|
+
if (type === 'object') return {};
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {Object} parsed - Datasource config
|
|
100
|
+
* @param {string[]} pathParts - Path under raw payload
|
|
101
|
+
* @param {Object} [attrConfig] - Attribute config (optional type fallback)
|
|
102
|
+
* @returns {*}
|
|
103
|
+
*/
|
|
104
|
+
function resolvePlaceholderForPath(parsed, pathParts, attrConfig) {
|
|
105
|
+
const node = getSchemaNodeAtPath(parsed?.metadataSchema, pathParts);
|
|
106
|
+
if (node) return placeholderFromSchemaNode(node);
|
|
107
|
+
return placeholderForType(attrConfig?.type || 'string');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {Object} obj - Target object (mutated)
|
|
112
|
+
* @param {string[]} pathParts - Path segments
|
|
113
|
+
* @param {*} value - Value to set
|
|
114
|
+
*/
|
|
115
|
+
function setNestedValue(obj, pathParts, value) {
|
|
116
|
+
const parts = pathParts.filter(Boolean);
|
|
117
|
+
if (!parts.length) return;
|
|
118
|
+
let cur = obj;
|
|
119
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
120
|
+
const p = parts[i];
|
|
121
|
+
if (!(p in cur) || typeof cur[p] !== 'object' || cur[p] === null || Array.isArray(cur[p])) {
|
|
122
|
+
cur[p] = {};
|
|
123
|
+
}
|
|
124
|
+
cur = cur[p];
|
|
125
|
+
}
|
|
126
|
+
const last = parts[parts.length - 1];
|
|
127
|
+
const existing = cur[last];
|
|
128
|
+
if (
|
|
129
|
+
existing &&
|
|
130
|
+
typeof existing === 'object' &&
|
|
131
|
+
!Array.isArray(existing) &&
|
|
132
|
+
value &&
|
|
133
|
+
typeof value === 'object' &&
|
|
134
|
+
!Array.isArray(value)
|
|
135
|
+
) {
|
|
136
|
+
cur[last] = { ...existing, ...value };
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
cur[last] = value;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param {Object} parsed - Datasource config
|
|
144
|
+
* @param {Array<{ path: string, config: Object }>} pathEntries - Sorted deepest-first
|
|
145
|
+
* @param {Object} payloadTemplate - payloadTemplate (mutated)
|
|
146
|
+
*/
|
|
147
|
+
function enrichObjectLeavesFromNestedPaths(parsed, pathEntries, payloadTemplate) {
|
|
148
|
+
const propertiesNode = getSchemaNodeAtPath(parsed?.metadataSchema, ['properties']);
|
|
149
|
+
for (const { path } of pathEntries) {
|
|
150
|
+
if (!path.startsWith('properties.')) continue;
|
|
151
|
+
const leaf = path.slice('properties.'.length);
|
|
152
|
+
if (!leaf || leaf.includes('.')) continue;
|
|
153
|
+
if (!payloadTemplate.properties || typeof payloadTemplate.properties !== 'object') {
|
|
154
|
+
payloadTemplate.properties = placeholderFromSchemaNode(propertiesNode);
|
|
155
|
+
}
|
|
156
|
+
if (payloadTemplate.properties[leaf] === undefined || payloadTemplate.properties[leaf] === '') {
|
|
157
|
+
const leafNode = getSchemaNodeAtPath(parsed?.metadataSchema, ['properties', leaf]);
|
|
158
|
+
payloadTemplate.properties[leaf] = leafNode
|
|
159
|
+
? placeholderFromSchemaNode(leafNode)
|
|
160
|
+
: 'sample';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @param {string} expressionPath - Path from {{ ... }} token
|
|
167
|
+
* @returns {string|null}
|
|
168
|
+
*/
|
|
169
|
+
function stripExpressionPathPrefix(expressionPath) {
|
|
170
|
+
const path = expressionPath.trim();
|
|
171
|
+
if (path.startsWith('raw.')) return path.slice(4);
|
|
172
|
+
if (path.startsWith('metadata.')) return path.slice(9);
|
|
173
|
+
return path;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {Object} parsed - Datasource config
|
|
178
|
+
* @param {Object} attrs - fieldMappings.attributes
|
|
179
|
+
* @returns {Array<{ attrKey: string, path: string|null, config: Object, expectedOnly: *|null }>}
|
|
180
|
+
*/
|
|
181
|
+
function buildPathEntriesFromAttributes(parsed, attrs) {
|
|
182
|
+
const pathEntries = [];
|
|
183
|
+
for (const [attrKey, config] of Object.entries(attrs)) {
|
|
184
|
+
const match = config?.expression?.match(/\{\{\s*([^}|]+)/);
|
|
185
|
+
if (!match) {
|
|
186
|
+
const metaNode = parsed.metadataSchema?.properties?.[attrKey];
|
|
187
|
+
const expected = metaNode
|
|
188
|
+
? placeholderFromSchemaNode(metaNode)
|
|
189
|
+
: placeholderForType(config?.type || 'string');
|
|
190
|
+
pathEntries.push({ attrKey, path: null, config, expectedOnly: expected });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
pathEntries.push({
|
|
194
|
+
attrKey,
|
|
195
|
+
path: stripExpressionPathPrefix(match[1]),
|
|
196
|
+
config,
|
|
197
|
+
expectedOnly: null
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
return pathEntries.sort((a, b) => {
|
|
201
|
+
const depthA = a.path ? a.path.split('.').length : 0;
|
|
202
|
+
const depthB = b.path ? b.path.split('.').length : 0;
|
|
203
|
+
return depthB - depthA;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @param {Object} parsed - Datasource config
|
|
209
|
+
* @param {Array} pathEntries - From buildPathEntriesFromAttributes
|
|
210
|
+
* @returns {{ payloadTemplate: Object, expectedResult: Object }}
|
|
211
|
+
*/
|
|
212
|
+
function buildTemplatesFromPathEntries(parsed, pathEntries) {
|
|
213
|
+
const payloadTemplate = {};
|
|
214
|
+
const expectedResult = {};
|
|
215
|
+
for (const entry of pathEntries) {
|
|
216
|
+
const { attrKey, path, config, expectedOnly } = entry;
|
|
217
|
+
const metaNode = parsed.metadataSchema?.properties?.[attrKey];
|
|
218
|
+
expectedResult[attrKey] = metaNode
|
|
219
|
+
? placeholderFromSchemaNode(metaNode)
|
|
220
|
+
: (expectedOnly ?? placeholderForType(config?.type || 'string'));
|
|
221
|
+
if (!path) continue;
|
|
222
|
+
const parts = path.split('.');
|
|
223
|
+
setNestedValue(payloadTemplate, parts, resolvePlaceholderForPath(parsed, parts, config));
|
|
224
|
+
}
|
|
225
|
+
enrichObjectLeavesFromNestedPaths(parsed, pathEntries, payloadTemplate);
|
|
226
|
+
return { payloadTemplate, expectedResult };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Builds minimal payloadTemplate and expectedResult from attributes + metadataSchema.
|
|
231
|
+
*
|
|
232
|
+
* @param {Object} parsed - Parsed datasource (mutated)
|
|
233
|
+
* @param {string[]} changes - Change log
|
|
234
|
+
* @returns {boolean} True if testPayload was added or updated
|
|
235
|
+
*/
|
|
236
|
+
function repairTestPayload(parsed, changes) {
|
|
237
|
+
const attrs = parsed?.fieldMappings?.attributes;
|
|
238
|
+
if (!attrs || typeof attrs !== 'object') return false;
|
|
239
|
+
|
|
240
|
+
const pathEntries = buildPathEntriesFromAttributes(parsed, attrs);
|
|
241
|
+
const { payloadTemplate, expectedResult } = buildTemplatesFromPathEntries(parsed, pathEntries);
|
|
242
|
+
|
|
243
|
+
if (!parsed.testPayload) parsed.testPayload = {};
|
|
244
|
+
parsed.testPayload.payloadTemplate = payloadTemplate;
|
|
245
|
+
parsed.testPayload.expectedResult = expectedResult;
|
|
246
|
+
changes.push(
|
|
247
|
+
'Generated testPayload.payloadTemplate and testPayload.expectedResult from attributes and metadataSchema'
|
|
248
|
+
);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = {
|
|
253
|
+
primarySchemaType,
|
|
254
|
+
getSchemaNodeAtPath,
|
|
255
|
+
placeholderFromSchemaNode,
|
|
256
|
+
placeholderForType,
|
|
257
|
+
resolvePlaceholderForPath,
|
|
258
|
+
repairTestPayload
|
|
259
|
+
};
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const { repairOpenapiSection } = require('./repair-datasource-openapi');
|
|
13
13
|
const { normalizeAutoRbacOperationKeys } = require('./repair-datasource-auto-rbac');
|
|
14
|
+
const { repairTestPayload } = require('./repair-datasource-test-payload');
|
|
14
15
|
|
|
15
16
|
const DEFAULT_SYNC = {
|
|
16
17
|
mode: 'pull',
|
|
@@ -358,25 +359,6 @@ function repairSyncSection(parsed, changes) {
|
|
|
358
359
|
return true;
|
|
359
360
|
}
|
|
360
361
|
|
|
361
|
-
function placeholderForType(type) {
|
|
362
|
-
if (type === 'number' || type === 'integer') return 0;
|
|
363
|
-
if (type === 'boolean') return false;
|
|
364
|
-
if (type === 'array') return [];
|
|
365
|
-
if (type === 'object') return {};
|
|
366
|
-
return '';
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function setNested(obj, pathParts, value) {
|
|
370
|
-
let cur = obj;
|
|
371
|
-
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
372
|
-
const p = pathParts[i];
|
|
373
|
-
if (!(p in cur) || typeof cur[p] !== 'object') cur[p] = {};
|
|
374
|
-
cur = cur[p];
|
|
375
|
-
}
|
|
376
|
-
const last = pathParts[pathParts.length - 1];
|
|
377
|
-
if (last) cur[last] = value;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
362
|
/**
|
|
381
363
|
* Removes unknown top-level keys from testPayload (v2.4.1 closed root).
|
|
382
364
|
* @param {Object} parsed - Parsed datasource (mutated)
|
|
@@ -398,38 +380,6 @@ function sanitizeTestPayloadTopLevel(parsed, changes) {
|
|
|
398
380
|
return true;
|
|
399
381
|
}
|
|
400
382
|
|
|
401
|
-
/**
|
|
402
|
-
* Builds minimal payloadTemplate and expectedResult from attribute expression paths.
|
|
403
|
-
*
|
|
404
|
-
* @param {Object} parsed - Parsed datasource (mutated)
|
|
405
|
-
* @param {string[]} changes - Change log
|
|
406
|
-
* @returns {boolean} True if testPayload was added or updated
|
|
407
|
-
*/
|
|
408
|
-
function repairTestPayload(parsed, changes) {
|
|
409
|
-
const attrs = parsed?.fieldMappings?.attributes;
|
|
410
|
-
if (!attrs || typeof attrs !== 'object') return false;
|
|
411
|
-
const payloadTemplate = {};
|
|
412
|
-
const expectedResult = {};
|
|
413
|
-
for (const [key, config] of Object.entries(attrs)) {
|
|
414
|
-
const type = config?.type || 'string';
|
|
415
|
-
const placeholder = placeholderForType(type);
|
|
416
|
-
expectedResult[key] = placeholder;
|
|
417
|
-
const match = config?.expression?.match(/\{\{\s*([^}|]+)/);
|
|
418
|
-
if (match) {
|
|
419
|
-
let path = match[1].trim();
|
|
420
|
-
if (path.startsWith('raw.')) {
|
|
421
|
-
path = path.slice(4);
|
|
422
|
-
}
|
|
423
|
-
setNested(payloadTemplate, path.split('.'), placeholder);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
if (!parsed.testPayload) parsed.testPayload = {};
|
|
427
|
-
parsed.testPayload.payloadTemplate = payloadTemplate;
|
|
428
|
-
parsed.testPayload.expectedResult = expectedResult;
|
|
429
|
-
changes.push('Generated testPayload.payloadTemplate and testPayload.expectedResult from attributes');
|
|
430
|
-
return true;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
383
|
/**
|
|
434
384
|
* Runs all requested datasource repairs.
|
|
435
385
|
*
|