@aifabrix/builder 2.40.2 → 2.42.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/docs-rules.mdc +30 -0
- package/README.md +7 -5
- package/integration/hubspot/README.md +8 -4
- package/integration/hubspot/application.json +54 -0
- package/integration/hubspot/create-hubspot.js +9 -136
- package/integration/hubspot/env.template +3 -4
- package/integration/hubspot/hubspot-datasource-company.json +343 -5
- package/integration/hubspot/hubspot-datasource-contact.json +413 -5
- package/integration/hubspot/hubspot-datasource-deal.json +341 -4
- package/integration/hubspot/hubspot-datasource-users.json +116 -0
- package/integration/hubspot/hubspot-deploy.json +1250 -108
- package/integration/hubspot/hubspot-system.json +15 -32
- package/integration/hubspot/test-dataplane-down-tests.js +17 -16
- package/integration/hubspot/test-dataplane-down.js +2 -2
- package/integration/hubspot/test.js +1 -1
- package/jest.config.manual.js +2 -1
- package/lib/api/credential.api.js +40 -0
- package/lib/api/dev.api.js +423 -0
- package/lib/api/external-test.api.js +111 -0
- package/lib/api/index.js +42 -19
- package/lib/api/pipeline.api.js +66 -120
- package/lib/api/types/credential.types.js +23 -0
- package/lib/api/types/dev.types.js +140 -0
- package/lib/api/types/pipeline.types.js +37 -0
- package/lib/api/wizard-platform.api.js +61 -0
- package/lib/api/wizard.api.js +34 -1
- package/lib/app/config.js +44 -11
- package/lib/app/down.js +2 -1
- package/lib/app/index.js +12 -1
- package/lib/app/prompts.js +44 -29
- package/lib/app/push.js +36 -12
- package/lib/app/readme.js +9 -6
- package/lib/app/run-env-compose.js +264 -0
- package/lib/app/run-helpers.js +121 -118
- package/lib/app/run.js +148 -28
- package/lib/app/show-display.js +1 -1
- package/lib/app/show.js +5 -2
- package/lib/build/index.js +11 -3
- package/lib/cli/setup-app.js +172 -15
- package/lib/cli/setup-credential-deployment.js +31 -6
- package/lib/cli/setup-dev.js +206 -16
- package/lib/cli/setup-environment.js +16 -6
- package/lib/cli/setup-external-system.js +89 -24
- package/lib/cli/setup-infra.js +82 -15
- package/lib/cli/setup-secrets.js +52 -5
- package/lib/cli/setup-utility.js +129 -24
- package/lib/commands/app-install.js +172 -0
- package/lib/commands/app-shell.js +75 -0
- package/lib/commands/app-test.js +282 -0
- package/lib/commands/app.js +1 -1
- package/lib/commands/credential-env.js +162 -0
- package/lib/commands/credential-list.js +17 -22
- package/lib/commands/credential-push.js +96 -0
- package/lib/commands/datasource.js +77 -6
- package/lib/commands/dev-cli-handlers.js +141 -0
- package/lib/commands/dev-down.js +114 -0
- package/lib/commands/dev-init.js +347 -0
- package/lib/commands/repair-auth-config.js +99 -0
- package/lib/commands/repair-datasource-keys.js +208 -0
- package/lib/commands/repair-datasource.js +235 -0
- package/lib/commands/repair-env-template.js +348 -0
- package/lib/commands/repair-internal.js +85 -0
- package/lib/commands/repair-rbac.js +158 -0
- package/lib/commands/repair.js +507 -0
- package/lib/commands/secrets-list.js +118 -0
- package/lib/commands/secrets-remove.js +97 -0
- package/lib/commands/secrets-set.js +30 -17
- package/lib/commands/secrets-validate.js +50 -0
- package/lib/commands/test-e2e-external.js +165 -0
- package/lib/commands/up-dataplane.js +2 -2
- package/lib/commands/up-miso.js +0 -25
- package/lib/commands/upload.js +96 -40
- package/lib/commands/wizard-core-helpers.js +226 -4
- package/lib/commands/wizard-core.js +67 -29
- package/lib/commands/wizard-dataplane.js +1 -1
- package/lib/commands/wizard-entity-selection.js +43 -0
- package/lib/commands/wizard-headless.js +44 -5
- package/lib/commands/wizard-helpers.js +7 -3
- package/lib/commands/wizard.js +86 -64
- package/lib/core/admin-secrets.js +96 -0
- package/lib/core/config.js +7 -1
- package/lib/core/secrets-ensure.js +378 -0
- package/lib/core/secrets-env-write.js +157 -0
- package/lib/core/secrets.js +176 -89
- package/lib/datasource/deploy.js +12 -3
- package/lib/datasource/field-reference-validator.js +91 -0
- package/lib/datasource/test-e2e.js +219 -0
- package/lib/datasource/test-integration.js +154 -0
- package/lib/datasource/validate.js +21 -3
- package/lib/deployment/deployer.js +7 -5
- package/lib/deployment/environment-config.js +137 -0
- package/lib/deployment/environment.js +21 -98
- package/lib/deployment/push.js +32 -2
- package/lib/external-system/download.js +188 -203
- package/lib/external-system/generator.js +204 -56
- package/lib/external-system/test-auth.js +7 -3
- package/lib/external-system/test-execution.js +2 -1
- package/lib/external-system/test-system-level.js +73 -0
- package/lib/external-system/test.js +56 -19
- package/lib/generator/external-controller-manifest.js +29 -2
- package/lib/generator/external-schema-utils.js +1 -1
- package/lib/generator/external.js +10 -3
- package/lib/generator/index.js +177 -25
- package/lib/generator/split-readme.js +1 -0
- package/lib/generator/split-variables.js +7 -1
- package/lib/generator/split.js +194 -54
- package/lib/generator/wizard-prompts-secondary.js +294 -0
- package/lib/generator/wizard-prompts.js +105 -106
- package/lib/generator/wizard-readme.js +88 -0
- package/lib/generator/wizard.js +155 -158
- package/lib/infrastructure/compose.js +11 -1
- package/lib/infrastructure/helpers.js +103 -20
- package/lib/infrastructure/index.js +98 -12
- package/lib/infrastructure/services.js +88 -22
- package/lib/schema/application-schema.json +32 -8
- package/lib/schema/external-datasource.schema.json +49 -26
- package/lib/schema/external-system.schema.json +509 -411
- package/lib/schema/wizard-config.schema.json +16 -0
- package/lib/utils/api.js +41 -13
- package/lib/utils/app-register-auth.js +25 -3
- package/lib/utils/auth-headers.js +8 -7
- package/lib/utils/cli-utils.js +20 -0
- package/lib/utils/compose-generator.js +77 -76
- package/lib/utils/compose-handlebars-helpers.js +54 -0
- package/lib/utils/compose-vector-helper.js +18 -0
- package/lib/utils/config-format-preference.js +51 -0
- package/lib/utils/config-format.js +36 -0
- package/lib/utils/config-paths.js +127 -2
- package/lib/utils/configuration-env-resolver.js +179 -0
- package/lib/utils/credential-display.js +83 -0
- package/lib/utils/credential-secrets-env.js +357 -0
- package/lib/utils/dataplane-pipeline-warning.js +28 -0
- package/lib/utils/deployment-validation-helpers.js +4 -4
- package/lib/utils/dev-ca-install.js +139 -0
- package/lib/utils/dev-cert-helper.js +122 -0
- package/lib/utils/device-code-helpers.js +224 -0
- package/lib/utils/device-code.js +37 -336
- package/lib/utils/docker-build.js +40 -8
- package/lib/utils/env-copy.js +103 -13
- package/lib/utils/env-map.js +35 -5
- package/lib/utils/env-template.js +6 -5
- package/lib/utils/error-formatters/http-status-errors.js +20 -2
- package/lib/utils/error-formatters/permission-errors.js +0 -1
- package/lib/utils/error-formatters/validation-errors.js +0 -1
- package/lib/utils/external-readme.js +56 -29
- package/lib/utils/external-system-display.js +59 -1
- package/lib/utils/external-system-test-helpers.js +21 -8
- package/lib/utils/external-system-validators.js +3 -0
- package/lib/utils/file-upload.js +20 -50
- package/lib/utils/help-builder.js +16 -2
- package/lib/utils/infra-status.js +80 -45
- package/lib/utils/local-secrets.js +7 -52
- package/lib/utils/mutagen-install.js +195 -0
- package/lib/utils/mutagen.js +146 -0
- package/lib/utils/paths.js +128 -37
- package/lib/utils/port-resolver.js +28 -16
- package/lib/utils/remote-dev-auth.js +38 -0
- package/lib/utils/remote-docker-env.js +43 -0
- package/lib/utils/remote-secrets-loader.js +60 -0
- package/lib/utils/secrets-canonical.js +93 -0
- package/lib/utils/secrets-generator.js +114 -6
- package/lib/utils/secrets-helpers.js +108 -114
- package/lib/utils/secrets-path.js +2 -2
- package/lib/utils/secrets-utils.js +52 -1
- package/lib/utils/secrets-validation.js +84 -0
- package/lib/utils/ssh-key-helper.js +116 -0
- package/lib/utils/test-log-writer.js +56 -0
- package/lib/utils/token-manager-messages.js +90 -0
- package/lib/utils/token-manager.js +29 -36
- package/lib/utils/variable-transformer.js +3 -3
- package/lib/validation/env-template-auth.js +157 -0
- package/lib/validation/env-template-kv.js +41 -0
- package/lib/validation/external-manifest-validator.js +25 -0
- package/lib/validation/external-system-auth-rules.js +86 -0
- package/lib/validation/validate-batch.js +149 -0
- package/lib/validation/validate-datasource-keys-api.js +33 -0
- package/lib/validation/validate-display.js +94 -16
- package/lib/validation/validate.js +25 -12
- package/lib/validation/validator.js +72 -9
- package/lib/validation/wizard-datasource-validation.js +50 -0
- package/package.json +8 -3
- package/scripts/install-local.js +34 -15
- package/templates/README.md +0 -1
- package/templates/applications/README.md.hbs +4 -4
- package/templates/applications/dataplane/application.yaml +6 -5
- package/templates/applications/dataplane/env.template +15 -10
- package/templates/applications/dataplane/rbac.yaml +2 -2
- package/templates/applications/keycloak/env.template +2 -0
- package/templates/applications/miso-controller/application.yaml +1 -0
- package/templates/applications/miso-controller/env.template +12 -10
- package/templates/external-system/README.md.hbs +65 -25
- package/templates/external-system/deploy.js.hbs +4 -2
- package/templates/external-system/external-datasource.yaml.hbs +217 -0
- package/templates/external-system/external-system.json.hbs +1 -18
- package/templates/infra/compose.yaml.hbs +6 -0
- package/templates/python/docker-compose.hbs +49 -23
- package/templates/typescript/docker-compose.hbs +48 -22
- package/integration/hubspot/application.yaml +0 -37
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasource repair helpers: align dimensions, metadataSchema, exposed, sync, testPayload
|
|
3
|
+
* with fieldMappings.attributes as source of truth.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Repair datasource files for external integration
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_SYNC = {
|
|
13
|
+
mode: 'pull',
|
|
14
|
+
batchSize: 500,
|
|
15
|
+
maxParallelRequests: 5
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const MINIMAL_METADATA_SCHEMA = {
|
|
19
|
+
type: 'object',
|
|
20
|
+
additionalProperties: true
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns the set of attribute keys from fieldMappings.attributes.
|
|
25
|
+
* @param {Object} parsed - Parsed datasource object
|
|
26
|
+
* @returns {Set<string>}
|
|
27
|
+
*/
|
|
28
|
+
function getAttributeKeys(parsed) {
|
|
29
|
+
const attrs = parsed?.fieldMappings?.attributes;
|
|
30
|
+
if (!attrs || typeof attrs !== 'object') return new Set();
|
|
31
|
+
return new Set(Object.keys(attrs));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Extracts paths from attribute expressions (e.g. {{ metadata.email }}).
|
|
36
|
+
* Skips record_ref: expressions.
|
|
37
|
+
* - topLevelKeys: first segment of each path (e.g. "metadata" from "metadata.id").
|
|
38
|
+
* - referencedSchemaPropertyNames: for paths like "metadata.xxx" or "metadata.xxx.yyy", the name "xxx"
|
|
39
|
+
* (the first property under metadata). Used to prune metadataSchema.properties so we only keep
|
|
40
|
+
* properties that are referenced; we do not compare against "metadata" or the schema would be wiped.
|
|
41
|
+
*
|
|
42
|
+
* @param {Object} attributes - fieldMappings.attributes object
|
|
43
|
+
* @returns {{ paths: string[], topLevelKeys: Set<string>, referencedSchemaPropertyNames: Set<string> }}
|
|
44
|
+
*/
|
|
45
|
+
function parsePathsFromExpressions(attributes) {
|
|
46
|
+
const paths = [];
|
|
47
|
+
const topLevelKeys = new Set();
|
|
48
|
+
const referencedSchemaPropertyNames = new Set();
|
|
49
|
+
if (!attributes || typeof attributes !== 'object') return { paths, topLevelKeys, referencedSchemaPropertyNames };
|
|
50
|
+
for (const attr of Object.values(attributes)) {
|
|
51
|
+
const expr = attr?.expression;
|
|
52
|
+
if (typeof expr !== 'string') continue;
|
|
53
|
+
if (/^\s*record_ref:/i.test(expr.trim())) continue;
|
|
54
|
+
const match = expr.match(/\{\{\s*([^}]+)\s*\}\}/);
|
|
55
|
+
if (!match) continue;
|
|
56
|
+
const path = match[1].trim().split('|')[0].trim();
|
|
57
|
+
if (path) {
|
|
58
|
+
paths.push(path);
|
|
59
|
+
const segments = path.split('.');
|
|
60
|
+
const first = segments[0];
|
|
61
|
+
if (first) topLevelKeys.add(first);
|
|
62
|
+
if (first === 'metadata' && segments.length >= 2 && segments[1]) {
|
|
63
|
+
referencedSchemaPropertyNames.add(segments[1]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { paths, topLevelKeys, referencedSchemaPropertyNames };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Removes dimension entries whose value is metadata.<attr> and attr is not in fieldMappings.attributes.
|
|
72
|
+
* Mutates parsed.fieldMappings.dimensions.
|
|
73
|
+
*
|
|
74
|
+
* @param {Object} parsed - Parsed datasource (mutated)
|
|
75
|
+
* @param {string[]} changes - Array to append change descriptions to
|
|
76
|
+
* @returns {boolean} True if any dimension was removed
|
|
77
|
+
*/
|
|
78
|
+
function repairDimensionsFromAttributes(parsed, changes) {
|
|
79
|
+
const dims = parsed?.fieldMappings?.dimensions;
|
|
80
|
+
if (!dims || typeof dims !== 'object') return false;
|
|
81
|
+
const attributeKeys = getAttributeKeys(parsed);
|
|
82
|
+
let updated = false;
|
|
83
|
+
for (const [dimKey, value] of Object.entries(dims)) {
|
|
84
|
+
if (typeof value !== 'string') continue;
|
|
85
|
+
if (!value.startsWith('metadata.')) continue;
|
|
86
|
+
const attr = value.slice('metadata.'.length).trim();
|
|
87
|
+
if (!attr || attributeKeys.has(attr)) continue;
|
|
88
|
+
delete dims[dimKey];
|
|
89
|
+
changes.push(`Removed dimension '${dimKey}': ${value} not in fieldMappings.attributes`);
|
|
90
|
+
updated = true;
|
|
91
|
+
}
|
|
92
|
+
return updated;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Ensures metadataSchema exists (minimal stub if missing). If present, prunes top-level
|
|
97
|
+
* properties not referenced by any attribute expression. Uses the first property name under
|
|
98
|
+
* "metadata" in paths (e.g. metadata.id → "id") so we do not remove schema properties that
|
|
99
|
+
* are referenced. If no metadata.xxx paths exist, we do not prune (keep all properties).
|
|
100
|
+
*
|
|
101
|
+
* @param {Object} parsed - Parsed datasource (mutated)
|
|
102
|
+
* @param {string[]} changes - Array to append change descriptions to
|
|
103
|
+
* @returns {boolean} True if schema was added or pruned
|
|
104
|
+
*/
|
|
105
|
+
function repairMetadataSchemaFromAttributes(parsed, changes) {
|
|
106
|
+
const { referencedSchemaPropertyNames } = parsePathsFromExpressions(parsed?.fieldMappings?.attributes ?? {});
|
|
107
|
+
if (!parsed.metadataSchema || typeof parsed.metadataSchema !== 'object') {
|
|
108
|
+
parsed.metadataSchema = { ...MINIMAL_METADATA_SCHEMA };
|
|
109
|
+
changes.push('Added minimal metadataSchema (was missing)');
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
const props = parsed.metadataSchema.properties;
|
|
113
|
+
if (!props || typeof props !== 'object') return false;
|
|
114
|
+
if (referencedSchemaPropertyNames.size === 0) return false;
|
|
115
|
+
const toRemove = Object.keys(props).filter(k => !referencedSchemaPropertyNames.has(k));
|
|
116
|
+
if (toRemove.length === 0) return false;
|
|
117
|
+
toRemove.forEach(k => delete props[k]);
|
|
118
|
+
changes.push(`Pruned metadataSchema.properties: removed [${toRemove.join(', ')}] (not referenced by attributes)`);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Sets exposed.attributes to the list of fieldMappings.attributes keys (sorted).
|
|
124
|
+
* Only when options.expose is true; caller should gate.
|
|
125
|
+
*
|
|
126
|
+
* @param {Object} parsed - Parsed datasource (mutated)
|
|
127
|
+
* @param {string[]} changes - Array to append change descriptions to
|
|
128
|
+
* @returns {boolean} True if exposed was updated
|
|
129
|
+
*/
|
|
130
|
+
function repairExposeFromAttributes(parsed, changes) {
|
|
131
|
+
const keys = Array.from(getAttributeKeys(parsed)).filter(Boolean).sort();
|
|
132
|
+
if (keys.length === 0) return false;
|
|
133
|
+
if (!parsed.exposed) parsed.exposed = {};
|
|
134
|
+
const prev = parsed.exposed.attributes;
|
|
135
|
+
const same = Array.isArray(prev) && prev.length === keys.length && prev.every((v, i) => v === keys[i]);
|
|
136
|
+
if (same) return false;
|
|
137
|
+
parsed.exposed.attributes = keys;
|
|
138
|
+
changes.push(`Set exposed.attributes to [${keys.join(', ')}]`);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Adds default sync section if missing or empty.
|
|
144
|
+
*
|
|
145
|
+
* @param {Object} parsed - Parsed datasource (mutated)
|
|
146
|
+
* @param {string[]} changes - Array to append change descriptions to
|
|
147
|
+
* @returns {boolean} True if sync was added
|
|
148
|
+
*/
|
|
149
|
+
function repairSyncSection(parsed, changes) {
|
|
150
|
+
const sync = parsed.sync;
|
|
151
|
+
if (sync && typeof sync === 'object' && Object.keys(sync).length > 0) return false;
|
|
152
|
+
parsed.sync = { ...DEFAULT_SYNC };
|
|
153
|
+
changes.push('Added default sync section (mode: pull, batchSize: 500, maxParallelRequests: 5)');
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function placeholderForType(type) {
|
|
158
|
+
if (type === 'number' || type === 'integer') return 0;
|
|
159
|
+
if (type === 'boolean') return false;
|
|
160
|
+
if (type === 'array') return [];
|
|
161
|
+
if (type === 'object') return {};
|
|
162
|
+
return '';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function setNested(obj, pathParts, value) {
|
|
166
|
+
let cur = obj;
|
|
167
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
168
|
+
const p = pathParts[i];
|
|
169
|
+
if (!(p in cur) || typeof cur[p] !== 'object') cur[p] = {};
|
|
170
|
+
cur = cur[p];
|
|
171
|
+
}
|
|
172
|
+
const last = pathParts[pathParts.length - 1];
|
|
173
|
+
if (last) cur[last] = value;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Builds minimal payloadTemplate and expectedResult from attribute expression paths.
|
|
178
|
+
*
|
|
179
|
+
* @param {Object} parsed - Parsed datasource (mutated)
|
|
180
|
+
* @param {string[]} changes - Array to append change descriptions to
|
|
181
|
+
* @returns {boolean} True if testPayload was added or updated
|
|
182
|
+
*/
|
|
183
|
+
function repairTestPayload(parsed, changes) {
|
|
184
|
+
const attrs = parsed?.fieldMappings?.attributes;
|
|
185
|
+
if (!attrs || typeof attrs !== 'object') return false;
|
|
186
|
+
const payloadTemplate = {};
|
|
187
|
+
const expectedResult = {};
|
|
188
|
+
for (const [key, config] of Object.entries(attrs)) {
|
|
189
|
+
const type = config?.type || 'string';
|
|
190
|
+
const placeholder = placeholderForType(type);
|
|
191
|
+
expectedResult[key] = placeholder;
|
|
192
|
+
const match = config?.expression?.match(/\{\{\s*([^}|]+)/);
|
|
193
|
+
if (match) {
|
|
194
|
+
const path = match[1].trim();
|
|
195
|
+
setNested(payloadTemplate, path.split('.'), placeholder);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (!parsed.testPayload) parsed.testPayload = {};
|
|
199
|
+
parsed.testPayload.payloadTemplate = payloadTemplate;
|
|
200
|
+
parsed.testPayload.expectedResult = expectedResult;
|
|
201
|
+
changes.push('Generated testPayload.payloadTemplate and testPayload.expectedResult from attributes');
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Runs all requested datasource repairs. Core: dimensions + metadataSchema. Optional: expose, sync, testPayload.
|
|
207
|
+
*
|
|
208
|
+
* @param {Object} parsed - Parsed datasource object (mutated)
|
|
209
|
+
* @param {Object} options - { expose?: boolean, sync?: boolean, test?: boolean }
|
|
210
|
+
* @param {string[]} [changes] - Optional array to append change descriptions to
|
|
211
|
+
* @returns {{ updated: boolean, changes: string[] }}
|
|
212
|
+
*/
|
|
213
|
+
function repairDatasourceFile(parsed, options = {}, changes = []) {
|
|
214
|
+
const out = Array.isArray(changes) ? changes : [];
|
|
215
|
+
let updated = false;
|
|
216
|
+
updated = repairDimensionsFromAttributes(parsed, out) || updated;
|
|
217
|
+
updated = repairMetadataSchemaFromAttributes(parsed, out) || updated;
|
|
218
|
+
if (options.expose) updated = repairExposeFromAttributes(parsed, out) || updated;
|
|
219
|
+
if (options.sync) updated = repairSyncSection(parsed, out) || updated;
|
|
220
|
+
if (options.test) updated = repairTestPayload(parsed, out) || updated;
|
|
221
|
+
return { updated, changes: out };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = {
|
|
225
|
+
getAttributeKeys,
|
|
226
|
+
parsePathsFromExpressions,
|
|
227
|
+
repairDimensionsFromAttributes,
|
|
228
|
+
repairMetadataSchemaFromAttributes,
|
|
229
|
+
repairExposeFromAttributes,
|
|
230
|
+
repairSyncSection,
|
|
231
|
+
repairTestPayload,
|
|
232
|
+
repairDatasourceFile,
|
|
233
|
+
DEFAULT_SYNC,
|
|
234
|
+
MINIMAL_METADATA_SCHEMA
|
|
235
|
+
};
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for repairing env.template from system config (KV_* names, path-style kv://).
|
|
3
|
+
* @fileoverview Repair env.template to match system file
|
|
4
|
+
* @author AI Fabrix Team
|
|
5
|
+
* @version 2.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const { systemKeyToKvPrefix, kvEnvKeyToPath, securityKeyToVar } = require('../utils/credential-secrets-env');
|
|
13
|
+
const { extractEnvTemplate } = require('../generator/split');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Normalizes a keyvault config entry to canonical KV_* name and path-style value.
|
|
17
|
+
* Path format: kv://<system-key>/<variable> (e.g. kv://microsoft-teams/clientId).
|
|
18
|
+
* @param {Object} entry - Config entry with name, value, location
|
|
19
|
+
* @param {string} prefix - KV prefix (e.g. MICROSOFT_TEAMS)
|
|
20
|
+
* @param {string} systemKey - System key (e.g. microsoft-teams) for path namespace
|
|
21
|
+
* @returns {{ name: string, value: string }|null}
|
|
22
|
+
*/
|
|
23
|
+
function normalizeKeyvaultEntry(entry, prefix, systemKey) {
|
|
24
|
+
const afterPrefix = entry.name.startsWith(`KV_${prefix}_`)
|
|
25
|
+
? entry.name.slice(`KV_${prefix}_`.length)
|
|
26
|
+
: entry.name.replace(/^KV_[A-Z0-9]+_/, '');
|
|
27
|
+
const normalizedVar = afterPrefix.replace(/_/g, '').toUpperCase();
|
|
28
|
+
const normalizedName = `KV_${prefix}_${normalizedVar}`;
|
|
29
|
+
const pathVal = kvEnvKeyToPath(normalizedName, systemKey);
|
|
30
|
+
if (!pathVal) return null;
|
|
31
|
+
return {
|
|
32
|
+
name: normalizedName,
|
|
33
|
+
value: pathVal.replace(/^kv:\/\//, ''),
|
|
34
|
+
location: 'keyvault'
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Adds effective config entries from system configuration array.
|
|
40
|
+
* @param {Array} effective - Mutable result array
|
|
41
|
+
* @param {Array} config - System configuration array
|
|
42
|
+
* @param {string} prefix - KV prefix
|
|
43
|
+
* @param {Set<string>} seenNames - Mutable set of names already added
|
|
44
|
+
* @param {string} systemKey - System key for path format kv://systemKey/variable
|
|
45
|
+
*/
|
|
46
|
+
function addFromConfiguration(effective, config, prefix, seenNames, systemKey) {
|
|
47
|
+
for (const entry of config) {
|
|
48
|
+
if (!entry || !entry.name) continue;
|
|
49
|
+
if (entry.location === 'keyvault') {
|
|
50
|
+
const normalized = normalizeKeyvaultEntry(entry, prefix, systemKey);
|
|
51
|
+
if (normalized && !seenNames.has(normalized.name)) {
|
|
52
|
+
effective.push(normalized);
|
|
53
|
+
seenNames.add(normalized.name);
|
|
54
|
+
}
|
|
55
|
+
} else if (!seenNames.has(entry.name)) {
|
|
56
|
+
effective.push({
|
|
57
|
+
name: entry.name,
|
|
58
|
+
value: String(entry.value ?? ''),
|
|
59
|
+
location: entry.location
|
|
60
|
+
});
|
|
61
|
+
seenNames.add(entry.name);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Adds effective config entries from authentication.security.
|
|
68
|
+
* Path format: kv://<system-key>/<variable> (e.g. kv://microsoft-teams/clientId).
|
|
69
|
+
* @param {Array} effective - Mutable result array
|
|
70
|
+
* @param {Object} systemParsed - Parsed system config
|
|
71
|
+
* @param {string} prefix - KV prefix
|
|
72
|
+
* @param {Set<string>} seenNames - Mutable set of names already added
|
|
73
|
+
* @param {string} systemKey - System key for path namespace
|
|
74
|
+
*/
|
|
75
|
+
function addFromAuthSecurity(effective, systemParsed, prefix, seenNames, systemKey) {
|
|
76
|
+
const security = systemParsed.authentication?.security;
|
|
77
|
+
if (!security || typeof security !== 'object') return;
|
|
78
|
+
for (const key of Object.keys(security)) {
|
|
79
|
+
const envName = `KV_${prefix}_${securityKeyToVar(key)}`;
|
|
80
|
+
if (seenNames.has(envName)) continue;
|
|
81
|
+
const pathVal = kvEnvKeyToPath(envName, systemKey);
|
|
82
|
+
if (pathVal) {
|
|
83
|
+
effective.push({
|
|
84
|
+
name: envName,
|
|
85
|
+
value: pathVal.replace(/^kv:\/\//, ''),
|
|
86
|
+
location: 'keyvault'
|
|
87
|
+
});
|
|
88
|
+
seenNames.add(envName);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Builds effective configuration array from system (KV_* names, path-style kv://).
|
|
95
|
+
* @param {Object} systemParsed - Parsed system config
|
|
96
|
+
* @param {string} systemKey - System key (e.g. 'hubspot')
|
|
97
|
+
* @returns {Array<{ name: string, value: string, location?: string }>}
|
|
98
|
+
*/
|
|
99
|
+
function buildEffectiveConfiguration(systemParsed, systemKey) {
|
|
100
|
+
const effective = [];
|
|
101
|
+
const prefix = systemKeyToKvPrefix(systemKey);
|
|
102
|
+
if (!prefix) return effective;
|
|
103
|
+
const seenNames = new Set();
|
|
104
|
+
const config = Array.isArray(systemParsed.configuration) ? systemParsed.configuration : [];
|
|
105
|
+
addFromConfiguration(effective, config, prefix, seenNames, systemKey);
|
|
106
|
+
addFromAuthSecurity(effective, systemParsed, prefix, seenNames, systemKey);
|
|
107
|
+
return effective;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Builds map of env key -> full line (KEY=value) from effective config.
|
|
112
|
+
* @param {Array} effective - Effective configuration array
|
|
113
|
+
* @returns {Map<string, string>}
|
|
114
|
+
*/
|
|
115
|
+
function buildExpectedByKey(effective) {
|
|
116
|
+
const expectedByKey = new Map();
|
|
117
|
+
const lines = extractEnvTemplate(effective).split('\n').filter(Boolean);
|
|
118
|
+
for (const line of lines) {
|
|
119
|
+
const eq = line.indexOf('=');
|
|
120
|
+
if (eq > 0) {
|
|
121
|
+
const key = line.substring(0, eq).trim();
|
|
122
|
+
expectedByKey.set(key, `${key}=${line.substring(eq + 1)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return expectedByKey;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Creates env.template when missing. Returns true if created (and change pushed).
|
|
130
|
+
* @param {string} envPath - Path to env.template
|
|
131
|
+
* @param {Map<string, string>} expectedByKey - Expected key->line map
|
|
132
|
+
* @param {boolean} dryRun - If true, do not write
|
|
133
|
+
* @param {string[]} changes - Array to append to
|
|
134
|
+
* @returns {boolean}
|
|
135
|
+
*/
|
|
136
|
+
function createEnvTemplateIfMissing(envPath, expectedByKey, dryRun, changes) {
|
|
137
|
+
if (fs.existsSync(envPath)) return false;
|
|
138
|
+
const lines = Array.from(expectedByKey.values());
|
|
139
|
+
const content = lines.join('\n');
|
|
140
|
+
if (!content) return false;
|
|
141
|
+
if (!dryRun) {
|
|
142
|
+
fs.writeFileSync(envPath, content + '\n', { mode: 0o644, encoding: 'utf8' });
|
|
143
|
+
}
|
|
144
|
+
changes.push('Created env.template from system configuration');
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Extracts env key from a commented line (e.g. "# KEY=value" or "#KEY=value"). Returns null if not key=value.
|
|
150
|
+
* @param {string} trimmed - Trimmed line (starts with #)
|
|
151
|
+
* @returns {string|null} Key part after # and before =, or null
|
|
152
|
+
*/
|
|
153
|
+
function keyFromCommentedLine(trimmed) {
|
|
154
|
+
const afterHash = trimmed.slice(1).trim();
|
|
155
|
+
if (!afterHash.includes('=')) return null;
|
|
156
|
+
const eq = afterHash.indexOf('=');
|
|
157
|
+
if (eq <= 0) return null;
|
|
158
|
+
const key = afterHash.substring(0, eq).trim();
|
|
159
|
+
return key || null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Processes one line: keep as-is or replace with expected. Mutates updatedLines, keysWritten, changed.
|
|
164
|
+
* Preserves existing key=value in env.template: if a key already has a value, that value is never overwritten.
|
|
165
|
+
* Treats commented key=value (e.g. # KV_X=kv://...) as "already present" so repair does not add it again.
|
|
166
|
+
* Only missing keys from expectedByKey are added at the end.
|
|
167
|
+
* @param {string} line - Current line
|
|
168
|
+
* @param {Map<string, string>} expectedByKey - Expected key->line map
|
|
169
|
+
* @param {string[]} updatedLines - Mutable output lines
|
|
170
|
+
* @param {Set<string>} keysWritten - Mutable set of keys written
|
|
171
|
+
* @param {{ value: boolean }} _changedRef - Mutable changed flag (caller tracks changes)
|
|
172
|
+
*/
|
|
173
|
+
function processLine(line, expectedByKey, updatedLines, keysWritten, _changedRef) {
|
|
174
|
+
const trimmed = line.trim();
|
|
175
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
176
|
+
if (trimmed.startsWith('#')) {
|
|
177
|
+
const commentedKey = keyFromCommentedLine(trimmed);
|
|
178
|
+
if (commentedKey && expectedByKey.has(commentedKey)) keysWritten.add(commentedKey);
|
|
179
|
+
}
|
|
180
|
+
updatedLines.push(line);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const eq = line.indexOf('=');
|
|
184
|
+
if (eq <= 0) {
|
|
185
|
+
updatedLines.push(line);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const key = line.substring(0, eq).trim();
|
|
189
|
+
if (expectedByKey.has(key)) {
|
|
190
|
+
// Keep existing value; do not overwrite with expected (user may have set kv:// or custom value)
|
|
191
|
+
updatedLines.push(line);
|
|
192
|
+
keysWritten.add(key);
|
|
193
|
+
} else {
|
|
194
|
+
updatedLines.push(line);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Merges existing env.template content with expected key=value lines.
|
|
200
|
+
* Preserves comments, blank lines, and vars not in expectedByKey.
|
|
201
|
+
* @param {string} content - Current file content
|
|
202
|
+
* @param {Map<string, string>} expectedByKey - Expected key->line map
|
|
203
|
+
* @returns {{ output: string, changed: boolean }}
|
|
204
|
+
*/
|
|
205
|
+
function mergeEnvTemplateContent(content, expectedByKey) {
|
|
206
|
+
const lines = content.split(/\r?\n/);
|
|
207
|
+
const updatedLines = [];
|
|
208
|
+
const keysWritten = new Set();
|
|
209
|
+
const changedRef = { value: false };
|
|
210
|
+
|
|
211
|
+
for (const line of lines) {
|
|
212
|
+
processLine(line, expectedByKey, updatedLines, keysWritten, changedRef);
|
|
213
|
+
}
|
|
214
|
+
for (const key of expectedByKey.keys()) {
|
|
215
|
+
if (!keysWritten.has(key)) {
|
|
216
|
+
updatedLines.push(expectedByKey.get(key));
|
|
217
|
+
changedRef.value = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const output = updatedLines.join('\n') + (updatedLines.length > 0 ? '\n' : '');
|
|
222
|
+
return { output, changed: changedRef.value };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Repairs env.template so KV_ names and path-style kv:// values match the system file.
|
|
227
|
+
* @param {string} appPath - Application directory path
|
|
228
|
+
* @param {Object} systemParsed - Parsed system config
|
|
229
|
+
* @param {string} systemKey - System key
|
|
230
|
+
* @param {boolean} dryRun - If true, do not write
|
|
231
|
+
* @param {string[]} changes - Array to append change descriptions to
|
|
232
|
+
* @returns {boolean} True if env.template was repaired or created
|
|
233
|
+
*/
|
|
234
|
+
function repairEnvTemplate(appPath, systemParsed, systemKey, dryRun, changes) {
|
|
235
|
+
const effective = buildEffectiveConfiguration(systemParsed, systemKey);
|
|
236
|
+
const expectedByKey = buildExpectedByKey(effective);
|
|
237
|
+
const envPath = path.join(appPath, 'env.template');
|
|
238
|
+
|
|
239
|
+
if (createEnvTemplateIfMissing(envPath, expectedByKey, dryRun, changes)) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
if (!fs.existsSync(envPath)) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
247
|
+
const { output, changed } = mergeEnvTemplateContent(content, expectedByKey);
|
|
248
|
+
|
|
249
|
+
if (changed && !dryRun) {
|
|
250
|
+
fs.writeFileSync(envPath, output, { mode: 0o644, encoding: 'utf8' });
|
|
251
|
+
}
|
|
252
|
+
if (changed) {
|
|
253
|
+
changes.push('Repaired env.template (KV_* names and path-style kv:// values)');
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Returns true if a kv value looks like legacy format (KeyVault suffix or no path segments).
|
|
261
|
+
* @param {string} val - Value from authentication.security or configuration
|
|
262
|
+
* @returns {boolean}
|
|
263
|
+
*/
|
|
264
|
+
function isLegacyKvValue(val) {
|
|
265
|
+
if (typeof val !== 'string' || !val.trim().toLowerCase().startsWith('kv://')) return false;
|
|
266
|
+
const after = val.trim().slice(5); // after 'kv://'
|
|
267
|
+
return after.includes('KeyVault') || !after.includes('/');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Normalizes authentication.security values to path-style kv:// (kv://systemKey/variable).
|
|
272
|
+
* @param {Object} security - authentication.security object (mutated)
|
|
273
|
+
* @param {string} prefix - KV prefix (e.g. 'HUBSPOT')
|
|
274
|
+
* @param {string} systemKey - System key for path namespace
|
|
275
|
+
* @param {string[]} changes - Array to append change descriptions to
|
|
276
|
+
* @returns {boolean} True if any change was made
|
|
277
|
+
*/
|
|
278
|
+
function normalizeSecuritySection(security, prefix, systemKey, changes) {
|
|
279
|
+
let updated = false;
|
|
280
|
+
for (const key of Object.keys(security)) {
|
|
281
|
+
const val = security[key];
|
|
282
|
+
if (typeof val !== 'string' || !isLegacyKvValue(val)) continue;
|
|
283
|
+
const envName = `KV_${prefix}_${securityKeyToVar(key)}`;
|
|
284
|
+
const pathVal = kvEnvKeyToPath(envName, systemKey);
|
|
285
|
+
if (pathVal) {
|
|
286
|
+
security[key] = pathVal;
|
|
287
|
+
changes.push(`authentication.security.${key}: normalized to path-style ${pathVal}`);
|
|
288
|
+
updated = true;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return updated;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Normalizes configuration array keyvault entries to canonical KV_* names and path-style values.
|
|
296
|
+
* @param {Object[]} config - configuration array (mutated)
|
|
297
|
+
* @param {string} prefix - KV prefix (e.g. 'HUBSPOT')
|
|
298
|
+
* @param {string} systemKey - System key for path namespace
|
|
299
|
+
* @param {string[]} changes - Array to append change descriptions to
|
|
300
|
+
* @returns {boolean} True if any change was made
|
|
301
|
+
*/
|
|
302
|
+
function normalizeConfigurationSection(config, prefix, systemKey, changes) {
|
|
303
|
+
let updated = false;
|
|
304
|
+
for (let i = 0; i < config.length; i++) {
|
|
305
|
+
const entry = config[i];
|
|
306
|
+
if (!entry || !entry.name || (entry.location !== 'keyvault' && !String(entry.name).startsWith('KV_'))) continue;
|
|
307
|
+
const afterPrefix = entry.name.startsWith(`KV_${prefix}_`)
|
|
308
|
+
? entry.name.slice(`KV_${prefix}_`.length)
|
|
309
|
+
: entry.name.replace(/^KV_[A-Z0-9]+_/, '');
|
|
310
|
+
const normalizedVar = afterPrefix.replace(/_/g, '').toUpperCase();
|
|
311
|
+
const canonicalName = `KV_${prefix}_${normalizedVar}`;
|
|
312
|
+
const pathVal = kvEnvKeyToPath(canonicalName, systemKey);
|
|
313
|
+
if (!pathVal) continue;
|
|
314
|
+
const pathValWithoutPrefix = pathVal.replace(/^kv:\/\//, '');
|
|
315
|
+
const valueLegacy = typeof entry.value === 'string' && (entry.value.includes('KeyVault') || !entry.value.includes('/'));
|
|
316
|
+
if (entry.name !== canonicalName || (valueLegacy && entry.value !== pathValWithoutPrefix)) {
|
|
317
|
+
config[i] = { ...entry, name: canonicalName, value: pathValWithoutPrefix, location: 'keyvault' };
|
|
318
|
+
changes.push(`configuration: normalized ${entry.name} → ${canonicalName}, value → path-style`);
|
|
319
|
+
updated = true;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return updated;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Normalizes system file authentication.security and configuration keyvault entries.
|
|
327
|
+
* @param {Object} systemParsed - Parsed system config (mutated)
|
|
328
|
+
* @param {string} systemKey - System key (e.g. 'hubspot')
|
|
329
|
+
* @param {string[]} changes - Array to append change descriptions to
|
|
330
|
+
* @returns {boolean} True if any change was made
|
|
331
|
+
*/
|
|
332
|
+
function normalizeSystemFileAuthAndConfig(systemParsed, systemKey, changes) {
|
|
333
|
+
const prefix = systemKeyToKvPrefix(systemKey);
|
|
334
|
+
if (!prefix) return false;
|
|
335
|
+
const security = systemParsed.authentication?.security;
|
|
336
|
+
let updated = (security && typeof security === 'object' && normalizeSecuritySection(security, prefix, systemKey, changes));
|
|
337
|
+
const config = systemParsed.configuration;
|
|
338
|
+
if (Array.isArray(config)) {
|
|
339
|
+
updated = normalizeConfigurationSection(config, prefix, systemKey, changes) || updated;
|
|
340
|
+
}
|
|
341
|
+
return updated;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
module.exports = {
|
|
345
|
+
buildEffectiveConfiguration,
|
|
346
|
+
repairEnvTemplate,
|
|
347
|
+
normalizeSystemFileAuthAndConfig
|
|
348
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helpers for repair command: file discovery and datasource list building.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Repair discovery and datasource helpers
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.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
|
+
|
|
16
|
+
/** Matches *-datasource-*.(yaml|yml|json) or datasource-*.(yaml|yml|json) */
|
|
17
|
+
function isDatasourceFileName(name) {
|
|
18
|
+
if (!/\.(yaml|yml|json)$/i.test(name)) return false;
|
|
19
|
+
return /-datasource-.+\.(yaml|yml|json)$/i.test(name) || /^datasource-.+\.(yaml|yml|json)$/i.test(name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Discovers system and datasource files in app directory
|
|
24
|
+
* @param {string} appPath - Application directory path
|
|
25
|
+
* @returns {{ systemFiles: string[], datasourceFiles: string[] }}
|
|
26
|
+
*/
|
|
27
|
+
function discoverIntegrationFiles(appPath) {
|
|
28
|
+
if (!fs.existsSync(appPath)) {
|
|
29
|
+
return { systemFiles: [], datasourceFiles: [] };
|
|
30
|
+
}
|
|
31
|
+
const entries = fs.readdirSync(appPath);
|
|
32
|
+
const systemFiles = [];
|
|
33
|
+
const datasourceFiles = [];
|
|
34
|
+
for (const name of entries) {
|
|
35
|
+
if (!/^[a-z0-9_.-]+\.(yaml|yml|json)$/i.test(name)) continue;
|
|
36
|
+
if (/-system\.(yaml|yml|json)$/i.test(name)) {
|
|
37
|
+
systemFiles.push(name);
|
|
38
|
+
} else if (isDatasourceFileName(name)) {
|
|
39
|
+
datasourceFiles.push(name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
systemFiles.sort();
|
|
43
|
+
datasourceFiles.sort();
|
|
44
|
+
return { systemFiles, datasourceFiles };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Builds the effective datasource file list from application.yaml and discovered files.
|
|
49
|
+
* @param {string} appPath - Application directory path
|
|
50
|
+
* @param {string[]} discoveredDatasourceFiles - Filenames from discoverIntegrationFiles
|
|
51
|
+
* @param {string[]} [existingDataSources] - externalIntegration.dataSources from application.yaml
|
|
52
|
+
* @returns {string[]} Sorted, deduplicated list of datasource filenames
|
|
53
|
+
*/
|
|
54
|
+
function buildEffectiveDatasourceFiles(appPath, discoveredDatasourceFiles, existingDataSources) {
|
|
55
|
+
const existing = Array.isArray(existingDataSources) ? existingDataSources : [];
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const result = [];
|
|
58
|
+
for (const name of existing) {
|
|
59
|
+
if (!name || typeof name !== 'string') continue;
|
|
60
|
+
const trimmed = name.trim();
|
|
61
|
+
if (!trimmed || seen.has(trimmed)) continue;
|
|
62
|
+
const filePath = path.join(appPath, trimmed);
|
|
63
|
+
if (fs.existsSync(filePath)) {
|
|
64
|
+
result.push(trimmed);
|
|
65
|
+
seen.add(trimmed);
|
|
66
|
+
} else {
|
|
67
|
+
logger.log(chalk.yellow(`⚠ Datasource file referenced in application.yaml not found: ${trimmed}`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const name of discoveredDatasourceFiles) {
|
|
71
|
+
if (seen.has(name)) continue;
|
|
72
|
+
const filePath = path.join(appPath, name);
|
|
73
|
+
if (fs.existsSync(filePath)) {
|
|
74
|
+
result.push(name);
|
|
75
|
+
seen.add(name);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
result.sort();
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
discoverIntegrationFiles,
|
|
84
|
+
buildEffectiveDatasourceFiles
|
|
85
|
+
};
|