@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
|
@@ -174,11 +174,14 @@ async function loadSystemFile(appPath, schemaBasePath, systemFileName) {
|
|
|
174
174
|
* @param {string} appPath - Application path
|
|
175
175
|
* @param {string} schemaBasePath - Schema base path
|
|
176
176
|
* @param {Array<string>} datasourceFiles - Array of datasource file names
|
|
177
|
+
* @param {Object} [options] - Options
|
|
178
|
+
* @param {boolean} [options.skipMissingDatasourceFiles] - If true, skip missing files (e.g. when generating deploy JSON) instead of throwing
|
|
177
179
|
* @returns {Promise<Array<Object>>} Array of datasource JSON objects
|
|
178
|
-
* @throws {Error} If
|
|
180
|
+
* @throws {Error} If a file is missing and skipMissingDatasourceFiles is not set
|
|
179
181
|
*/
|
|
180
|
-
async function loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles) {
|
|
182
|
+
async function loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles, options = {}) {
|
|
181
183
|
const datasourceJsons = [];
|
|
184
|
+
const { skipMissingDatasourceFiles } = options;
|
|
182
185
|
|
|
183
186
|
for (const datasourceFile of datasourceFiles) {
|
|
184
187
|
const datasourcePath = path.isAbsolute(schemaBasePath)
|
|
@@ -186,6 +189,9 @@ async function loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles) {
|
|
|
186
189
|
: path.join(appPath, schemaBasePath, datasourceFile);
|
|
187
190
|
|
|
188
191
|
if (!fs.existsSync(datasourcePath)) {
|
|
192
|
+
if (skipMissingDatasourceFiles) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
189
195
|
throw new Error(`Datasource file not found: ${datasourcePath}`);
|
|
190
196
|
}
|
|
191
197
|
|
|
@@ -417,6 +423,7 @@ module.exports = {
|
|
|
417
423
|
generateExternalSystemApplicationSchema,
|
|
418
424
|
splitExternalApplicationSchema,
|
|
419
425
|
loadSystemFile,
|
|
420
|
-
loadDatasourceFiles
|
|
426
|
+
loadDatasourceFiles,
|
|
427
|
+
loadExternalIntegrationConfig
|
|
421
428
|
};
|
|
422
429
|
|
package/lib/generator/index.js
CHANGED
|
@@ -20,6 +20,8 @@ const { loadVariables, loadEnvTemplate, loadRbac, parseEnvironmentVariables } =
|
|
|
20
20
|
const { generateExternalSystemApplicationSchema, splitExternalApplicationSchema } = require('./external');
|
|
21
21
|
const { generateControllerManifest } = require('./external-controller-manifest');
|
|
22
22
|
const { resolveVersionForApp } = require('../utils/image-version');
|
|
23
|
+
const { getContainerPort } = require('../utils/port-resolver');
|
|
24
|
+
const { buildEnvVarMap } = require('../utils/env-map');
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Generates deployment JSON from application configuration files
|
|
@@ -59,6 +61,96 @@ function loadDeploymentConfigFiles(appPath, appType, appName) {
|
|
|
59
61
|
return { variables, envTemplate, rbac, jsonPath };
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
/** Placeholder replaced with application port from application.yaml */
|
|
65
|
+
const PORT_PLACEHOLDER = '${PORT}';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns the numeric port to use when substituting ${PORT} in the manifest.
|
|
69
|
+
* When application.yaml has port: "${PORT}", uses defaultPort (e.g. 3000).
|
|
70
|
+
*
|
|
71
|
+
* @param {Object} variables - Parsed application config
|
|
72
|
+
* @param {number} [defaultPort=3000] - Default when port is "${PORT}" or invalid
|
|
73
|
+
* @returns {number} Port number for substitution
|
|
74
|
+
*/
|
|
75
|
+
function getEffectivePortForSubstitution(variables, defaultPort = 3000) {
|
|
76
|
+
const raw = getContainerPort(variables, defaultPort);
|
|
77
|
+
if (raw === PORT_PLACEHOLDER || (typeof raw === 'string' && raw.trim() === PORT_PLACEHOLDER)) {
|
|
78
|
+
return defaultPort;
|
|
79
|
+
}
|
|
80
|
+
if (typeof raw === 'number' && raw > 0) {
|
|
81
|
+
return raw;
|
|
82
|
+
}
|
|
83
|
+
const num = Number(raw);
|
|
84
|
+
return Number.isFinite(num) && num > 0 ? num : defaultPort;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Recursively replaces ${PORT} with the given port number in all string values of obj (in-place).
|
|
89
|
+
*
|
|
90
|
+
* @param {Object} obj - Deployment manifest or any nested object
|
|
91
|
+
* @param {number} portNumber - Port to substitute (e.g. from application.yaml)
|
|
92
|
+
*/
|
|
93
|
+
function substitutePortInDeployment(obj, portNumber) {
|
|
94
|
+
if (obj === null || obj === undefined) return;
|
|
95
|
+
if (Array.isArray(obj)) {
|
|
96
|
+
for (let i = 0; i < obj.length; i++) {
|
|
97
|
+
if (typeof obj[i] === 'string' && obj[i].includes(PORT_PLACEHOLDER)) {
|
|
98
|
+
obj[i] = obj[i].split(PORT_PLACEHOLDER).join(String(portNumber));
|
|
99
|
+
} else if (typeof obj[i] === 'object' && obj[i] !== null) {
|
|
100
|
+
substitutePortInDeployment(obj[i], portNumber);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (typeof obj === 'object') {
|
|
106
|
+
for (const key of Object.keys(obj)) {
|
|
107
|
+
const value = obj[key];
|
|
108
|
+
if (typeof value === 'string' && value.includes(PORT_PLACEHOLDER)) {
|
|
109
|
+
obj[key] = value.split(PORT_PLACEHOLDER).join(String(portNumber));
|
|
110
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
111
|
+
substitutePortInDeployment(value, portNumber);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Regex to find ${VAR} placeholders for env substitution */
|
|
118
|
+
const ENV_VAR_PLACEHOLDER_REGEX = /\$\{([^}]+)\}/g;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Recursively replaces ${VAR} with envVarMap[VAR] in all string values of obj (in-place).
|
|
122
|
+
* Only substitutes when VAR is a key in envVarMap (from env-config.yaml / config).
|
|
123
|
+
*
|
|
124
|
+
* @param {Object} obj - Deployment manifest or any nested object
|
|
125
|
+
* @param {Object} envVarMap - Flat map of variable names to values (e.g. from buildEnvVarMap('docker'))
|
|
126
|
+
*/
|
|
127
|
+
function substituteEnvVarsInDeployment(obj, envVarMap) {
|
|
128
|
+
if (!envVarMap || typeof envVarMap !== 'object') return;
|
|
129
|
+
if (obj === null || obj === undefined) return;
|
|
130
|
+
if (Array.isArray(obj)) {
|
|
131
|
+
for (let i = 0; i < obj.length; i++) {
|
|
132
|
+
if (typeof obj[i] === 'string') {
|
|
133
|
+
obj[i] = obj[i].replace(ENV_VAR_PLACEHOLDER_REGEX, (match, varName) =>
|
|
134
|
+
Object.prototype.hasOwnProperty.call(envVarMap, varName) ? String(envVarMap[varName]) : match);
|
|
135
|
+
} else if (typeof obj[i] === 'object' && obj[i] !== null) {
|
|
136
|
+
substituteEnvVarsInDeployment(obj[i], envVarMap);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (typeof obj === 'object') {
|
|
142
|
+
for (const key of Object.keys(obj)) {
|
|
143
|
+
const value = obj[key];
|
|
144
|
+
if (typeof value === 'string') {
|
|
145
|
+
obj[key] = value.replace(ENV_VAR_PLACEHOLDER_REGEX, (match, varName) =>
|
|
146
|
+
Object.prototype.hasOwnProperty.call(envVarMap, varName) ? String(envVarMap[varName]) : match);
|
|
147
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
148
|
+
substituteEnvVarsInDeployment(value, envVarMap);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
62
154
|
/**
|
|
63
155
|
* Builds and validates deployment manifest
|
|
64
156
|
* @function buildAndValidateDeployment
|
|
@@ -66,10 +158,12 @@ function loadDeploymentConfigFiles(appPath, appType, appName) {
|
|
|
66
158
|
* @param {Object} variables - Variables configuration
|
|
67
159
|
* @param {Object} envTemplate - Environment template
|
|
68
160
|
* @param {Object} rbac - RBAC configuration
|
|
161
|
+
* @param {Object} [options] - Optional options
|
|
162
|
+
* @param {Object} [options.envVarMap] - Env vars from env-config (e.g. REDIS_HOST, DB_HOST) to resolve ${VAR} in manifest
|
|
69
163
|
* @returns {Object} Deployment manifest
|
|
70
164
|
* @throws {Error} If validation fails
|
|
71
165
|
*/
|
|
72
|
-
function buildAndValidateDeployment(appName, variables, envTemplate, rbac) {
|
|
166
|
+
function buildAndValidateDeployment(appName, variables, envTemplate, rbac, options = null) {
|
|
73
167
|
// Parse environment variables from template and merge portalInput from application config
|
|
74
168
|
const configuration = parseEnvironmentVariables(envTemplate, variables);
|
|
75
169
|
|
|
@@ -83,6 +177,23 @@ function buildAndValidateDeployment(appName, variables, envTemplate, rbac) {
|
|
|
83
177
|
throw new Error(`Generated deployment JSON does not match schema:\n${errorMessages}`);
|
|
84
178
|
}
|
|
85
179
|
|
|
180
|
+
// Replace ${PORT} with port from application.yaml so manifest deploys correctly
|
|
181
|
+
const effectivePort = getEffectivePortForSubstitution(variables, 3000);
|
|
182
|
+
substitutePortInDeployment(deployment, effectivePort);
|
|
183
|
+
if (deployment.port !== undefined) {
|
|
184
|
+
deployment.port = typeof deployment.port === 'string' && /^\d+$/.test(deployment.port)
|
|
185
|
+
? parseInt(deployment.port, 10) : (typeof deployment.port === 'number' ? deployment.port : effectivePort);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Resolve ${REDIS_HOST}, ${DB_HOST}, etc. from env-config.yaml so manifest has no unresolved vars
|
|
189
|
+
const envVarMap = options && options.envVarMap;
|
|
190
|
+
if (envVarMap) {
|
|
191
|
+
substituteEnvVarsInDeployment(deployment, envVarMap);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Ensure no other ${...} placeholders remain in manifest
|
|
195
|
+
_validator.validateNoUnresolvedVariablesInDeployment(deployment);
|
|
196
|
+
|
|
86
197
|
return deployment;
|
|
87
198
|
}
|
|
88
199
|
|
|
@@ -117,46 +228,84 @@ async function buildDeploymentManifestInMemory(appName, options = {}) {
|
|
|
117
228
|
const configuration = parseEnvironmentVariables(envTemplate, variables);
|
|
118
229
|
const deployment = builders.buildManifestStructure(appName, variablesWithVersion, configuration, rbac);
|
|
119
230
|
|
|
231
|
+
const effectivePort = getEffectivePortForSubstitution(variablesWithVersion, 3000);
|
|
232
|
+
substitutePortInDeployment(deployment, effectivePort);
|
|
233
|
+
if (deployment.port !== undefined) {
|
|
234
|
+
deployment.port = typeof deployment.port === 'string' && /^\d+$/.test(deployment.port)
|
|
235
|
+
? parseInt(deployment.port, 10) : (typeof deployment.port === 'number' ? deployment.port : effectivePort);
|
|
236
|
+
}
|
|
237
|
+
const envVarMap = await buildEnvVarMap('docker', null, null, { appPort: effectivePort });
|
|
238
|
+
substituteEnvVarsInDeployment(deployment, envVarMap);
|
|
239
|
+
_validator.validateNoUnresolvedVariablesInDeployment(deployment);
|
|
120
240
|
return { deployment, appPath };
|
|
121
241
|
}
|
|
122
242
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Writes external system deploy JSON (manifest + ${PORT} substitution + validation).
|
|
245
|
+
* @async
|
|
246
|
+
* @param {string} appName - Application name
|
|
247
|
+
* @param {string} appPath - Application path
|
|
248
|
+
* @param {Object} options - Generation options
|
|
249
|
+
* @returns {Promise<string>} Path to written deploy JSON
|
|
250
|
+
*/
|
|
251
|
+
async function writeExternalDeployJson(appName, appPath, options) {
|
|
252
|
+
const manifest = await generateControllerManifest(appName, {
|
|
253
|
+
...options,
|
|
254
|
+
skipMissingDatasourceFiles: true
|
|
255
|
+
});
|
|
256
|
+
let effectivePort = 3000;
|
|
257
|
+
try {
|
|
258
|
+
const variablesPath = resolveApplicationConfigPath(appPath);
|
|
259
|
+
const { parsed: variables } = loadVariables(variablesPath);
|
|
260
|
+
effectivePort = getEffectivePortForSubstitution(variables, 3000);
|
|
261
|
+
substitutePortInDeployment(manifest, effectivePort);
|
|
262
|
+
} catch {
|
|
263
|
+
substitutePortInDeployment(manifest, 3000);
|
|
142
264
|
}
|
|
265
|
+
const envVarMap = await buildEnvVarMap('docker', null, null, { appPort: effectivePort });
|
|
266
|
+
substituteEnvVarsInDeployment(manifest, envVarMap);
|
|
267
|
+
_validator.validateNoUnresolvedVariablesInDeployment(manifest);
|
|
268
|
+
const systemKey = manifest.key || appName;
|
|
269
|
+
const deployJsonPath = path.join(appPath, `${systemKey}-deploy.json`);
|
|
270
|
+
await fs.promises.writeFile(deployJsonPath, JSON.stringify(manifest, null, 2), { mode: 0o644, encoding: 'utf8' });
|
|
271
|
+
return deployJsonPath;
|
|
272
|
+
}
|
|
143
273
|
|
|
144
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Writes regular app deploy JSON (build + validate + write).
|
|
276
|
+
* @async
|
|
277
|
+
* @param {string} appName - Application name
|
|
278
|
+
* @param {string} appPath - Application path
|
|
279
|
+
* @param {string} appType - Application type
|
|
280
|
+
* @returns {Promise<string>} Path to written deploy JSON
|
|
281
|
+
*/
|
|
282
|
+
async function writeRegularDeployJson(appName, appPath, appType) {
|
|
145
283
|
const { variables, envTemplate, rbac, jsonPath } = loadDeploymentConfigFiles(appPath, appType, appName);
|
|
146
284
|
const resolved = await resolveVersionForApp(appName, variables, { updateBuilder: false });
|
|
147
285
|
const variablesWithVersion = {
|
|
148
286
|
...variables,
|
|
149
287
|
app: { ...variables.app, version: resolved.version }
|
|
150
288
|
};
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
289
|
+
const effectivePort = getEffectivePortForSubstitution(variablesWithVersion, 3000);
|
|
290
|
+
const envVarMap = await buildEnvVarMap('docker', null, null, { appPort: effectivePort });
|
|
291
|
+
const deployment = buildAndValidateDeployment(appName, variablesWithVersion, envTemplate, rbac, { envVarMap });
|
|
154
292
|
const jsonContent = JSON.stringify(deployment, null, 2);
|
|
155
293
|
fs.writeFileSync(jsonPath, jsonContent, { mode: 0o644 });
|
|
156
|
-
|
|
157
294
|
return jsonPath;
|
|
158
295
|
}
|
|
159
296
|
|
|
297
|
+
async function generateDeployJson(appName, options = {}) {
|
|
298
|
+
if (!appName || typeof appName !== 'string') {
|
|
299
|
+
throw new Error('App name is required and must be a string');
|
|
300
|
+
}
|
|
301
|
+
const { isExternal, appPath, appType } = await detectAppType(appName);
|
|
302
|
+
logOfflinePathWhenType(appPath);
|
|
303
|
+
if (isExternal) {
|
|
304
|
+
return writeExternalDeployJson(appName, appPath, options);
|
|
305
|
+
}
|
|
306
|
+
return await writeRegularDeployJson(appName, appPath, appType);
|
|
307
|
+
}
|
|
308
|
+
|
|
160
309
|
async function generateDeployJsonWithValidation(appName, options = {}) {
|
|
161
310
|
const jsonPath = await generateDeployJson(appName, options);
|
|
162
311
|
const jsonContent = fs.readFileSync(jsonPath, 'utf8');
|
|
@@ -187,6 +336,9 @@ module.exports = {
|
|
|
187
336
|
generateDeployJson,
|
|
188
337
|
generateDeployJsonWithValidation,
|
|
189
338
|
buildDeploymentManifestInMemory,
|
|
339
|
+
getEffectivePortForSubstitution,
|
|
340
|
+
substitutePortInDeployment,
|
|
341
|
+
substituteEnvVarsInDeployment,
|
|
190
342
|
generateExternalSystemApplicationSchema,
|
|
191
343
|
splitExternalApplicationSchema,
|
|
192
344
|
parseEnvironmentVariables,
|
|
@@ -25,6 +25,7 @@ function buildReadmeConfigForExternal(deployment) {
|
|
|
25
25
|
systemType: system.type || 'openapi',
|
|
26
26
|
systemDisplayName: system.displayName || appName,
|
|
27
27
|
systemDescription: system.description || `External system integration for ${appName}`,
|
|
28
|
+
fileExt: '.yaml',
|
|
28
29
|
datasourceCount: dataSources.length,
|
|
29
30
|
datasources: dataSources
|
|
30
31
|
}
|
|
@@ -78,10 +78,12 @@ function extractPortalInputConfiguration(configuration) {
|
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
80
|
* Datasource filename for externalIntegration.dataSources.
|
|
81
|
+
* Avoids duplicate "datasource" in name (e.g. hubspot-users-datasource -> hubspot-datasource-users).
|
|
82
|
+
*
|
|
81
83
|
* @param {string} systemKey - System key
|
|
82
84
|
* @param {Object} datasource - Datasource from deployment.dataSources
|
|
83
85
|
* @param {number} index - Index
|
|
84
|
-
* @returns {string} Filename e.g.
|
|
86
|
+
* @returns {string} Filename e.g. hubspot-datasource-users.yaml
|
|
85
87
|
*/
|
|
86
88
|
function getExternalDatasourceFileName(systemKey, datasource, index) {
|
|
87
89
|
const key = datasource.key || '';
|
|
@@ -90,6 +92,10 @@ function getExternalDatasourceFileName(systemKey, datasource, index) {
|
|
|
90
92
|
else if (key.startsWith(`${systemKey}-`)) suffix = key.slice(systemKey.length + 1);
|
|
91
93
|
else if (key) suffix = key;
|
|
92
94
|
else suffix = datasource.entityType || datasource.entityKey || `entity${index + 1}`;
|
|
95
|
+
// Strip trailing -datasource so we get hubspot-datasource-users not hubspot-datasource-users-datasource
|
|
96
|
+
if (suffix.endsWith('-datasource')) {
|
|
97
|
+
suffix = suffix.slice(0, -'-datasource'.length);
|
|
98
|
+
}
|
|
93
99
|
return `${systemKey}-datasource-${suffix}.yaml`;
|
|
94
100
|
}
|
|
95
101
|
|
package/lib/generator/split.js
CHANGED
|
@@ -139,6 +139,125 @@ async function writeComponentFile(filePath, content) {
|
|
|
139
139
|
await fs.writeFile(filePath, content, { mode: 0o644, encoding: 'utf8' });
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Builds key -> line map from configuration array (for env.template merge).
|
|
144
|
+
* @param {Array} configuration - Configuration array from deployment
|
|
145
|
+
* @returns {Map<string, string>} Key to full line map
|
|
146
|
+
*/
|
|
147
|
+
function buildEnvTemplateExpectedByKey(configuration) {
|
|
148
|
+
const expectedByKey = new Map();
|
|
149
|
+
if (!Array.isArray(configuration)) return expectedByKey;
|
|
150
|
+
const lines = extractEnvTemplate(configuration).split('\n').filter(Boolean);
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
const eq = line.indexOf('=');
|
|
153
|
+
if (eq > 0) {
|
|
154
|
+
const key = line.substring(0, eq).trim();
|
|
155
|
+
expectedByKey.set(key, `${key}=${line.substring(eq + 1)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return expectedByKey;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Pushes the appropriate line for a key that exists in expectedByKey (preserves MISO_CONTROLLER_URL).
|
|
163
|
+
* @param {string} line - Existing line
|
|
164
|
+
* @param {string} key - Parsed key
|
|
165
|
+
* @param {Map<string, string>} expectedByKey - Expected key -> line
|
|
166
|
+
* @param {string[]} updatedLines - Output lines
|
|
167
|
+
* @param {Set<string>} keysWritten - Keys already written
|
|
168
|
+
*/
|
|
169
|
+
function pushMergedKeyValueLine(line, key, expectedByKey, updatedLines, keysWritten) {
|
|
170
|
+
const valueToPush = key === 'MISO_CONTROLLER_URL' ? line : expectedByKey.get(key);
|
|
171
|
+
updatedLines.push(valueToPush);
|
|
172
|
+
keysWritten.add(key);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Merges existing env.template with new lines from configuration; preserves comments and unknown keys.
|
|
177
|
+
* When MISO_CONTROLLER_URL already exists in the file, its value is preserved (only add when missing).
|
|
178
|
+
* @param {string} existingContent - Current env.template content
|
|
179
|
+
* @param {Map<string, string>} expectedByKey - New key -> line from download
|
|
180
|
+
* @returns {string} Merged content
|
|
181
|
+
*/
|
|
182
|
+
function mergeEnvTemplateWithExisting(existingContent, expectedByKey) {
|
|
183
|
+
const lines = existingContent.split(/\r?\n/);
|
|
184
|
+
const updatedLines = [];
|
|
185
|
+
const keysWritten = new Set();
|
|
186
|
+
for (const line of lines) {
|
|
187
|
+
const trimmed = line.trim();
|
|
188
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
189
|
+
updatedLines.push(line);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const eq = line.indexOf('=');
|
|
193
|
+
if (eq <= 0) {
|
|
194
|
+
updatedLines.push(line);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const key = line.substring(0, eq).trim();
|
|
198
|
+
if (expectedByKey.has(key)) {
|
|
199
|
+
pushMergedKeyValueLine(line, key, expectedByKey, updatedLines, keysWritten);
|
|
200
|
+
} else {
|
|
201
|
+
updatedLines.push(line);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const key of expectedByKey.keys()) {
|
|
205
|
+
if (!keysWritten.has(key)) updatedLines.push(expectedByKey.get(key));
|
|
206
|
+
}
|
|
207
|
+
return updatedLines.join('\n') + (updatedLines.length > 0 ? '\n' : '');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Writes env.template (merge or overwrite).
|
|
212
|
+
* @param {string} outputDir - Output directory
|
|
213
|
+
* @param {string} envTemplate - Default env.template content
|
|
214
|
+
* @param {Object} options - Options (mergeEnvTemplate, configuration)
|
|
215
|
+
* @returns {Promise<string>} Path to env.template
|
|
216
|
+
*/
|
|
217
|
+
async function writeEnvTemplateToDir(outputDir, envTemplate, options) {
|
|
218
|
+
const envTemplatePath = path.join(outputDir, 'env.template');
|
|
219
|
+
const fsSync = require('fs');
|
|
220
|
+
if (options.mergeEnvTemplate && options.configuration && fsSync.existsSync(envTemplatePath)) {
|
|
221
|
+
const expectedByKey = buildEnvTemplateExpectedByKey(options.configuration);
|
|
222
|
+
const existingContent = await fs.readFile(envTemplatePath, 'utf8');
|
|
223
|
+
const merged = mergeEnvTemplateWithExisting(existingContent, expectedByKey);
|
|
224
|
+
await writeComponentFile(envTemplatePath, merged);
|
|
225
|
+
} else {
|
|
226
|
+
await writeComponentFile(envTemplatePath, envTemplate);
|
|
227
|
+
}
|
|
228
|
+
return envTemplatePath;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Writes application.yaml, rbac.yaml, and README.md.
|
|
233
|
+
* @param {string} outputDir - Output directory
|
|
234
|
+
* @param {Object} variables - Variables object
|
|
235
|
+
* @param {Object|null} rbac - RBAC object or null
|
|
236
|
+
* @param {string} readme - README content
|
|
237
|
+
* @param {Object} options - Options (overwriteReadme)
|
|
238
|
+
* @returns {Promise<Object>} Results with variables, rbac?, readme? paths
|
|
239
|
+
*/
|
|
240
|
+
async function writeVariablesRbacReadme(outputDir, variables, rbac, readme, options) {
|
|
241
|
+
const out = {};
|
|
242
|
+
const variablesPath = path.join(outputDir, 'application.yaml');
|
|
243
|
+
await writeComponentFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: -1 }));
|
|
244
|
+
out.variables = variablesPath;
|
|
245
|
+
if (rbac) {
|
|
246
|
+
const rbacPath = path.join(outputDir, 'rbac.yaml');
|
|
247
|
+
await writeComponentFile(rbacPath, yaml.dump(rbac, { indent: 2, lineWidth: -1 }));
|
|
248
|
+
out.rbac = rbacPath;
|
|
249
|
+
}
|
|
250
|
+
const readmePath = path.join(outputDir, 'README.md');
|
|
251
|
+
const fsSync = require('fs');
|
|
252
|
+
if (options.overwriteReadme === false && fsSync.existsSync(readmePath)) {
|
|
253
|
+
out.readmeSkipped = readmePath;
|
|
254
|
+
} else {
|
|
255
|
+
await writeComponentFile(readmePath, readme);
|
|
256
|
+
out.readme = readmePath;
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
|
|
142
261
|
/**
|
|
143
262
|
* Writes all component files
|
|
144
263
|
* @async
|
|
@@ -148,36 +267,36 @@ async function writeComponentFile(filePath, content) {
|
|
|
148
267
|
* @param {Object} variables - Variables object
|
|
149
268
|
* @param {Object|null} rbac - RBAC object or null
|
|
150
269
|
* @param {string} readme - README content
|
|
270
|
+
* @param {Object} [options] - Options
|
|
271
|
+
* @param {boolean} [options.mergeEnvTemplate] - If true and env.template exists, merge instead of overwrite
|
|
272
|
+
* @param {Array} [options.configuration] - Configuration array for merge (required when mergeEnvTemplate)
|
|
273
|
+
* @param {boolean} [options.overwriteReadme] - If false and README.md exists, skip writing README
|
|
151
274
|
* @returns {Promise<Object>} Results object with file paths
|
|
152
275
|
*/
|
|
153
|
-
async function writeComponentFiles(outputDir, envTemplate, variables, rbac, readme) {
|
|
276
|
+
async function writeComponentFiles(outputDir, envTemplate, variables, rbac, readme, options = {}) {
|
|
154
277
|
const results = {};
|
|
278
|
+
results.envTemplate = await writeEnvTemplateToDir(outputDir, envTemplate, options);
|
|
279
|
+
Object.assign(results, await writeVariablesRbacReadme(outputDir, variables, rbac, readme, options));
|
|
280
|
+
return results;
|
|
281
|
+
}
|
|
155
282
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
await writeComponentFile(rbacPath, rbacYaml);
|
|
172
|
-
results.rbac = rbacPath;
|
|
283
|
+
/**
|
|
284
|
+
* Writes datasource YAML files for external system.
|
|
285
|
+
* @param {string} outputDir - Output directory
|
|
286
|
+
* @param {string} systemKey - System key
|
|
287
|
+
* @param {Array} dataSourcesList - DataSources array
|
|
288
|
+
* @returns {Promise<string[]>} Paths to written datasource files
|
|
289
|
+
*/
|
|
290
|
+
async function writeDatasourceFiles(outputDir, systemKey, dataSourcesList) {
|
|
291
|
+
const paths = [];
|
|
292
|
+
for (let i = 0; i < dataSourcesList.length; i++) {
|
|
293
|
+
const ds = dataSourcesList[i];
|
|
294
|
+
const fileName = getExternalDatasourceFileName(systemKey, ds, i);
|
|
295
|
+
const dsPath = path.join(outputDir, fileName);
|
|
296
|
+
await writeComponentFile(dsPath, yaml.dump(ds, { indent: 2, lineWidth: -1 }));
|
|
297
|
+
paths.push(dsPath);
|
|
173
298
|
}
|
|
174
|
-
|
|
175
|
-
// Write README.md
|
|
176
|
-
const readmePath = path.join(outputDir, 'README.md');
|
|
177
|
-
await writeComponentFile(readmePath, readme);
|
|
178
|
-
results.readme = readmePath;
|
|
179
|
-
|
|
180
|
-
return results;
|
|
299
|
+
return paths;
|
|
181
300
|
}
|
|
182
301
|
|
|
183
302
|
/**
|
|
@@ -194,25 +313,11 @@ async function writeExternalSystemAndDatasourceFiles(outputDir, deployment) {
|
|
|
194
313
|
const system = deployment.system;
|
|
195
314
|
const systemKey = system.key || 'external-system';
|
|
196
315
|
const dataSourcesList = deployment.dataSources || deployment.datasources || [];
|
|
197
|
-
const
|
|
198
|
-
|
|
316
|
+
const { roles: _roles, permissions: _permissions, ...systemWithoutRbac } = system;
|
|
199
317
|
const systemPath = path.join(outputDir, `${systemKey}-system.yaml`);
|
|
200
|
-
|
|
201
|
-
await
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const datasourcePaths = [];
|
|
205
|
-
for (let i = 0; i < dataSourcesList.length; i++) {
|
|
206
|
-
const ds = dataSourcesList[i];
|
|
207
|
-
const fileName = getExternalDatasourceFileName(systemKey, ds, i);
|
|
208
|
-
const dsPath = path.join(outputDir, fileName);
|
|
209
|
-
const dsYaml = yaml.dump(ds, { indent: 2, lineWidth: -1 });
|
|
210
|
-
await writeComponentFile(dsPath, dsYaml);
|
|
211
|
-
datasourcePaths.push(dsPath);
|
|
212
|
-
}
|
|
213
|
-
results.datasourceFiles = datasourcePaths;
|
|
214
|
-
|
|
215
|
-
return results;
|
|
318
|
+
await writeComponentFile(systemPath, yaml.dump(systemWithoutRbac, { indent: 2, lineWidth: -1 }));
|
|
319
|
+
const datasourcePaths = await writeDatasourceFiles(outputDir, systemKey, dataSourcesList);
|
|
320
|
+
return { systemFile: systemPath, datasourceFiles: datasourcePaths };
|
|
216
321
|
}
|
|
217
322
|
|
|
218
323
|
/**
|
|
@@ -247,7 +352,49 @@ function normalizeDeploymentForSplit(deployment) {
|
|
|
247
352
|
* @returns {Promise<Object>} Object with paths to generated files
|
|
248
353
|
* @throws {Error} If JSON file not found or invalid
|
|
249
354
|
*/
|
|
250
|
-
|
|
355
|
+
/**
|
|
356
|
+
* Builds write options for split from splitOptions and config array.
|
|
357
|
+
* @param {Object} splitOptions - Split options
|
|
358
|
+
* @param {Array} configArray - Deployment configuration array
|
|
359
|
+
* @returns {Object} Write options for writeComponentFiles
|
|
360
|
+
*/
|
|
361
|
+
function buildSplitWriteOptions(splitOptions, configArray) {
|
|
362
|
+
const writeOptions = {};
|
|
363
|
+
if (splitOptions.mergeEnvTemplate) {
|
|
364
|
+
writeOptions.mergeEnvTemplate = true;
|
|
365
|
+
writeOptions.configuration = configArray;
|
|
366
|
+
}
|
|
367
|
+
if (splitOptions.overwriteReadme === false) {
|
|
368
|
+
writeOptions.overwriteReadme = false;
|
|
369
|
+
}
|
|
370
|
+
return writeOptions;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Writes external system/datasource files if deployment has system and assigns to result.
|
|
375
|
+
* @param {string} finalOutputDir - Output directory
|
|
376
|
+
* @param {Object} deployment - Deployment object
|
|
377
|
+
* @param {Object} result - Result object to mutate
|
|
378
|
+
*/
|
|
379
|
+
async function applyExternalSystemFilesToResult(finalOutputDir, deployment, result) {
|
|
380
|
+
if (!deployment.system || typeof deployment.system !== 'object') {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
const externalFiles = await writeExternalSystemAndDatasourceFiles(finalOutputDir, deployment);
|
|
384
|
+
if (externalFiles.systemFile) result.systemFile = externalFiles.systemFile;
|
|
385
|
+
if (externalFiles.datasourceFiles && externalFiles.datasourceFiles.length > 0) {
|
|
386
|
+
result.datasourceFiles = externalFiles.datasourceFiles;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* @param {string} deployJsonPath - Path to deployment JSON file
|
|
392
|
+
* @param {string} [outputDir] - Directory to write component files
|
|
393
|
+
* @param {Object} [splitOptions] - Options for split behavior
|
|
394
|
+
* @param {boolean} [splitOptions.mergeEnvTemplate] - If true and env.template exists, merge download config into it
|
|
395
|
+
* @param {boolean} [splitOptions.overwriteReadme] - If false and README.md exists, do not overwrite
|
|
396
|
+
*/
|
|
397
|
+
async function splitDeployJson(deployJsonPath, outputDir = null, splitOptions = {}) {
|
|
251
398
|
validateDeployJsonPath(deployJsonPath);
|
|
252
399
|
const finalOutputDir = await prepareOutputDirectory(deployJsonPath, outputDir);
|
|
253
400
|
const deployment = await loadDeploymentJson(deployJsonPath);
|
|
@@ -259,16 +406,9 @@ async function splitDeployJson(deployJsonPath, outputDir = null) {
|
|
|
259
406
|
const rbac = extractRbacYaml(deployment);
|
|
260
407
|
const readme = generateReadmeFromDeployJson(deployment);
|
|
261
408
|
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const externalFiles = await writeExternalSystemAndDatasourceFiles(finalOutputDir, deployment);
|
|
266
|
-
if (externalFiles.systemFile) result.systemFile = externalFiles.systemFile;
|
|
267
|
-
if (externalFiles.datasourceFiles && externalFiles.datasourceFiles.length > 0) {
|
|
268
|
-
result.datasourceFiles = externalFiles.datasourceFiles;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
409
|
+
const writeOptions = buildSplitWriteOptions(splitOptions, configArray);
|
|
410
|
+
const result = await writeComponentFiles(finalOutputDir, envTemplate, variables, rbac, readme, writeOptions);
|
|
411
|
+
await applyExternalSystemFilesToResult(finalOutputDir, deployment, result);
|
|
272
412
|
return result;
|
|
273
413
|
}
|
|
274
414
|
|