@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
|
@@ -1,28 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* External System Download Module
|
|
3
3
|
*
|
|
4
|
-
* Downloads
|
|
5
|
-
*
|
|
4
|
+
* Downloads full running manifest from dataplane and splits it into component files
|
|
5
|
+
* using split-json. The dataplane GET /api/v1/external/systems/{systemKey}/config
|
|
6
|
+
* returns { application, dataSources, version } in pipeline format. Supports legacy
|
|
7
|
+
* responses where datasources are inline in application.configuration.dataSources.
|
|
8
|
+
*
|
|
9
|
+
* Process: validate key ā auth + dataplane (Bearer required) ā GET full manifest ā
|
|
10
|
+
* validate ā build deploy JSON (augments configuration with auth KV_* for env.template) ā
|
|
11
|
+
* write deploy JSON ā splitDeployJson (merge env.template if exists; README overwrite per
|
|
12
|
+
* prompt or --force) ā ensure placeholder secrets from env.template ā convert to JSON if
|
|
13
|
+
* --format json.
|
|
6
14
|
*
|
|
7
15
|
* @fileoverview External system download functionality for AI Fabrix Builder
|
|
8
16
|
* @author AI Fabrix Team
|
|
9
17
|
* @version 2.0.0
|
|
18
|
+
* @see docs/commands/external-integration.md#aifabrix-download-system-key
|
|
10
19
|
*/
|
|
11
20
|
|
|
12
21
|
const fs = require('fs').promises;
|
|
22
|
+
const fsSync = require('fs');
|
|
13
23
|
const path = require('path');
|
|
14
|
-
const
|
|
24
|
+
const readline = require('readline');
|
|
15
25
|
const yaml = require('js-yaml');
|
|
16
26
|
const chalk = require('chalk');
|
|
17
27
|
const { getExternalSystemConfig } = require('../api/external-systems.api');
|
|
18
|
-
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
28
|
+
const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../utils/token-manager');
|
|
19
29
|
const { getConfig } = require('../core/config');
|
|
20
|
-
const {
|
|
30
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
31
|
+
const { retemplateConfigurationForDownload } = require('../utils/configuration-env-resolver');
|
|
21
32
|
const logger = require('../utils/logger');
|
|
22
|
-
const { writeConfigFile } = require('../utils/config-format');
|
|
23
|
-
const { generateEnvTemplate } = require('../utils/external-system-env-helpers');
|
|
24
|
-
const { generateVariablesYaml, generateReadme } = require('./download-helpers');
|
|
25
33
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
34
|
+
const generator = require('../generator');
|
|
26
35
|
|
|
27
36
|
/**
|
|
28
37
|
* Validates system type from downloaded application
|
|
@@ -97,25 +106,6 @@ function validateDownloadedData(application, dataSources) {
|
|
|
97
106
|
}
|
|
98
107
|
}
|
|
99
108
|
|
|
100
|
-
/**
|
|
101
|
-
* Handles partial download errors gracefully
|
|
102
|
-
* @param {string} systemKey - System key
|
|
103
|
-
* @param {Object} systemData - System data that was successfully downloaded
|
|
104
|
-
* @param {Array<Error>} datasourceErrors - Array of errors from datasource downloads
|
|
105
|
-
* @throws {Error} Aggregated error message
|
|
106
|
-
*/
|
|
107
|
-
function handlePartialDownload(systemKey, systemData, datasourceErrors) {
|
|
108
|
-
if (datasourceErrors.length === 0) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const errorMessages = datasourceErrors.map(err => err.message).join('\n - ');
|
|
113
|
-
throw new Error(
|
|
114
|
-
`Partial download completed for system '${systemKey}', but some datasources failed:\n - ${errorMessages}\n\n` +
|
|
115
|
-
'System configuration was downloaded successfully. You may need to download datasources separately.'
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
109
|
/**
|
|
120
110
|
* Setup authentication and get dataplane URL
|
|
121
111
|
* @async
|
|
@@ -131,7 +121,8 @@ async function setupAuthenticationAndDataplane(systemKey, _options, _config) {
|
|
|
131
121
|
const controllerUrl = await resolveControllerUrl();
|
|
132
122
|
const authConfig = await getDeploymentAuth(controllerUrl, environment, systemKey);
|
|
133
123
|
|
|
134
|
-
|
|
124
|
+
requireBearerForDataplanePipeline(authConfig);
|
|
125
|
+
if (!authConfig.token) {
|
|
135
126
|
throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
|
|
136
127
|
}
|
|
137
128
|
|
|
@@ -144,16 +135,19 @@ async function setupAuthenticationAndDataplane(systemKey, _options, _config) {
|
|
|
144
135
|
}
|
|
145
136
|
|
|
146
137
|
/**
|
|
147
|
-
* Download
|
|
138
|
+
* Download full running manifest from dataplane
|
|
139
|
+
* GET /api/v1/external/systems/{systemKey}/config returns
|
|
140
|
+
* { application, dataSources, version } in pipeline format.
|
|
141
|
+
*
|
|
148
142
|
* @async
|
|
149
143
|
* @param {string} dataplaneUrl - Dataplane URL
|
|
150
144
|
* @param {string} systemKey - System key
|
|
151
145
|
* @param {Object} authConfig - Authentication configuration
|
|
152
|
-
* @returns {Promise<
|
|
146
|
+
* @returns {Promise<{application: Object, dataSources: Array, version?: string}>} Full manifest
|
|
153
147
|
* @throws {Error} If download fails
|
|
154
148
|
*/
|
|
155
|
-
async function
|
|
156
|
-
logger.log(chalk.blue(`š” Downloading
|
|
149
|
+
async function downloadFullManifest(dataplaneUrl, systemKey, authConfig) {
|
|
150
|
+
logger.log(chalk.blue(`š” Downloading full manifest: ${systemKey}`));
|
|
157
151
|
const response = await getExternalSystemConfig(dataplaneUrl, systemKey, authConfig);
|
|
158
152
|
|
|
159
153
|
if (!response.success || !response.data) {
|
|
@@ -163,14 +157,14 @@ async function downloadSystemConfiguration(dataplaneUrl, systemKey, authConfig)
|
|
|
163
157
|
const downloadData = response.data.data || response.data;
|
|
164
158
|
let application = downloadData.application;
|
|
165
159
|
let dataSources = downloadData.dataSources || [];
|
|
160
|
+
const version = downloadData.version;
|
|
166
161
|
|
|
167
|
-
//
|
|
162
|
+
// Legacy: datasources inline in application.configuration.dataSources
|
|
168
163
|
if (!application && downloadData.configuration) {
|
|
169
164
|
application = downloadData;
|
|
170
165
|
}
|
|
171
166
|
if (application && application.configuration && Array.isArray(application.configuration.dataSources)) {
|
|
172
167
|
dataSources = [...dataSources, ...application.configuration.dataSources];
|
|
173
|
-
// Remove inline datasources from application to avoid duplication
|
|
174
168
|
const { dataSources: _inlineDataSources, ...configWithoutDataSources } = application.configuration;
|
|
175
169
|
application = { ...application, configuration: configWithoutDataSources };
|
|
176
170
|
}
|
|
@@ -179,149 +173,78 @@ async function downloadSystemConfiguration(dataplaneUrl, systemKey, authConfig)
|
|
|
179
173
|
throw new Error('Application configuration not found in download response');
|
|
180
174
|
}
|
|
181
175
|
|
|
182
|
-
return { application, dataSources };
|
|
176
|
+
return { application, dataSources, version };
|
|
183
177
|
}
|
|
184
178
|
|
|
185
179
|
/**
|
|
186
|
-
*
|
|
187
|
-
* @
|
|
188
|
-
* @param {string}
|
|
189
|
-
* @
|
|
190
|
-
* @param {Object} application - Application configuration
|
|
191
|
-
* @param {Array} dataSources - Array of datasource configurations
|
|
192
|
-
* @returns {Promise<Object>} Object with file paths
|
|
193
|
-
* @throws {Error} If file generation fails
|
|
180
|
+
* Derives env variable name from system key and security key (e.g. KV_HUBSPOT_CLIENTID).
|
|
181
|
+
* @param {string} systemKey - System key (e.g. 'hubspot')
|
|
182
|
+
* @param {string} securityKey - Security key (e.g. 'clientId')
|
|
183
|
+
* @returns {string} Variable name
|
|
194
184
|
*/
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
* @param {string} tempDir - Temporary directory
|
|
200
|
-
* @param {string} systemKey - System key
|
|
201
|
-
* @param {Object} application - Application object
|
|
202
|
-
* @returns {Promise<string>} System file path
|
|
203
|
-
*/
|
|
204
|
-
async function generateSystemFile(tempDir, systemKey, application) {
|
|
205
|
-
const systemFileName = `${systemKey}-system.yaml`;
|
|
206
|
-
const systemFilePath = path.join(tempDir, systemFileName);
|
|
207
|
-
writeConfigFile(systemFilePath, application);
|
|
208
|
-
return systemFilePath;
|
|
185
|
+
function deriveAuthVarName(systemKey, securityKey) {
|
|
186
|
+
const safeKey = (systemKey || '').toUpperCase().replace(/-/g, '_');
|
|
187
|
+
const safeSec = (securityKey || '').replace(/([A-Z])/g, '_$1').toUpperCase().replace(/^_/, '');
|
|
188
|
+
return `KV_${safeKey}_${safeSec}`;
|
|
209
189
|
}
|
|
210
190
|
|
|
211
191
|
/**
|
|
212
|
-
*
|
|
213
|
-
* @
|
|
214
|
-
* @
|
|
215
|
-
* @param {string} tempDir - Temporary directory
|
|
216
|
-
* @param {string} systemKey - System key
|
|
217
|
-
* @param {Array} dataSources - Array of datasource objects
|
|
218
|
-
* @returns {Promise<Object>} Object with datasourceFiles array and datasourceErrors array
|
|
192
|
+
* Collects kv:// paths already present in configuration array.
|
|
193
|
+
* @param {Array} config - Configuration array
|
|
194
|
+
* @returns {Set<string>} Set of kv paths
|
|
219
195
|
*/
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
let datasourceKeyOnly;
|
|
228
|
-
if (datasourceKey.startsWith(`${systemKey}-`)) {
|
|
229
|
-
datasourceKeyOnly = datasourceKey.substring(systemKey.length + 1);
|
|
230
|
-
} else {
|
|
231
|
-
const entityType = datasource.entityType || datasource.entityKey || datasourceKey.split('-').pop();
|
|
232
|
-
datasourceKeyOnly = entityType;
|
|
233
|
-
}
|
|
234
|
-
const datasourceFileName = `${systemKey}-datasource-${datasourceKeyOnly}.yaml`;
|
|
235
|
-
const datasourceFilePath = path.join(tempDir, datasourceFileName);
|
|
236
|
-
writeConfigFile(datasourceFilePath, datasource);
|
|
237
|
-
datasourceFiles.push(datasourceFilePath);
|
|
238
|
-
} catch (error) {
|
|
239
|
-
datasourceErrors.push(new Error(`Failed to write datasource ${datasource.key}: ${error.message}`));
|
|
240
|
-
}
|
|
196
|
+
function collectExistingKvPaths(config) {
|
|
197
|
+
const paths = new Set();
|
|
198
|
+
for (const item of config) {
|
|
199
|
+
if (!item || !item.value) continue;
|
|
200
|
+
const val = String(item.value).trim();
|
|
201
|
+
if (val.startsWith('kv://')) paths.add(val);
|
|
202
|
+
else if (item.location === 'keyvault') paths.add(`kv://${val}`);
|
|
241
203
|
}
|
|
242
|
-
return
|
|
204
|
+
return paths;
|
|
243
205
|
}
|
|
244
206
|
|
|
245
207
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* @
|
|
249
|
-
* @param {string} tempDir - Temporary directory
|
|
250
|
-
* @param {string} systemKey - System key
|
|
251
|
-
* @param {Object} application - Application object
|
|
252
|
-
* @param {Array} dataSources - Array of datasource objects
|
|
253
|
-
* @returns {Promise<Object>} Object with file paths
|
|
208
|
+
* Augments system.configuration with entries for authentication.security kv paths
|
|
209
|
+
* so that extractEnvTemplate produces env.template that passes validateAuthKvCoverage.
|
|
210
|
+
* @param {Object} system - System config (mutated)
|
|
254
211
|
*/
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return { variablesPath, envTemplatePath, readmePath };
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
async function generateFilesInTempDir(tempDir, systemKey, application, dataSources) {
|
|
275
|
-
const systemFilePath = await generateSystemFile(tempDir, systemKey, application);
|
|
276
|
-
|
|
277
|
-
const { datasourceFiles, datasourceErrors } = await generateDatasourceFiles(tempDir, systemKey, dataSources);
|
|
278
|
-
|
|
279
|
-
// Handle partial downloads
|
|
280
|
-
if (datasourceErrors.length > 0) {
|
|
281
|
-
handlePartialDownload(systemKey, application, datasourceErrors);
|
|
212
|
+
function augmentConfigurationWithAuthSecrets(system) {
|
|
213
|
+
if (!system || typeof system !== 'object') return;
|
|
214
|
+
const security = system.authentication?.security;
|
|
215
|
+
if (!security || typeof security !== 'object') return;
|
|
216
|
+
const config = Array.isArray(system.configuration) ? [...system.configuration] : [];
|
|
217
|
+
const existingPaths = collectExistingKvPaths(config);
|
|
218
|
+
const systemKey = system.key || '';
|
|
219
|
+
for (const [key, val] of Object.entries(security)) {
|
|
220
|
+
if (typeof val !== 'string' || !/^kv:\/\/.+/.test(val)) continue;
|
|
221
|
+
if (existingPaths.has(val)) continue;
|
|
222
|
+
config.push({
|
|
223
|
+
name: deriveAuthVarName(systemKey, key),
|
|
224
|
+
value: val.replace(/^kv:\/\//, ''),
|
|
225
|
+
location: 'keyvault',
|
|
226
|
+
required: true
|
|
227
|
+
});
|
|
282
228
|
}
|
|
283
|
-
|
|
284
|
-
const { variablesPath, envTemplatePath, readmePath } = await generateConfigFiles(tempDir, systemKey, application, dataSources);
|
|
285
|
-
|
|
286
|
-
return {
|
|
287
|
-
systemFilePath,
|
|
288
|
-
variablesPath,
|
|
289
|
-
envTemplatePath,
|
|
290
|
-
readmePath,
|
|
291
|
-
datasourceFiles
|
|
292
|
-
};
|
|
229
|
+
system.configuration = config;
|
|
293
230
|
}
|
|
294
231
|
|
|
295
232
|
/**
|
|
296
|
-
*
|
|
297
|
-
* @
|
|
298
|
-
* @param {
|
|
299
|
-
* @param {string}
|
|
300
|
-
* @
|
|
301
|
-
* @param {Object} filePaths - Object with file paths
|
|
302
|
-
* @throws {Error} If file move fails
|
|
233
|
+
* Build deploy JSON from full running manifest (split-json compatible).
|
|
234
|
+
* @param {Object} application - System config from dataplane
|
|
235
|
+
* @param {Array} dataSources - Inline datasource configs
|
|
236
|
+
* @param {string} [version] - Optional version
|
|
237
|
+
* @returns {Object} Deploy JSON object for splitDeployJson
|
|
303
238
|
*/
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
{ from: filePaths.readmePath, to: path.join(finalPath, 'README.md') }
|
|
314
|
-
];
|
|
315
|
-
|
|
316
|
-
for (const dsFile of filePaths.datasourceFiles) {
|
|
317
|
-
const fileName = path.basename(dsFile);
|
|
318
|
-
filesToMove.push({ from: dsFile, to: path.join(finalPath, fileName) });
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
for (const file of filesToMove) {
|
|
322
|
-
await fs.copyFile(file.from, file.to);
|
|
323
|
-
logger.log(chalk.green(`ā Created: ${path.relative(process.cwd(), file.to)}`));
|
|
324
|
-
}
|
|
239
|
+
function buildDeployJsonFromManifest(application, dataSources, version) {
|
|
240
|
+
const dataSourcesKeys = Array.isArray(dataSources)
|
|
241
|
+
? dataSources.map(ds => (ds && ds.key) ? ds.key : ds)
|
|
242
|
+
: [];
|
|
243
|
+
const system = { ...application, dataSources: application.dataSources || dataSourcesKeys };
|
|
244
|
+
augmentConfigurationWithAuthSecrets(system);
|
|
245
|
+
const deploy = { system, dataSources };
|
|
246
|
+
if (version) deploy.version = version;
|
|
247
|
+
return deploy;
|
|
325
248
|
}
|
|
326
249
|
|
|
327
250
|
/**
|
|
@@ -346,8 +269,8 @@ function validateSystemKeyFormat(systemKey) {
|
|
|
346
269
|
function handleDryRun(systemKey, dataplaneUrl) {
|
|
347
270
|
logger.log(chalk.yellow('š Dry run mode - would download from:'));
|
|
348
271
|
logger.log(chalk.gray(` ${dataplaneUrl}/api/v1/external/systems/${systemKey}/config`));
|
|
349
|
-
logger.log(chalk.yellow('\nWould create:'));
|
|
350
|
-
logger.log(chalk.gray(` integration/${systemKey}
|
|
272
|
+
logger.log(chalk.yellow('\nWould create (via split-json):'));
|
|
273
|
+
logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-deploy.json`));
|
|
351
274
|
logger.log(chalk.gray(` integration/${systemKey}/application.yaml`));
|
|
352
275
|
logger.log(chalk.gray(` integration/${systemKey}/${systemKey}-system.yaml`));
|
|
353
276
|
logger.log(chalk.gray(` integration/${systemKey}/env.template`));
|
|
@@ -370,28 +293,92 @@ function validateAndLogDownloadedData(application, dataSources) {
|
|
|
370
293
|
}
|
|
371
294
|
|
|
372
295
|
/**
|
|
373
|
-
*
|
|
296
|
+
* Prompts user: "Do you want to replace README.md? (yes/no)"
|
|
297
|
+
* @returns {Promise<boolean>} True if user answers yes
|
|
298
|
+
*/
|
|
299
|
+
function promptReplaceReadme() {
|
|
300
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
301
|
+
return new Promise(resolve => {
|
|
302
|
+
rl.question(chalk.yellow('README.md already exists. Do you want to replace it? (yes/no) '), answer => {
|
|
303
|
+
rl.close();
|
|
304
|
+
const normalized = (answer || '').trim().toLowerCase();
|
|
305
|
+
resolve(normalized === 'yes' || normalized === 'y');
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Resolves split options for download: merge env.template if it exists, prompt for README replace if it exists (unless force).
|
|
312
|
+
* @param {string} finalPath - Integration directory path
|
|
313
|
+
* @param {Object} [options] - Download options
|
|
314
|
+
* @param {boolean} [options.force] - If true, overwrite README.md without prompting
|
|
315
|
+
* @returns {Promise<{ mergeEnvTemplate: boolean, overwriteReadme: boolean }>}
|
|
316
|
+
*/
|
|
317
|
+
async function resolveDownloadSplitOptions(finalPath, options = {}) {
|
|
318
|
+
const opts = { mergeEnvTemplate: false, overwriteReadme: true };
|
|
319
|
+
if (!fsSync.existsSync(finalPath)) return opts;
|
|
320
|
+
const envPath = path.join(finalPath, 'env.template');
|
|
321
|
+
const readmePath = path.join(finalPath, 'README.md');
|
|
322
|
+
if (fsSync.existsSync(envPath)) opts.mergeEnvTemplate = true;
|
|
323
|
+
if (fsSync.existsSync(readmePath)) {
|
|
324
|
+
if (options.force) {
|
|
325
|
+
opts.overwriteReadme = true;
|
|
326
|
+
} else {
|
|
327
|
+
opts.overwriteReadme = await promptReplaceReadme();
|
|
328
|
+
if (!opts.overwriteReadme) logger.log(chalk.gray(' Keeping existing README.md'));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return opts;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Re-templates system file configuration from env.template when present (mutates file on disk).
|
|
336
|
+
* @param {string} systemKey - System key
|
|
337
|
+
* @param {string} systemFilePath - Path to *-system.yaml
|
|
338
|
+
* @returns {Promise<void>}
|
|
339
|
+
*/
|
|
340
|
+
async function applyRetemplateToSystemFile(systemKey, systemFilePath) {
|
|
341
|
+
if (!systemFilePath || !fsSync.existsSync(systemFilePath)) return;
|
|
342
|
+
const systemContent = await fs.readFile(systemFilePath, 'utf8');
|
|
343
|
+
const systemObj = yaml.load(systemContent);
|
|
344
|
+
if (!Array.isArray(systemObj?.configuration)) return;
|
|
345
|
+
const applied = await retemplateConfigurationForDownload(systemKey, systemObj.configuration);
|
|
346
|
+
if (applied) {
|
|
347
|
+
await fs.writeFile(systemFilePath, yaml.dump(systemObj, { indent: 2, lineWidth: -1 }), 'utf8');
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Write deploy JSON and split into component files
|
|
374
353
|
* @async
|
|
375
354
|
* @param {string} systemKey - System key
|
|
376
|
-
* @param {Object}
|
|
377
|
-
* @param {
|
|
378
|
-
* @param {string} tempDir - Temporary directory path
|
|
355
|
+
* @param {Object} manifest - Full manifest { application, dataSources, version }
|
|
356
|
+
* @param {Object} [splitOptions] - Options for split (mergeEnvTemplate, overwriteReadme)
|
|
379
357
|
* @returns {Promise<string>} Final destination path
|
|
380
358
|
* @throws {Error} If processing fails
|
|
381
359
|
*/
|
|
382
|
-
async function processDownloadedSystem(systemKey,
|
|
383
|
-
|
|
384
|
-
const
|
|
360
|
+
async function processDownloadedSystem(systemKey, manifest, splitOptions = {}) {
|
|
361
|
+
const { application, dataSources, version } = manifest;
|
|
362
|
+
const finalPath = getIntegrationPath(systemKey);
|
|
385
363
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
364
|
+
logger.log(chalk.blue(`š Creating directory: ${finalPath}`));
|
|
365
|
+
await fs.mkdir(finalPath, { recursive: true });
|
|
366
|
+
|
|
367
|
+
const deployJson = buildDeployJsonFromManifest(application, dataSources, version);
|
|
368
|
+
const deployJsonPath = path.join(finalPath, `${systemKey}-deploy.json`);
|
|
369
|
+
await fs.writeFile(deployJsonPath, JSON.stringify(deployJson, null, 2), 'utf8');
|
|
370
|
+
logger.log(chalk.green(`ā Created: ${path.relative(process.cwd(), deployJsonPath)}`));
|
|
389
371
|
|
|
390
|
-
|
|
391
|
-
await
|
|
372
|
+
logger.log(chalk.blue('š Splitting deploy JSON into component files...'));
|
|
373
|
+
const splitResult = await generator.splitDeployJson(deployJsonPath, finalPath, splitOptions);
|
|
374
|
+
await applyRetemplateToSystemFile(systemKey, splitResult.systemFile);
|
|
392
375
|
|
|
393
|
-
|
|
394
|
-
|
|
376
|
+
try {
|
|
377
|
+
const secretsEnsure = require('../core/secrets-ensure');
|
|
378
|
+
await secretsEnsure.ensureSecretsFromEnvTemplate(path.join(finalPath, 'env.template'), { emptyValuesForCredentials: true });
|
|
379
|
+
} catch (err) {
|
|
380
|
+
if (err.code !== 'ENOENT') logger.warn(`Could not ensure integration placeholder secrets: ${err.message}`);
|
|
381
|
+
}
|
|
395
382
|
|
|
396
383
|
return finalPath;
|
|
397
384
|
}
|
|
@@ -415,50 +402,52 @@ function displayDownloadSuccess(systemKey, finalPath, datasourceCount) {
|
|
|
415
402
|
* @function downloadExternalSystem
|
|
416
403
|
* @param {string} systemKey - System key or ID
|
|
417
404
|
* @param {Object} options - Download options
|
|
405
|
+
* @param {string} [options.format] - Output format: 'yaml' (default) or 'json' (runs convert after split)
|
|
418
406
|
* @param {string} [options.environment] - Environment (dev, tst, pro)
|
|
419
407
|
* @param {string} [options.controller] - Controller URL
|
|
420
408
|
* @param {boolean} [options.dryRun] - Show what would be downloaded without actually downloading
|
|
409
|
+
* @param {boolean} [options.force] - Overwrite existing README.md without prompting
|
|
421
410
|
* @returns {Promise<void>} Resolves when download completes
|
|
422
411
|
* @throws {Error} If download fails
|
|
423
412
|
*/
|
|
413
|
+
async function runConvertToJsonIfRequested(systemKey) {
|
|
414
|
+
const { runConvert } = require('../commands/convert');
|
|
415
|
+
try {
|
|
416
|
+
await runConvert(systemKey, { format: 'json', force: true });
|
|
417
|
+
logger.log(chalk.green('ā Converted component files to JSON'));
|
|
418
|
+
} catch (convertErr) {
|
|
419
|
+
throw new Error(`Download succeeded but convert to JSON failed: ${convertErr.message}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
424
423
|
async function downloadExternalSystem(systemKey, options = {}) {
|
|
425
424
|
validateSystemKeyFormat(systemKey);
|
|
426
425
|
|
|
426
|
+
const format = (options.format || 'yaml').toLowerCase();
|
|
427
|
+
|
|
427
428
|
try {
|
|
428
429
|
logger.log(chalk.blue(`\nš„ Downloading external system: ${systemKey}`));
|
|
429
430
|
|
|
430
|
-
// Get authentication and dataplane URL
|
|
431
431
|
const config = await getConfig();
|
|
432
432
|
const { authConfig, dataplaneUrl } = await setupAuthenticationAndDataplane(systemKey, options, config);
|
|
433
433
|
|
|
434
|
-
// Handle dry run
|
|
435
434
|
if (options.dryRun) {
|
|
436
435
|
handleDryRun(systemKey, dataplaneUrl);
|
|
437
436
|
return;
|
|
438
437
|
}
|
|
439
438
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
try {
|
|
451
|
-
const finalPath = await processDownloadedSystem(systemKey, application, dataSources, tempDir);
|
|
452
|
-
displayDownloadSuccess(systemKey, finalPath, dataSources.length);
|
|
453
|
-
} catch (error) {
|
|
454
|
-
// Clean up temporary folder on error
|
|
455
|
-
try {
|
|
456
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
457
|
-
} catch {
|
|
458
|
-
// Ignore cleanup errors
|
|
459
|
-
}
|
|
460
|
-
throw error;
|
|
439
|
+
const manifest = await downloadFullManifest(dataplaneUrl, systemKey, authConfig);
|
|
440
|
+
validateAndLogDownloadedData(manifest.application, manifest.dataSources);
|
|
441
|
+
|
|
442
|
+
const finalPath = getIntegrationPath(systemKey);
|
|
443
|
+
const splitOptions = await resolveDownloadSplitOptions(finalPath, options);
|
|
444
|
+
await processDownloadedSystem(systemKey, manifest, splitOptions);
|
|
445
|
+
|
|
446
|
+
if (format === 'json') {
|
|
447
|
+
await runConvertToJsonIfRequested(systemKey);
|
|
461
448
|
}
|
|
449
|
+
|
|
450
|
+
displayDownloadSuccess(systemKey, finalPath, manifest.dataSources.length);
|
|
462
451
|
} catch (error) {
|
|
463
452
|
throw new Error(`Failed to download external system: ${error.message}`);
|
|
464
453
|
}
|
|
@@ -467,9 +456,5 @@ async function downloadExternalSystem(systemKey, options = {}) {
|
|
|
467
456
|
module.exports = {
|
|
468
457
|
downloadExternalSystem,
|
|
469
458
|
validateSystemType,
|
|
470
|
-
validateDownloadedData
|
|
471
|
-
generateVariablesYaml,
|
|
472
|
-
generateReadme,
|
|
473
|
-
generateEnvTemplate,
|
|
474
|
-
handlePartialDownload
|
|
459
|
+
validateDownloadedData
|
|
475
460
|
};
|