@aifabrix/builder 2.42.1 → 2.43.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/README.md +1 -1
- package/bin/aifabrix.js +1 -1
- package/integration/hubspot-test/README.md +126 -0
- package/integration/{hubspot → hubspot-test}/application.json +6 -6
- package/integration/{hubspot → hubspot-test}/create-hubspot.js +5 -5
- package/integration/hubspot-test/env.template +4 -0
- package/integration/{hubspot/hubspot-datasource-company.json → hubspot-test/hubspot-test-datasource-company.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-contact.json → hubspot-test/hubspot-test-datasource-contact.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-deal.json → hubspot-test/hubspot-test-datasource-deal.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-users.json → hubspot-test/hubspot-test-datasource-users.json} +3 -2
- package/integration/{hubspot/hubspot-deploy.json → hubspot-test/hubspot-test-deploy.json} +198 -21
- package/integration/{hubspot/hubspot-system.json → hubspot-test/hubspot-test-system.json} +8 -7
- package/integration/hubspot-test/rbac.json +166 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-credential-real.yaml +3 -3
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-env-vars.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-add-datasource.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-create.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-select.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-known-platform.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-mode.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-file.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-url.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-source.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-array-test.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test.js +102 -59
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-e2e.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-platform.yaml +1 -1
- package/lib/api/external-test.api.js +1 -1
- package/lib/api/service-users.api.js +111 -2
- package/lib/api/types/service-users.types.js +41 -0
- package/lib/app/register.js +3 -1
- package/lib/app/rotate-secret.js +3 -0
- package/lib/cli/setup-app.js +2 -2
- package/lib/cli/setup-auth.js +19 -11
- package/lib/cli/setup-dev.js +62 -32
- package/lib/cli/setup-environment.js +6 -21
- package/lib/cli/setup-infra.js +13 -0
- package/lib/cli/setup-secrets.js +45 -6
- package/lib/cli/setup-service-user.js +146 -20
- package/lib/cli/setup-utility.js +12 -0
- package/lib/commands/auth-config.js +4 -8
- package/lib/commands/datasource.js +46 -1
- package/lib/commands/dev-init.js +1 -1
- package/lib/commands/repair-env-template.js +14 -8
- package/lib/commands/repair-rbac.js +25 -19
- package/lib/commands/repair.js +96 -30
- package/lib/commands/secrets-remove.js +1 -1
- package/lib/commands/secrets-validate.js +17 -4
- package/lib/commands/service-user.js +231 -2
- package/lib/commands/up-common.js +25 -0
- package/lib/commands/up-dataplane.js +2 -2
- package/lib/core/admin-secrets.js +2 -0
- package/lib/core/config.js +7 -5
- package/lib/core/ensure-encryption-key.js +1 -3
- package/lib/core/secrets.js +32 -9
- package/lib/core/templates.js +1 -1
- package/lib/datasource/abac-validator.js +157 -0
- package/lib/datasource/field-reference-validator.js +74 -36
- package/lib/datasource/log-viewer.js +221 -0
- package/lib/datasource/resolve-app.js +109 -0
- package/lib/datasource/test-e2e.js +11 -20
- package/lib/datasource/test-integration.js +42 -22
- package/lib/datasource/validate.js +5 -2
- package/lib/external-system/generator.js +12 -8
- package/lib/external-system/test-system-level.js +1 -1
- package/lib/generator/external-controller-manifest.js +3 -3
- package/lib/generator/external.js +7 -7
- package/lib/generator/helpers.js +13 -9
- package/lib/generator/index.js +4 -4
- package/lib/generator/split.js +45 -10
- package/lib/generator/wizard.js +9 -6
- package/lib/infrastructure/helpers.js +50 -35
- package/lib/infrastructure/index.js +39 -23
- package/lib/schema/env-config.yaml +19 -2
- package/lib/schema/external-datasource.schema.json +11 -1
- package/lib/utils/app-config-resolver.js +23 -1
- package/lib/utils/config-paths.js +48 -4
- package/lib/utils/credential-secrets-env.js +16 -1
- package/lib/utils/env-map.js +7 -3
- package/lib/utils/error-formatter.js +37 -0
- package/lib/utils/external-env-template.js +180 -0
- package/lib/utils/external-system-display.js +43 -0
- package/lib/utils/external-system-validators.js +2 -2
- package/lib/utils/help-builder.js +3 -5
- package/lib/utils/local-secrets.js +26 -3
- package/lib/utils/paths.js +2 -1
- package/lib/utils/secrets-generator.js +2 -2
- package/lib/utils/secrets-utils.js +4 -0
- package/lib/utils/secure-file-permissions.js +91 -0
- package/lib/utils/token-manager.js +36 -3
- package/lib/utils/yaml-preserve.js +59 -1
- package/lib/validation/env-template-auth.js +50 -2
- package/lib/validation/external-manifest-validator.js +8 -0
- package/lib/validation/validate.js +8 -0
- package/lib/validation/validator.js +10 -13
- package/package.json +5 -1
- package/templates/applications/dataplane/env.template +5 -1
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +13 -2
- package/templates/external-system/env.template.hbs +22 -0
- package/integration/hubspot/README.md +0 -102
- package/integration/hubspot/env.template +0 -4
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
- /package/integration/{hubspot → hubspot-test}/companies.json +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-app-name.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-missing-app.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-helpers.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-tests.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down.js +0 -0
|
@@ -16,11 +16,19 @@ environments:
|
|
|
16
16
|
REDIS_PORT: 6379 # Internal port (container-to-container). REDIS_PUBLIC_PORT calculated automatically.
|
|
17
17
|
MISO_HOST: miso-controller
|
|
18
18
|
MISO_PORT: 3000 # Internal port (container-to-container). MISO_PUBLIC_PORT calculated automatically.
|
|
19
|
+
MISO_PUBLIC_PORT: 3000
|
|
19
20
|
KEYCLOAK_HOST: keycloak
|
|
20
|
-
KEYCLOAK_PORT:
|
|
21
|
+
KEYCLOAK_PORT: 8080 # Internal port (container-to-container). KEYCLOAK_PUBLIC_PORT calculated automatically.
|
|
21
22
|
KEYCLOAK_PUBLIC_PORT: 8082
|
|
23
|
+
MORI_HOST: mori-controller
|
|
24
|
+
MORI_PORT: 3004
|
|
25
|
+
OPENWEBUI_HOST: openwebui
|
|
26
|
+
OPENWEBUI_PORT: 3003
|
|
27
|
+
FLOWISE_HOST: flowise
|
|
28
|
+
FLOWISE_PORT: 3002
|
|
22
29
|
DATAPLANE_HOST: dataplane
|
|
23
|
-
DATAPLANE_PORT: 3001
|
|
30
|
+
DATAPLANE_PORT: 3001 # Internal port (container-to-container). DATAPLANE_PUBLIC_PORT calculated automatically.
|
|
31
|
+
DATAPLANE_PUBLIC_PORT: 3001
|
|
24
32
|
NODE_ENV: production
|
|
25
33
|
PYTHONUNBUFFERED: 1
|
|
26
34
|
PYTHONDONTWRITEBYTECODE: 1
|
|
@@ -33,10 +41,19 @@ environments:
|
|
|
33
41
|
REDIS_PORT: 6379
|
|
34
42
|
MISO_HOST: localhost
|
|
35
43
|
MISO_PORT: 3010
|
|
44
|
+
MISO_PUBLIC_PORT: 3010
|
|
36
45
|
KEYCLOAK_HOST: localhost
|
|
37
46
|
KEYCLOAK_PORT: 8082
|
|
47
|
+
KEYCLOAK_PUBLIC_PORT: 8082
|
|
48
|
+
MORI_HOST: localhost
|
|
49
|
+
MORI_PORT: 3014
|
|
50
|
+
OPENWEBUI_HOST: localhost
|
|
51
|
+
OPENWEBUI_PORT: 3013
|
|
52
|
+
FLOWISE_HOST: localhost
|
|
53
|
+
FLOWISE_PORT: 3012
|
|
38
54
|
DATAPLANE_HOST: localhost
|
|
39
55
|
DATAPLANE_PORT: 3011
|
|
56
|
+
DATAPLANE_PUBLIC_PORT: 3011
|
|
40
57
|
NODE_ENV: development
|
|
41
58
|
PYTHONUNBUFFERED: 1
|
|
42
59
|
PYTHONDONTWRITEBYTECODE: 1
|
|
@@ -544,7 +544,7 @@
|
|
|
544
544
|
"properties":{
|
|
545
545
|
"rejectIf":{
|
|
546
546
|
"type":"array",
|
|
547
|
-
"description":"List of conditions that cause a record to be rejected.",
|
|
547
|
+
"description":"List of conditions that cause a record to be rejected. For lessThan: missing field is treated as reject. For greaterThan: missing field is not rejected. See quality docs for operator semantics.",
|
|
548
548
|
"items":{
|
|
549
549
|
"type":"object",
|
|
550
550
|
"required":[
|
|
@@ -659,6 +659,11 @@
|
|
|
659
659
|
"default":true,
|
|
660
660
|
"description":"Enable two-phase sync pattern. When true: validates metadata first (quality rules, comparison with DocumentRecords), then fetches binaries via CIP for changed/new documents. When false: fetches binaries directly without metadata validation phase (single-phase sync). Note: Files are never synced back to external systems (one-way sync only: external → dataplane)."
|
|
661
661
|
},
|
|
662
|
+
"ingestAfterSync":{
|
|
663
|
+
"type":"boolean",
|
|
664
|
+
"default":false,
|
|
665
|
+
"description":"When true, chunk and embed each document after store during sync so vector search returns hits immediately. When false, ingestion runs later (e.g. Celery task or on approval). Set true for E2E tests that validate vector step."
|
|
666
|
+
},
|
|
662
667
|
"binaryOperationRef":{
|
|
663
668
|
"type":"string",
|
|
664
669
|
"default":"get",
|
|
@@ -700,6 +705,11 @@
|
|
|
700
705
|
},
|
|
701
706
|
"notifications":{
|
|
702
707
|
"type":"object"
|
|
708
|
+
},
|
|
709
|
+
"ingestAfterSync":{
|
|
710
|
+
"type":"boolean",
|
|
711
|
+
"default":false,
|
|
712
|
+
"description":"When true, chunk and embed each document after store during sync so vector search returns hits."
|
|
703
713
|
}
|
|
704
714
|
},
|
|
705
715
|
"additionalProperties":false
|
|
@@ -49,4 +49,26 @@ function resolveApplicationConfigPath(appPath) {
|
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
const RBAC_NAMES = ['rbac.yaml', 'rbac.yml', 'rbac.json'];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolves path to RBAC config file (rbac.yaml, rbac.yml, or rbac.json).
|
|
56
|
+
* Returns the first path that exists; no renames or migrations.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} appPath - Absolute path to application directory
|
|
59
|
+
* @returns {string|null} Absolute path to RBAC file, or null if none exist
|
|
60
|
+
*/
|
|
61
|
+
function resolveRbacPath(appPath) {
|
|
62
|
+
if (!appPath || typeof appPath !== 'string') {
|
|
63
|
+
throw new Error('App path is required and must be a string');
|
|
64
|
+
}
|
|
65
|
+
for (const name of RBAC_NAMES) {
|
|
66
|
+
const candidate = path.join(appPath, name);
|
|
67
|
+
if (fs.existsSync(candidate)) {
|
|
68
|
+
return candidate;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { resolveApplicationConfigPath, resolveRbacPath };
|
|
@@ -56,30 +56,73 @@ async function setPathConfig(getConfigFn, saveConfigFn, key, value, errorMsg) {
|
|
|
56
56
|
await saveConfigFn(config);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Clear a path config key (set to undefined so getPathConfig returns null).
|
|
61
|
+
* @param {Function} getConfigFn - Function to get config
|
|
62
|
+
* @param {Function} saveConfigFn - Function to save config
|
|
63
|
+
* @param {string} key - Configuration key
|
|
64
|
+
* @returns {Promise<void>}
|
|
65
|
+
*/
|
|
66
|
+
async function clearPathConfig(getConfigFn, saveConfigFn, key) {
|
|
67
|
+
const config = await getConfigFn();
|
|
68
|
+
config[key] = undefined;
|
|
69
|
+
await saveConfigFn(config);
|
|
70
|
+
}
|
|
71
|
+
|
|
59
72
|
function createHomeAndSecretsPathFunctions(getConfigFn, saveConfigFn) {
|
|
60
73
|
return {
|
|
61
74
|
async getAifabrixHomeOverride() {
|
|
62
75
|
return getPathConfig(getConfigFn, 'aifabrix-home');
|
|
63
76
|
},
|
|
64
77
|
async setAifabrixHomeOverride(homePath) {
|
|
65
|
-
|
|
78
|
+
if (typeof homePath !== 'string') {
|
|
79
|
+
throw new Error('Home path is required and must be a string');
|
|
80
|
+
}
|
|
81
|
+
const trimmed = homePath.trim();
|
|
82
|
+
if (trimmed === '') {
|
|
83
|
+
await clearPathConfig(getConfigFn, saveConfigFn, 'aifabrix-home');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-home', trimmed, 'Home path must be a non-empty string');
|
|
66
87
|
},
|
|
67
88
|
async getAifabrixSecretsPath() {
|
|
68
89
|
return getPathConfig(getConfigFn, 'aifabrix-secrets');
|
|
69
90
|
},
|
|
70
91
|
async setAifabrixSecretsPath(secretsPath) {
|
|
71
|
-
|
|
92
|
+
if (typeof secretsPath !== 'string') {
|
|
93
|
+
throw new Error('Secrets path is required and must be a string');
|
|
94
|
+
}
|
|
95
|
+
const trimmed = secretsPath.trim();
|
|
96
|
+
if (trimmed === '') {
|
|
97
|
+
await clearPathConfig(getConfigFn, saveConfigFn, 'aifabrix-secrets');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-secrets', trimmed, 'Secrets path must be a non-empty string');
|
|
72
101
|
}
|
|
73
102
|
};
|
|
74
103
|
}
|
|
75
104
|
|
|
105
|
+
/** Default env-config path when aifabrix-env-config is not set (builder schema). */
|
|
106
|
+
function getDefaultEnvConfigPath() {
|
|
107
|
+
return path.join(__dirname, '..', 'schema', 'env-config.yaml');
|
|
108
|
+
}
|
|
109
|
+
|
|
76
110
|
function createEnvConfigPathFunctions(getConfigFn, saveConfigFn) {
|
|
77
111
|
return {
|
|
78
112
|
async getAifabrixEnvConfigPath() {
|
|
79
|
-
|
|
113
|
+
const value = await getPathConfig(getConfigFn, 'aifabrix-env-config');
|
|
114
|
+
return value || getDefaultEnvConfigPath();
|
|
80
115
|
},
|
|
81
116
|
async setAifabrixEnvConfigPath(envConfigPath) {
|
|
82
|
-
|
|
117
|
+
if (typeof envConfigPath !== 'string') {
|
|
118
|
+
throw new Error('Env config path is required and must be a string');
|
|
119
|
+
}
|
|
120
|
+
const trimmed = envConfigPath.trim();
|
|
121
|
+
if (trimmed === '') {
|
|
122
|
+
await clearPathConfig(getConfigFn, saveConfigFn, 'aifabrix-env-config');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await setPathConfig(getConfigFn, saveConfigFn, 'aifabrix-env-config', trimmed, 'Env config path must be a non-empty string');
|
|
83
126
|
},
|
|
84
127
|
async getAifabrixBuilderDir() {
|
|
85
128
|
const envConfigPath = await getPathConfig(getConfigFn, 'aifabrix-env-config');
|
|
@@ -214,6 +257,7 @@ module.exports = {
|
|
|
214
257
|
getPathConfig,
|
|
215
258
|
setPathConfig,
|
|
216
259
|
createPathConfigFunctions,
|
|
260
|
+
getDefaultEnvConfigPath,
|
|
217
261
|
SETTINGS_RESPONSE_KEYS
|
|
218
262
|
};
|
|
219
263
|
|
|
@@ -92,6 +92,17 @@ function kvPathInferred(segments) {
|
|
|
92
92
|
return (namespace && pathVar) ? `kv://${namespace}/${pathVar}` : null;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Returns the path segment used in kv://<systemKey>/<segment> for a given security key.
|
|
97
|
+
* Uses the same derivation as env key → path (securityKeyToVar + varSegmentsToCamelCase).
|
|
98
|
+
* @param {string} securityKey - Security key (e.g. 'apiKey', 'clientId', 'clientSecret')
|
|
99
|
+
* @returns {string} Canonical path segment (e.g. 'apiKey', 'clientId')
|
|
100
|
+
*/
|
|
101
|
+
function getKvPathSegmentForSecurityKey(securityKey) {
|
|
102
|
+
if (!securityKey || typeof securityKey !== 'string') return '';
|
|
103
|
+
return varSegmentsToCamelCase([securityKeyToVar(securityKey)]);
|
|
104
|
+
}
|
|
105
|
+
|
|
95
106
|
/**
|
|
96
107
|
* Converts KV_* env key to kv:// path in format kv://<system-key>/<variable>.
|
|
97
108
|
* System-key uses hyphens (e.g. microsoft-teams); variable is camelCase (e.g. clientId).
|
|
@@ -220,7 +231,10 @@ function buildItemsFromEnv(envFilePath, secrets, itemsByKey) {
|
|
|
220
231
|
const fromEnv = collectKvEnvVarsAsSecretItems(envMap);
|
|
221
232
|
for (const { key, value } of fromEnv) {
|
|
222
233
|
const resolved = resolveKvValue(secrets, value);
|
|
223
|
-
|
|
234
|
+
// Skip placeholder: value that equals the kv path (e.g. from env.template) must not be pushed as the secret
|
|
235
|
+
if (resolved !== null && resolved !== undefined && isValidKvPath(key) && resolved.trim() !== key.trim()) {
|
|
236
|
+
itemsByKey.set(key, resolved);
|
|
237
|
+
}
|
|
224
238
|
}
|
|
225
239
|
} catch {
|
|
226
240
|
// Best-effort: continue without .env items
|
|
@@ -349,6 +363,7 @@ module.exports = {
|
|
|
349
363
|
collectKvRefsFromPayload,
|
|
350
364
|
pushCredentialSecrets,
|
|
351
365
|
kvEnvKeyToPath,
|
|
366
|
+
getKvPathSegmentForSecurityKey,
|
|
352
367
|
systemKeyToKvPrefix,
|
|
353
368
|
securityKeyToVar,
|
|
354
369
|
isValidKvPath,
|
package/lib/utils/env-map.js
CHANGED
|
@@ -270,9 +270,13 @@ function calculateDockerPublicPorts(result, devIdNum, schemaBaseVars = {}) {
|
|
|
270
270
|
// Match any variable ending with _PORT (e.g., MISO_PORT, KEYCLOAK_PORT, DB_PORT)
|
|
271
271
|
if (/_PORT$/.test(key) && !/_PUBLIC_PORT$/.test(key)) {
|
|
272
272
|
const publicPortKey = key.replace(/_PORT$/, '_PUBLIC_PORT');
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
const
|
|
273
|
+
// Prefer schema *_PUBLIC_PORT (e.g. KEYCLOAK_PUBLIC_PORT: 8082) so public port is canonical;
|
|
274
|
+
// fall back to schema *_PORT (e.g. KEYCLOAK_PORT: 8080) then merged value
|
|
275
|
+
const schemaPublic = schemaBaseVars[publicPortKey];
|
|
276
|
+
const schemaInternal = schemaBaseVars[key];
|
|
277
|
+
const sourceVal = schemaPublic !== undefined && schemaPublic !== null
|
|
278
|
+
? schemaPublic
|
|
279
|
+
: (schemaInternal !== undefined && schemaInternal !== null ? schemaInternal : value);
|
|
276
280
|
let portVal;
|
|
277
281
|
if (typeof sourceVal === 'string') {
|
|
278
282
|
portVal = parseInt(sourceVal, 10);
|
|
@@ -19,6 +19,8 @@ const PATTERN_DESCRIPTIONS = {
|
|
|
19
19
|
'^[a-z-]+$': 'lowercase letters and hyphens only',
|
|
20
20
|
'^[A-Z_][A-Z0-9_]*$': 'uppercase letters, numbers, and underscores (must start with letter or underscore)',
|
|
21
21
|
'^[a-zA-Z0-9_-]+$': 'letters, numbers, hyphens, and underscores only',
|
|
22
|
+
'^[a-zA-Z0-9_]+$': 'letters, numbers, and underscores only',
|
|
23
|
+
'^[a-zA-Z0-9_.]+$': 'letters, numbers, underscores, and dots only',
|
|
22
24
|
'^(http|https)://.*$': 'valid HTTP or HTTPS URL',
|
|
23
25
|
'^/[a-z0-9/-]*$': 'URL path starting with / (lowercase letters, numbers, hyphens, slashes)'
|
|
24
26
|
};
|
|
@@ -132,6 +134,35 @@ function createKeywordFormatters(field, error) {
|
|
|
132
134
|
* @param {Object} error - Raw validation error from Ajv
|
|
133
135
|
* @returns {string} Formatted error message
|
|
134
136
|
*/
|
|
137
|
+
/**
|
|
138
|
+
* Formats oneOf/anyOf validation errors with actionable message
|
|
139
|
+
* @param {string} field - Field name
|
|
140
|
+
* @param {Object} error - AJV error (keyword oneOf or anyOf)
|
|
141
|
+
* @returns {string} Formatted error message
|
|
142
|
+
*/
|
|
143
|
+
function formatOneOfAnyOfError(field, error) {
|
|
144
|
+
const instancePath = (error.instancePath || '').replace(/^\//, '');
|
|
145
|
+
if (instancePath === 'capabilities') {
|
|
146
|
+
return `${field}: must be either an array of operation names (e.g. ["list","get"]) or an object with boolean flags (e.g. { "list": true }).`;
|
|
147
|
+
}
|
|
148
|
+
return `${field}: value does not match any allowed shape. Check type and required fields.`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Formats const validation errors
|
|
153
|
+
* @param {string} field - Field name
|
|
154
|
+
* @param {Object} error - AJV error (keyword const)
|
|
155
|
+
* @returns {string} Formatted error message
|
|
156
|
+
*/
|
|
157
|
+
function formatConstError(field, error) {
|
|
158
|
+
const allowed = error.params?.allowedValue;
|
|
159
|
+
if (allowed !== undefined) {
|
|
160
|
+
const display = typeof allowed === 'string' ? `"${allowed}"` : String(allowed);
|
|
161
|
+
return `${field}: must be exactly ${display}`;
|
|
162
|
+
}
|
|
163
|
+
return `${field}: invalid value (constraint violation)`;
|
|
164
|
+
}
|
|
165
|
+
|
|
135
166
|
function formatSingleError(error) {
|
|
136
167
|
const field = getFieldName(error);
|
|
137
168
|
|
|
@@ -142,6 +173,12 @@ function formatSingleError(error) {
|
|
|
142
173
|
if (error.keyword === 'additionalProperties') {
|
|
143
174
|
return formatAdditionalPropertiesError(field, error);
|
|
144
175
|
}
|
|
176
|
+
if (error.keyword === 'oneOf' || error.keyword === 'anyOf') {
|
|
177
|
+
return formatOneOfAnyOfError(field, error);
|
|
178
|
+
}
|
|
179
|
+
if (error.keyword === 'const') {
|
|
180
|
+
return formatConstError(field, error);
|
|
181
|
+
}
|
|
145
182
|
|
|
146
183
|
// Use object lookup for keyword-specific messages
|
|
147
184
|
const formatters = createKeywordFormatters(field, error);
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds Handlebars context and generates env.template content for external systems.
|
|
3
|
+
* Single source for create, download, split, and repair so env.template structure is consistent.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview External system env.template generation
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const Handlebars = require('handlebars');
|
|
15
|
+
const { systemKeyToKvPrefix, kvEnvKeyToPath, securityKeyToVar } = require('./credential-secrets-env');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Builds hint string from portalInput (options → enum, validation → min-max or pattern).
|
|
19
|
+
* @param {Object} portalInput - Portal input config (label, options, validation)
|
|
20
|
+
* @returns {string} Hint suffix for comment
|
|
21
|
+
*/
|
|
22
|
+
function buildPortalInputHint(portalInput) {
|
|
23
|
+
if (!portalInput || typeof portalInput !== 'object') return '';
|
|
24
|
+
const parts = [];
|
|
25
|
+
if (Array.isArray(portalInput.options) && portalInput.options.length > 0) {
|
|
26
|
+
parts.push(`enum ${portalInput.options.join(',')}`);
|
|
27
|
+
}
|
|
28
|
+
const v = portalInput.validation;
|
|
29
|
+
if (v && typeof v === 'object') {
|
|
30
|
+
if (typeof v.minLength === 'number' || typeof v.maxLength === 'number') {
|
|
31
|
+
parts.push('min-max');
|
|
32
|
+
} else if (typeof v.pattern === 'string' && v.pattern) {
|
|
33
|
+
parts.push('pattern');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return parts.length ? ` - ${parts.join(', ')}` : '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Fallback security keys by auth method when authentication.security is absent. */
|
|
40
|
+
const FALLBACK_SECURITY_BY_AUTH = {
|
|
41
|
+
oauth2: ['clientId', 'clientSecret'],
|
|
42
|
+
oauth: ['clientId', 'clientSecret'],
|
|
43
|
+
aad: ['clientId', 'clientSecret'],
|
|
44
|
+
apikey: ['apiKey'],
|
|
45
|
+
apiKey: ['apiKey'],
|
|
46
|
+
basic: ['username', 'password'],
|
|
47
|
+
queryParam: ['paramValue'],
|
|
48
|
+
oidc: [],
|
|
49
|
+
hmac: ['signingSecret'],
|
|
50
|
+
bearer: ['bearerToken'],
|
|
51
|
+
token: ['bearerToken'],
|
|
52
|
+
none: []
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Builds authSecureVars array from system authentication.security (or fallback by auth type).
|
|
57
|
+
* @param {Object} system - System object with key and authentication
|
|
58
|
+
* @returns {Array<{name: string, value: string}>}
|
|
59
|
+
*/
|
|
60
|
+
function buildAuthSecureVarsFromSystem(system) {
|
|
61
|
+
const authSecureVars = [];
|
|
62
|
+
const systemKey = system?.key || 'external-system';
|
|
63
|
+
const prefix = systemKeyToKvPrefix(systemKey);
|
|
64
|
+
if (!prefix) return authSecureVars;
|
|
65
|
+
const security = system?.authentication?.security || system?.auth?.security;
|
|
66
|
+
const authMethod = (system?.authentication?.method || system?.authentication?.type ||
|
|
67
|
+
system?.auth?.method || system?.auth?.type || 'apikey').toLowerCase();
|
|
68
|
+
if (security && typeof security === 'object' && Object.keys(security).length > 0) {
|
|
69
|
+
for (const key of Object.keys(security)) {
|
|
70
|
+
const envName = `KV_${prefix}_${securityKeyToVar(key)}`;
|
|
71
|
+
const pathVal = kvEnvKeyToPath(envName, systemKey);
|
|
72
|
+
authSecureVars.push({ name: envName, value: pathVal || `kv://${systemKey}/${key}` });
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
const keys = FALLBACK_SECURITY_BY_AUTH[authMethod] || FALLBACK_SECURITY_BY_AUTH.apikey;
|
|
76
|
+
for (const key of keys) {
|
|
77
|
+
authSecureVars.push({
|
|
78
|
+
name: `KV_${prefix}_${securityKeyToVar(key)}`,
|
|
79
|
+
value: `kv://${systemKey}/${key}`
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return authSecureVars;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Builds configuration array with name, value, comment from system.configuration.
|
|
88
|
+
* @param {Object} system - System object with configuration array
|
|
89
|
+
* @returns {Array<{name: string, value: string, comment: string}>}
|
|
90
|
+
*/
|
|
91
|
+
function buildConfigurationEntries(system) {
|
|
92
|
+
const configuration = [];
|
|
93
|
+
const configList = Array.isArray(system?.configuration) ? system.configuration : [];
|
|
94
|
+
for (const entry of configList) {
|
|
95
|
+
if (!entry || !entry.name) continue;
|
|
96
|
+
const label = entry.portalInput?.label || entry.name;
|
|
97
|
+
const hint = buildPortalInputHint(entry.portalInput || {});
|
|
98
|
+
let value = entry.value !== undefined && entry.value !== null ? String(entry.value) : '';
|
|
99
|
+
if (entry.location === 'keyvault' && value && !value.startsWith('kv://')) value = `kv://${value}`;
|
|
100
|
+
configuration.push({ name: entry.name, value, comment: `${label}${hint}` });
|
|
101
|
+
}
|
|
102
|
+
return configuration;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Builds template context from system object for env.template.hbs.
|
|
107
|
+
* @param {Object} system - Full system object (e.g. deployment.system or parsed system file)
|
|
108
|
+
* @returns {{ authMethod: string, authSecureVars: Array<{name: string, value: string}>, authNonSecureVarNames: string[], configuration: Array<{name: string, value: string, comment: string}> }}
|
|
109
|
+
*/
|
|
110
|
+
function buildExternalEnvTemplateContext(system) {
|
|
111
|
+
const authMethod = (system?.authentication?.method ||
|
|
112
|
+
system?.authentication?.type ||
|
|
113
|
+
system?.auth?.method ||
|
|
114
|
+
system?.auth?.type ||
|
|
115
|
+
'apikey').toLowerCase();
|
|
116
|
+
const authSecureVars = buildAuthSecureVarsFromSystem(system);
|
|
117
|
+
const authVars = system?.authentication?.variables || system?.auth?.variables || {};
|
|
118
|
+
const authNonSecureVarNames = Object.keys(authVars);
|
|
119
|
+
const configuration = buildConfigurationEntries(system);
|
|
120
|
+
return {
|
|
121
|
+
authMethod,
|
|
122
|
+
authSecureVars,
|
|
123
|
+
authNonSecureVarNames,
|
|
124
|
+
configuration
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Inline fallback when env.template.hbs is missing or unreadable (e.g. CI path or bundled). */
|
|
129
|
+
const DEFAULT_ENV_TEMPLATE_HBS = `# Environment variables for external system integration
|
|
130
|
+
# Use kv:// (or aifabrix secret set) for sensitive values; plain values for non-sensitive configuration.
|
|
131
|
+
#
|
|
132
|
+
|
|
133
|
+
{{#if authMethod}}
|
|
134
|
+
# Authentication
|
|
135
|
+
# Type: {{authMethod}}
|
|
136
|
+
{{#each authSecureVars}}
|
|
137
|
+
{{name}}={{value}}
|
|
138
|
+
{{/each}}
|
|
139
|
+
{{#if authNonSecureVarNames}}
|
|
140
|
+
# Non-secure (e.g. URLs): {{#each authNonSecureVarNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}
|
|
141
|
+
{{/if}}
|
|
142
|
+
|
|
143
|
+
{{/if}}
|
|
144
|
+
{{#if configuration.length}}
|
|
145
|
+
# Configuration
|
|
146
|
+
{{#each configuration}}
|
|
147
|
+
# {{comment}}
|
|
148
|
+
{{name}}={{value}}
|
|
149
|
+
{{/each}}
|
|
150
|
+
{{/if}}
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Generates env.template content from system using the Handlebars template.
|
|
155
|
+
* @param {Object} system - Full system object (e.g. deployment.system or parsed system file)
|
|
156
|
+
* @returns {string} Rendered env.template content
|
|
157
|
+
*/
|
|
158
|
+
function generateExternalEnvTemplateContent(system) {
|
|
159
|
+
if (!system || typeof system !== 'object') {
|
|
160
|
+
return '# Environment variables for external system integration\n# Use kv:// (or aifabrix secret set) for sensitive values.\n\n';
|
|
161
|
+
}
|
|
162
|
+
let templateContent;
|
|
163
|
+
try {
|
|
164
|
+
const templatePath = path.join(__dirname, '..', '..', 'templates', 'external-system', 'env.template.hbs');
|
|
165
|
+
templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
166
|
+
} catch (_) {
|
|
167
|
+
templateContent = undefined;
|
|
168
|
+
}
|
|
169
|
+
if (typeof templateContent !== 'string' || !templateContent.trim()) {
|
|
170
|
+
templateContent = DEFAULT_ENV_TEMPLATE_HBS;
|
|
171
|
+
}
|
|
172
|
+
const template = Handlebars.compile(templateContent);
|
|
173
|
+
const context = buildExternalEnvTemplateContext(system);
|
|
174
|
+
return template(context);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = {
|
|
178
|
+
buildExternalEnvTemplateContext,
|
|
179
|
+
generateExternalEnvTemplateContent
|
|
180
|
+
};
|
|
@@ -289,6 +289,20 @@ function displayE2EResults(data, verbose = false) {
|
|
|
289
289
|
logger.log(` ${ok ? chalk.green('✓') : chalk.red('✗')} ${name}`);
|
|
290
290
|
if (!ok && (step.error || step.message)) logger.log(chalk.red(` ${step.error || step.message}`));
|
|
291
291
|
if (verbose && step.message && ok) logger.log(chalk.gray(` ${step.message}`));
|
|
292
|
+
if (verbose && ok && (name === 'sync' || step.step === 'sync') && step.evidence && step.evidence.jobs) {
|
|
293
|
+
formatSyncStepEvidence(step.evidence.jobs);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (verbose && data.auditLog && Array.isArray(data.auditLog) && data.auditLog.length > 0) {
|
|
297
|
+
const n = data.auditLog.length;
|
|
298
|
+
const first = data.auditLog[0];
|
|
299
|
+
const execId = (first && (first.executionId || first.id || first.traceId)) ? String(first.executionId || first.id || first.traceId) : null;
|
|
300
|
+
if (execId) {
|
|
301
|
+
const short = execId.length > 10 ? `${execId.slice(0, 8)}…` : execId;
|
|
302
|
+
logger.log(chalk.gray(` CIP execution trace(s): ${n} (executionId: ${short})`));
|
|
303
|
+
} else {
|
|
304
|
+
logger.log(chalk.gray(` CIP execution trace(s): ${n}`));
|
|
305
|
+
}
|
|
292
306
|
}
|
|
293
307
|
if (isRunning) {
|
|
294
308
|
return;
|
|
@@ -297,6 +311,35 @@ function displayE2EResults(data, verbose = false) {
|
|
|
297
311
|
logger.log(allPassed ? chalk.green('\n✅ E2E test passed!') : chalk.red('\n❌ E2E test failed'));
|
|
298
312
|
}
|
|
299
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Log sync step job evidence (record counts) in verbose E2E output
|
|
316
|
+
* @param {Object[]} jobs - evidence.jobs from sync step
|
|
317
|
+
*/
|
|
318
|
+
function formatSyncStepEvidence(jobs) {
|
|
319
|
+
for (const job of jobs) {
|
|
320
|
+
const rec = job.recordsProcessed ?? job.totalProcessed;
|
|
321
|
+
const total = job.totalRecords ?? (job.audit && job.audit.totalProcessed);
|
|
322
|
+
const parts = [];
|
|
323
|
+
if (rec !== undefined && rec !== null) parts.push(`${rec} processed`);
|
|
324
|
+
if (total !== undefined && total !== null) parts.push(`total: ${total}`);
|
|
325
|
+
const audit = job.audit || {};
|
|
326
|
+
const ins = audit.inserted ?? job.insertedCount;
|
|
327
|
+
const upd = audit.updated ?? job.updatedCount;
|
|
328
|
+
const del = audit.deleted ?? job.deletedCount;
|
|
329
|
+
const tot = audit.totalProcessed ?? total;
|
|
330
|
+
if (ins !== undefined || upd !== undefined || del !== undefined || tot !== undefined) {
|
|
331
|
+
const a = [`inserted: ${ins ?? 0}`, `updated: ${upd ?? 0}`, `deleted: ${del ?? 0}`];
|
|
332
|
+
if (tot !== undefined) a.push(`totalProcessed: ${tot}`);
|
|
333
|
+
parts.push(`(${a.join(', ')})`);
|
|
334
|
+
}
|
|
335
|
+
if (job.skippedCount !== undefined) parts.push(`skipped: ${job.skippedCount}`);
|
|
336
|
+
if (job.rejectedByQualityCount !== undefined) parts.push(`rejectedByQuality: ${job.rejectedByQualityCount}`);
|
|
337
|
+
if (parts.length > 0) {
|
|
338
|
+
logger.log(chalk.gray(` Managed records: ${parts.join(' ')}`));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
300
343
|
module.exports = {
|
|
301
344
|
displayTestResults,
|
|
302
345
|
displayIntegrationTestResults,
|
|
@@ -139,11 +139,11 @@ function validateDimensions(dimensions, results) {
|
|
|
139
139
|
// Validate dimension keys and values
|
|
140
140
|
for (const [dimensionKey, attributePath] of Object.entries(dimensions)) {
|
|
141
141
|
if (!/^[a-zA-Z0-9_]+$/.test(dimensionKey)) {
|
|
142
|
-
results.errors.push(`Invalid dimension key '${dimensionKey}': must
|
|
142
|
+
results.errors.push(`Invalid dimension key '${dimensionKey}': dimension key must contain only letters, numbers, and underscores`);
|
|
143
143
|
results.valid = false;
|
|
144
144
|
}
|
|
145
145
|
if (typeof attributePath !== 'string' || !/^[a-zA-Z0-9_.]+$/.test(attributePath)) {
|
|
146
|
-
results.errors.push(`Invalid attribute path '${attributePath}' for dimension '${dimensionKey}': must
|
|
146
|
+
results.errors.push(`Invalid attribute path '${attributePath}' for dimension '${dimensionKey}': attribute path must contain only letters, numbers, underscores, and dots`);
|
|
147
147
|
results.valid = false;
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -46,9 +46,7 @@ const CATEGORIES = [
|
|
|
46
46
|
{ name: 'build', term: 'build <app>' },
|
|
47
47
|
{ name: 'run', term: 'run <app>' },
|
|
48
48
|
{ name: 'shell', term: 'shell <app>' },
|
|
49
|
-
{ name: 'test', term: 'test <app>' },
|
|
50
49
|
{ name: 'install', term: 'install <app>' },
|
|
51
|
-
{ name: 'test-e2e', term: 'test-e2e <app>' },
|
|
52
50
|
{ name: 'lint', term: 'lint <app>' },
|
|
53
51
|
{ name: 'logs', term: 'logs <app>' },
|
|
54
52
|
{ name: 'stop', term: 'stop <app>' },
|
|
@@ -65,15 +63,13 @@ const CATEGORIES = [
|
|
|
65
63
|
{
|
|
66
64
|
name: 'Environments',
|
|
67
65
|
commands: [
|
|
68
|
-
{ name: 'environment' },
|
|
69
66
|
{ name: 'env' }
|
|
70
67
|
]
|
|
71
68
|
},
|
|
72
69
|
{
|
|
73
|
-
name: 'Application &
|
|
70
|
+
name: 'Application & Management',
|
|
74
71
|
commands: [
|
|
75
72
|
{ name: 'app' },
|
|
76
|
-
{ name: 'datasource' },
|
|
77
73
|
{ name: 'credential' },
|
|
78
74
|
{ name: 'deployment' },
|
|
79
75
|
{ name: 'service-user' }
|
|
@@ -98,7 +94,9 @@ const CATEGORIES = [
|
|
|
98
94
|
{ name: 'upload', term: 'upload <system-key>' },
|
|
99
95
|
{ name: 'delete', term: 'delete <system-key>' },
|
|
100
96
|
{ name: 'repair', term: 'repair <app>' },
|
|
97
|
+
{ name: 'datasource' },
|
|
101
98
|
{ name: 'test', term: 'test <app>' },
|
|
99
|
+
{ name: 'test-e2e', term: 'test-e2e <app>' },
|
|
102
100
|
{ name: 'test-integration', term: 'test-integration <app>' }
|
|
103
101
|
]
|
|
104
102
|
},
|
|
@@ -15,10 +15,31 @@ const logger = require('../utils/logger');
|
|
|
15
15
|
const pathsUtil = require('./paths');
|
|
16
16
|
const { mergeSecretsIntoFile } = require('./secrets-generator');
|
|
17
17
|
|
|
18
|
+
/** Bootstrap key name; never encrypt this key's value when writing (key is stored in config). */
|
|
19
|
+
const ENCRYPTION_KEY_VAULT = 'secrets-encryptionKeyVault';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolves value to write: encrypted (secure://) when encryption key is set and key is not the bootstrap key.
|
|
23
|
+
* @async
|
|
24
|
+
* @param {string} key - Secret key name
|
|
25
|
+
* @param {string} value - Secret value
|
|
26
|
+
* @returns {Promise<string>} Value to write (plaintext or secure://...)
|
|
27
|
+
*/
|
|
28
|
+
async function resolveValueForWrite(key, value) {
|
|
29
|
+
const config = require('../core/config');
|
|
30
|
+
const encryptionKey = await config.getSecretsEncryptionKey();
|
|
31
|
+
if (!encryptionKey || key === ENCRYPTION_KEY_VAULT) {
|
|
32
|
+
return typeof value === 'string' ? value : String(value);
|
|
33
|
+
}
|
|
34
|
+
const { encryptSecret } = require('./secrets-encryption');
|
|
35
|
+
return encryptSecret(typeof value === 'string' ? value : String(value), encryptionKey);
|
|
36
|
+
}
|
|
37
|
+
|
|
18
38
|
/**
|
|
19
39
|
* Saves a secret to ~/.aifabrix/secrets.local.yaml
|
|
20
40
|
* Uses paths.getAifabrixHome() to respect config.yaml aifabrix-home override
|
|
21
|
-
* Merges the key into the file (updates in place if key already exists, e.g. after rotate-secret)
|
|
41
|
+
* Merges the key into the file (updates in place if key already exists, e.g. after rotate-secret).
|
|
42
|
+
* Encrypts the value when a secrets-encryption key is configured (except for the bootstrap key).
|
|
22
43
|
*
|
|
23
44
|
* @async
|
|
24
45
|
* @function saveLocalSecret
|
|
@@ -39,8 +60,9 @@ async function saveLocalSecret(key, value) {
|
|
|
39
60
|
throw new Error('Secret value is required');
|
|
40
61
|
}
|
|
41
62
|
|
|
63
|
+
const valueToWrite = await resolveValueForWrite(key, value);
|
|
42
64
|
const secretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
|
|
43
|
-
mergeSecretsIntoFile(secretsPath, { [key]:
|
|
65
|
+
mergeSecretsIntoFile(secretsPath, { [key]: valueToWrite });
|
|
44
66
|
}
|
|
45
67
|
|
|
46
68
|
/**
|
|
@@ -121,8 +143,9 @@ function _loadExistingSecrets(resolvedPath) {
|
|
|
121
143
|
async function saveSecret(key, value, secretsPath) {
|
|
122
144
|
validateSaveSecretParams(key, value, secretsPath);
|
|
123
145
|
|
|
146
|
+
const valueToWrite = await resolveValueForWrite(key, value);
|
|
124
147
|
const resolvedPath = resolveAndPrepareSecretsPath(secretsPath);
|
|
125
|
-
mergeSecretsIntoFile(resolvedPath, { [key]:
|
|
148
|
+
mergeSecretsIntoFile(resolvedPath, { [key]: valueToWrite });
|
|
126
149
|
}
|
|
127
150
|
|
|
128
151
|
/**
|
package/lib/utils/paths.js
CHANGED
|
@@ -430,7 +430,7 @@ function getDeployJsonPath(appName, appType, preferNew = false) {
|
|
|
430
430
|
// If neither exists, return new naming (for generation)
|
|
431
431
|
return newPath;
|
|
432
432
|
}
|
|
433
|
-
const { resolveApplicationConfigPath } = require('./app-config-resolver');
|
|
433
|
+
const { resolveApplicationConfigPath, resolveRbacPath } = require('./app-config-resolver');
|
|
434
434
|
const { loadConfigFile } = require('./config-format');
|
|
435
435
|
/**
|
|
436
436
|
* Checks if app type is external from variables object
|
|
@@ -562,6 +562,7 @@ module.exports = {
|
|
|
562
562
|
resolveBuildContext,
|
|
563
563
|
getDeployJsonPath,
|
|
564
564
|
resolveApplicationConfigPath,
|
|
565
|
+
resolveRbacPath,
|
|
565
566
|
detectAppType,
|
|
566
567
|
getResolveAppPath,
|
|
567
568
|
resolveIntegrationAppKeyFromCwd,
|