@aifabrix/builder 2.41.0 ā 2.42.1
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 +2 -2
- package/integration/hubspot/README.md +11 -5
- 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/jest.config.manual.js +2 -1
- 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/pipeline.types.js +37 -0
- package/lib/api/wizard-platform.api.js +61 -0
- package/lib/api/wizard.api.js +36 -2
- package/lib/app/config.js +23 -11
- package/lib/app/index.js +5 -3
- package/lib/app/prompts.js +46 -31
- package/lib/app/readme.js +11 -4
- package/lib/app/run-env-compose.js +64 -1
- package/lib/app/run-helpers.js +1 -1
- package/lib/app/show-display.js +1 -1
- package/lib/cli/setup-app.js +45 -14
- package/lib/cli/setup-credential-deployment.js +31 -6
- package/lib/cli/setup-dev.js +27 -0
- package/lib/cli/setup-environment.js +12 -4
- package/lib/cli/setup-external-system.js +19 -4
- package/lib/cli/setup-infra.js +54 -14
- package/lib/cli/setup-utility.js +117 -21
- package/lib/commands/auth-config.js +22 -12
- 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-init.js +39 -1
- 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 +518 -0
- package/lib/commands/secrets-set.js +6 -0
- package/lib/commands/test-e2e-external.js +165 -0
- package/lib/commands/up-dataplane.js +90 -6
- package/lib/commands/upload.js +71 -40
- package/lib/commands/wizard-core-helpers.js +230 -5
- package/lib/commands/wizard-core.js +68 -29
- package/lib/commands/wizard-dataplane.js +1 -1
- package/lib/commands/wizard-entity-selection.js +43 -0
- package/lib/commands/wizard-headless.js +49 -5
- package/lib/commands/wizard-helpers.js +7 -3
- package/lib/commands/wizard.js +93 -64
- package/lib/core/config.js +7 -1
- package/lib/core/secrets.js +33 -12
- package/lib/datasource/deploy.js +12 -3
- package/lib/datasource/test-e2e.js +219 -0
- package/lib/datasource/test-integration.js +154 -0
- package/lib/deployment/deployer.js +7 -5
- package/lib/external-system/download-helpers.js +3 -1
- package/lib/external-system/download.js +182 -204
- package/lib/external-system/generator.js +204 -56
- 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 +51 -18
- package/lib/generator/external-controller-manifest.js +29 -2
- package/lib/generator/external-schema-utils.js +4 -2
- package/lib/generator/external.js +10 -3
- package/lib/generator/index.js +4 -1
- 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 +326 -0
- package/lib/generator/wizard-prompts.js +105 -106
- package/lib/generator/wizard-readme.js +91 -0
- package/lib/generator/wizard.js +180 -179
- package/lib/infrastructure/compose.js +11 -1
- package/lib/infrastructure/index.js +11 -3
- package/lib/infrastructure/services.js +22 -11
- package/lib/schema/application-schema.json +8 -5
- package/lib/schema/external-datasource.schema.json +49 -26
- package/lib/schema/external-system.schema.json +82 -6
- package/lib/schema/wizard-config.schema.json +23 -1
- package/lib/utils/api.js +38 -10
- package/lib/utils/auth-headers.js +8 -7
- package/lib/utils/compose-generator.js +1 -1
- package/lib/utils/compose-handlebars-helpers.js +11 -0
- package/lib/utils/config-format-preference.js +51 -0
- package/lib/utils/config-format.js +36 -0
- package/lib/utils/configuration-env-resolver.js +179 -0
- package/lib/utils/credential-display.js +83 -0
- package/lib/utils/credential-secrets-env.js +115 -25
- 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/env-copy.js +23 -3
- package/lib/utils/error-formatters/http-status-errors.js +0 -1
- 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 +89 -30
- 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 +1 -0
- package/lib/utils/infra-status.js +50 -44
- package/lib/utils/local-secrets.js +5 -5
- package/lib/utils/paths.js +85 -4
- package/lib/utils/secrets-canonical.js +93 -0
- package/lib/utils/secrets-generator.js +20 -0
- package/lib/utils/secrets-helpers.js +75 -89
- package/lib/utils/test-log-writer.js +56 -0
- package/lib/utils/token-manager.js +24 -32
- 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 +7 -9
- package/lib/validation/wizard-datasource-validation.js +50 -0
- package/package.json +7 -2
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +5 -5
- package/templates/applications/dataplane/rbac.yaml +2 -2
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/external-system/README.md.hbs +75 -22
- 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 +4 -4
- package/templates/typescript/docker-compose.hbs +4 -4
- 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
|
|
194
|
-
*/
|
|
195
|
-
/**
|
|
196
|
-
* Generates system file
|
|
197
|
-
* @async
|
|
198
|
-
* @function generateSystemFile
|
|
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
|
|
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
|
|
203
184
|
*/
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
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,25 +293,85 @@ 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
|
-
const finalPath = appPath || path.join(process.cwd(), 'integration', systemKey);
|
|
364
|
+
logger.log(chalk.blue(`š Creating directory: ${finalPath}`));
|
|
365
|
+
await fs.mkdir(finalPath, { recursive: true });
|
|
389
366
|
|
|
390
|
-
|
|
391
|
-
|
|
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)}`));
|
|
371
|
+
|
|
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
376
|
try {
|
|
394
377
|
const secretsEnsure = require('../core/secrets-ensure');
|
|
@@ -397,9 +380,6 @@ async function processDownloadedSystem(systemKey, application, dataSources, temp
|
|
|
397
380
|
if (err.code !== 'ENOENT') logger.warn(`Could not ensure integration placeholder secrets: ${err.message}`);
|
|
398
381
|
}
|
|
399
382
|
|
|
400
|
-
// Clean up temporary folder
|
|
401
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
402
|
-
|
|
403
383
|
return finalPath;
|
|
404
384
|
}
|
|
405
385
|
|
|
@@ -422,50 +402,52 @@ function displayDownloadSuccess(systemKey, finalPath, datasourceCount) {
|
|
|
422
402
|
* @function downloadExternalSystem
|
|
423
403
|
* @param {string} systemKey - System key or ID
|
|
424
404
|
* @param {Object} options - Download options
|
|
405
|
+
* @param {string} [options.format] - Output format: 'yaml' (default) or 'json' (runs convert after split)
|
|
425
406
|
* @param {string} [options.environment] - Environment (dev, tst, pro)
|
|
426
407
|
* @param {string} [options.controller] - Controller URL
|
|
427
408
|
* @param {boolean} [options.dryRun] - Show what would be downloaded without actually downloading
|
|
409
|
+
* @param {boolean} [options.force] - Overwrite existing README.md without prompting
|
|
428
410
|
* @returns {Promise<void>} Resolves when download completes
|
|
429
411
|
* @throws {Error} If download fails
|
|
430
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
|
+
|
|
431
423
|
async function downloadExternalSystem(systemKey, options = {}) {
|
|
432
424
|
validateSystemKeyFormat(systemKey);
|
|
433
425
|
|
|
426
|
+
const format = (options.format || 'yaml').toLowerCase();
|
|
427
|
+
|
|
434
428
|
try {
|
|
435
429
|
logger.log(chalk.blue(`\nš„ Downloading external system: ${systemKey}`));
|
|
436
430
|
|
|
437
|
-
// Get authentication and dataplane URL
|
|
438
431
|
const config = await getConfig();
|
|
439
432
|
const { authConfig, dataplaneUrl } = await setupAuthenticationAndDataplane(systemKey, options, config);
|
|
440
433
|
|
|
441
|
-
// Handle dry run
|
|
442
434
|
if (options.dryRun) {
|
|
443
435
|
handleDryRun(systemKey, dataplaneUrl);
|
|
444
436
|
return;
|
|
445
437
|
}
|
|
446
438
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
try {
|
|
458
|
-
const finalPath = await processDownloadedSystem(systemKey, application, dataSources, tempDir);
|
|
459
|
-
displayDownloadSuccess(systemKey, finalPath, dataSources.length);
|
|
460
|
-
} catch (error) {
|
|
461
|
-
// Clean up temporary folder on error
|
|
462
|
-
try {
|
|
463
|
-
await fs.rm(tempDir, { recursive: true, force: true });
|
|
464
|
-
} catch {
|
|
465
|
-
// Ignore cleanup errors
|
|
466
|
-
}
|
|
467
|
-
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);
|
|
468
448
|
}
|
|
449
|
+
|
|
450
|
+
displayDownloadSuccess(systemKey, finalPath, manifest.dataSources.length);
|
|
469
451
|
} catch (error) {
|
|
470
452
|
throw new Error(`Failed to download external system: ${error.message}`);
|
|
471
453
|
}
|
|
@@ -474,9 +456,5 @@ async function downloadExternalSystem(systemKey, options = {}) {
|
|
|
474
456
|
module.exports = {
|
|
475
457
|
downloadExternalSystem,
|
|
476
458
|
validateSystemType,
|
|
477
|
-
validateDownloadedData
|
|
478
|
-
generateVariablesYaml,
|
|
479
|
-
generateReadme,
|
|
480
|
-
generateEnvTemplate,
|
|
481
|
-
handlePartialDownload
|
|
459
|
+
validateDownloadedData
|
|
482
460
|
};
|