@aifabrix/builder 2.41.0 ā 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 +1 -1
- 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/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 +34 -1
- package/lib/app/config.js +23 -11
- package/lib/app/index.js +3 -1
- package/lib/app/prompts.js +44 -29
- package/lib/app/readme.js +8 -3
- 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 +42 -11
- 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/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 +507 -0
- package/lib/commands/test-e2e-external.js +165 -0
- package/lib/commands/upload.js +71 -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/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.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 +1 -1
- 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 +294 -0
- package/lib/generator/wizard-prompts.js +105 -106
- package/lib/generator/wizard-readme.js +88 -0
- package/lib/generator/wizard.js +147 -158
- 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 +16 -0
- 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 +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 +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 +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 +4 -4
- package/templates/typescript/docker-compose.hbs +4 -4
- package/integration/hubspot/application.yaml +0 -37
|
@@ -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
|
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Secondary wizard prompts (credential retry, platform, config review)
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const inquirer = require('inquirer');
|
|
8
|
+
const yaml = require('js-yaml');
|
|
9
|
+
|
|
10
|
+
let hasAutocompletePrompt = false;
|
|
11
|
+
try {
|
|
12
|
+
const AutocompletePrompt = require('inquirer-autocomplete-prompt');
|
|
13
|
+
inquirer.registerPrompt('autocomplete', AutocompletePrompt);
|
|
14
|
+
hasAutocompletePrompt = true;
|
|
15
|
+
} catch {
|
|
16
|
+
// Fallback: use 'list' if plugin not installed (no search, pageSize 10)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Re-prompt for credential ID/key when validation failed (e.g. not found on dataplane).
|
|
21
|
+
* Empty input means skip.
|
|
22
|
+
* @async
|
|
23
|
+
* @param {string} [previousError] - Error message from dataplane (e.g. "Credential not found")
|
|
24
|
+
* @returns {Promise<Object>} { credentialIdOrKey: string } or { skip: true } if user leaves empty
|
|
25
|
+
*/
|
|
26
|
+
async function promptForCredentialIdOrKeyRetry(previousError) {
|
|
27
|
+
const msg = previousError
|
|
28
|
+
? `Credential not found or invalid (${String(previousError).slice(0, 60)}). Enter ID/key or leave empty to skip:`
|
|
29
|
+
: 'Enter credential ID or key (or leave empty to skip):';
|
|
30
|
+
const { credentialIdOrKey } = await inquirer.prompt([
|
|
31
|
+
{ type: 'input', name: 'credentialIdOrKey', message: msg, default: '' }
|
|
32
|
+
]);
|
|
33
|
+
const trimmed = (credentialIdOrKey && credentialIdOrKey.trim()) || '';
|
|
34
|
+
return trimmed ? { credentialIdOrKey: trimmed } : { skip: true };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Prompt for known platform selection
|
|
39
|
+
* @async
|
|
40
|
+
* @param {Array<{key: string, displayName?: string}>} [platforms] - List of available platforms
|
|
41
|
+
* @returns {Promise<string>} Selected platform key
|
|
42
|
+
*/
|
|
43
|
+
async function promptForKnownPlatform(platforms = []) {
|
|
44
|
+
const defaultPlatforms = [
|
|
45
|
+
{ name: 'HubSpot', value: 'hubspot' },
|
|
46
|
+
{ name: 'Salesforce', value: 'salesforce' },
|
|
47
|
+
{ name: 'Zendesk', value: 'zendesk' },
|
|
48
|
+
{ name: 'Slack', value: 'slack' },
|
|
49
|
+
{ name: 'Microsoft 365', value: 'microsoft365' }
|
|
50
|
+
];
|
|
51
|
+
const choices = platforms.length > 0
|
|
52
|
+
? platforms.map(p => ({ name: p.displayName || p.key, value: p.key }))
|
|
53
|
+
: defaultPlatforms;
|
|
54
|
+
const { platform } = await inquirer.prompt([
|
|
55
|
+
{ type: 'list', name: 'platform', message: 'Select a platform:', choices, pageSize: 10 }
|
|
56
|
+
]);
|
|
57
|
+
return platform;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Format entity for display in list
|
|
62
|
+
* @param {Object} e - Entity { name, pathCount? }
|
|
63
|
+
* @returns {string} Display string
|
|
64
|
+
*/
|
|
65
|
+
function _formatEntityChoice(e) {
|
|
66
|
+
if (!e || typeof e.name !== 'string') return 'unknown';
|
|
67
|
+
return e.pathCount !== undefined && e.pathCount !== null ? `${e.name} (${e.pathCount} paths)` : e.name;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Prompt to select an entity from discover-entities list (searchable when plugin available)
|
|
72
|
+
* @async
|
|
73
|
+
* @param {Array<{name: string, pathCount?: number, schemaMatch?: boolean}>} entities - From discover-entities
|
|
74
|
+
* @returns {Promise<string>} Selected entity name
|
|
75
|
+
*/
|
|
76
|
+
async function promptForEntitySelection(entities = []) {
|
|
77
|
+
if (!Array.isArray(entities) || entities.length === 0) {
|
|
78
|
+
throw new Error('At least one entity is required');
|
|
79
|
+
}
|
|
80
|
+
const choices = entities.map(e => ({
|
|
81
|
+
name: _formatEntityChoice(e),
|
|
82
|
+
value: e.name
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
const promptConfig = {
|
|
86
|
+
type: hasAutocompletePrompt ? 'autocomplete' : 'list',
|
|
87
|
+
name: 'entityName',
|
|
88
|
+
message: 'Select entity for datasource generation:',
|
|
89
|
+
choices,
|
|
90
|
+
pageSize: 10
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (hasAutocompletePrompt) {
|
|
94
|
+
promptConfig.source = (answers, input) => {
|
|
95
|
+
const q = (input || '').toLowerCase();
|
|
96
|
+
const filtered = entities.filter(e => (e.name || '').toLowerCase().includes(q));
|
|
97
|
+
return Promise.resolve(
|
|
98
|
+
filtered.map(e => ({ name: _formatEntityChoice(e), value: e.name }))
|
|
99
|
+
);
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { entityName } = await inquirer.prompt([promptConfig]);
|
|
104
|
+
return entityName;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** @param {*} o - Value to stringify */
|
|
108
|
+
const _s = (o) => (o === null || o === undefined || o === '' ? 'ā' : String(o));
|
|
109
|
+
|
|
110
|
+
/** @param {Object} sys - systemSummary */
|
|
111
|
+
function _formatSystem(sys) {
|
|
112
|
+
return [
|
|
113
|
+
'\nSystem',
|
|
114
|
+
` Key: ${_s(sys.key)}`,
|
|
115
|
+
` Display name: ${_s(sys.displayName)}`,
|
|
116
|
+
` Type: ${_s(sys.type)}`,
|
|
117
|
+
` Base URL: ${_s(sys.baseUrl)}`,
|
|
118
|
+
` Auth: ${_s(sys.authenticationType)}`,
|
|
119
|
+
` Endpoints: ${_s(sys.endpointCount)}`
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** @param {Object} ds - datasourceSummary */
|
|
124
|
+
function _formatDatasource(ds) {
|
|
125
|
+
return [
|
|
126
|
+
'\nDatasource',
|
|
127
|
+
` Key: ${_s(ds.key)}`,
|
|
128
|
+
` Entity: ${_s(ds.entity)}`,
|
|
129
|
+
` Resource type: ${_s(ds.resourceType)}`,
|
|
130
|
+
` CIP steps: ${_s(ds.cipStepCount)}`,
|
|
131
|
+
` Field mappings: ${_s(ds.fieldMappingCount)}`,
|
|
132
|
+
` Exposed: ${_s(ds.exposedProfileCount)} profiles`
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** @param {Object} fm - fieldMappingsSummary */
|
|
137
|
+
function _formatFieldMappings(fm) {
|
|
138
|
+
const mapped = Array.isArray(fm.mappedFields) ? fm.mappedFields : [];
|
|
139
|
+
const unmapped = Array.isArray(fm.unmappedFields) ? fm.unmappedFields : [];
|
|
140
|
+
const mappedStr = mapped.length > 0
|
|
141
|
+
? `${mapped.length} (${mapped.slice(0, 5).join(', ')}${mapped.length > 5 ? ', ...' : ''})`
|
|
142
|
+
: _s(fm.mappingCount);
|
|
143
|
+
const unmappedStr = unmapped.length > 0
|
|
144
|
+
? `${unmapped.length} (${unmapped.slice(0, 3).join(', ')}${unmapped.length > 3 ? ', ...' : ''})`
|
|
145
|
+
: '0';
|
|
146
|
+
return ['\nField Mappings', ` Mapped: ${mappedStr}`, ` Unmapped: ${unmappedStr}`];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Derive a preview summary from systemConfig and datasourceConfigs when the dataplane
|
|
151
|
+
* preview API does not return summaries. Ensures a compact summary is always shown.
|
|
152
|
+
* @param {Object} systemConfig - System configuration
|
|
153
|
+
* @param {Object[]} datasourceConfigs - Array of datasource configurations
|
|
154
|
+
* @returns {Object} Preview object compatible with formatPreviewSummary
|
|
155
|
+
*/
|
|
156
|
+
function _buildDatasourceSummary(ds) {
|
|
157
|
+
let fieldMappingCount = 0;
|
|
158
|
+
const attrs = ds.fieldMappings?.attributes ?? ds.attributes ?? {};
|
|
159
|
+
if (typeof attrs === 'object' && !Array.isArray(attrs)) {
|
|
160
|
+
fieldMappingCount = Object.keys(attrs).length;
|
|
161
|
+
}
|
|
162
|
+
const exposedCount = ds.exposed?.attributes?.length ?? 0;
|
|
163
|
+
return {
|
|
164
|
+
key: ds.key,
|
|
165
|
+
entity: ds.entityType ?? ds.entity ?? ds.resourceType ?? ds.key?.split('-').pop(),
|
|
166
|
+
resourceType: ds.resourceType,
|
|
167
|
+
cipStepCount: null,
|
|
168
|
+
fieldMappingCount: fieldMappingCount || null,
|
|
169
|
+
exposedProfileCount: exposedCount || null
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function _buildSystemSummary(sys) {
|
|
174
|
+
const baseUrl = sys.openapi?.servers?.[0]?.url ?? sys.baseUrl ?? sys.openapi?.baseUrl ?? null;
|
|
175
|
+
const authType = sys.authentication?.type ?? sys.authenticationType ?? null;
|
|
176
|
+
const endpointCount = sys.openapi?.endpoints?.length ??
|
|
177
|
+
(sys.openapi?.operations ? Object.keys(sys.openapi.operations || {}).length : null);
|
|
178
|
+
return {
|
|
179
|
+
key: sys.key,
|
|
180
|
+
displayName: sys.displayName,
|
|
181
|
+
type: sys.type,
|
|
182
|
+
baseUrl,
|
|
183
|
+
authenticationType: authType,
|
|
184
|
+
endpointCount
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function _buildFieldMappingsSummary(ds0) {
|
|
189
|
+
const attrs0 = ds0.fieldMappings?.attributes ?? ds0.attributes ?? {};
|
|
190
|
+
const mappedFields = (typeof attrs0 === 'object' && !Array.isArray(attrs0)) ? Object.keys(attrs0) : [];
|
|
191
|
+
return mappedFields.length > 0 ? { mappingCount: mappedFields.length, mappedFields, unmappedFields: [] } : null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function derivePreviewFromConfig(systemConfig, datasourceConfigs) {
|
|
195
|
+
const sys = systemConfig || {};
|
|
196
|
+
const dsList = Array.isArray(datasourceConfigs) ? datasourceConfigs : (datasourceConfigs ? [datasourceConfigs] : []);
|
|
197
|
+
|
|
198
|
+
const result = {
|
|
199
|
+
systemSummary: _buildSystemSummary(sys),
|
|
200
|
+
fieldMappingsSummary: _buildFieldMappingsSummary(dsList[0] || {})
|
|
201
|
+
};
|
|
202
|
+
const datasourceSummaries = dsList.map(_buildDatasourceSummary);
|
|
203
|
+
if (datasourceSummaries.length === 1) {
|
|
204
|
+
result.datasourceSummary = datasourceSummaries[0];
|
|
205
|
+
} else if (datasourceSummaries.length > 1) {
|
|
206
|
+
result.datasourceSummaries = datasourceSummaries;
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Format a preview summary for display (WizardPreviewResponse from dataplane)
|
|
213
|
+
* @param {Object} preview - Preview data from GET /api/v1/wizard/preview/{sessionId}
|
|
214
|
+
* @returns {string} Formatted summary text
|
|
215
|
+
*/
|
|
216
|
+
function formatPreviewSummary(preview) {
|
|
217
|
+
const hasVal = (v) => v !== null && v !== undefined;
|
|
218
|
+
const parts = ['\nš Configuration Preview (what will be created)', 'ā'.repeat(60)];
|
|
219
|
+
|
|
220
|
+
if (preview.systemSummary) parts.push(..._formatSystem(preview.systemSummary));
|
|
221
|
+
if (preview.datasourceSummaries && preview.datasourceSummaries.length > 0) {
|
|
222
|
+
preview.datasourceSummaries.forEach((ds, i) => {
|
|
223
|
+
const lines = _formatDatasource(ds);
|
|
224
|
+
const label = preview.datasourceSummaries.length > 1 ? `Datasource ${i + 1}` : 'Datasource';
|
|
225
|
+
parts.push(...lines.map((line, j) => (j === 0 ? line.replace('Datasource', label) : line)));
|
|
226
|
+
});
|
|
227
|
+
} else if (preview.datasourceSummary) {
|
|
228
|
+
parts.push(..._formatDatasource(preview.datasourceSummary));
|
|
229
|
+
}
|
|
230
|
+
if (preview.cipPipelineSummary) {
|
|
231
|
+
const cip = preview.cipPipelineSummary;
|
|
232
|
+
parts.push('\nCIP Pipeline', ` Steps: ${_s(cip.stepCount)}`, ` Est. execution: ${_s(cip.estimatedExecutionTime) || 'ā'}`);
|
|
233
|
+
}
|
|
234
|
+
if (preview.fieldMappingsSummary) parts.push(..._formatFieldMappings(preview.fieldMappingsSummary));
|
|
235
|
+
if (hasVal(preview.estimatedRecords) || hasVal(preview.estimatedSyncTime)) {
|
|
236
|
+
parts.push('\nEstimates', ` Records: ${_s(preview.estimatedRecords)}`, ` Sync: ${_s(preview.estimatedSyncTime)}`);
|
|
237
|
+
}
|
|
238
|
+
parts.push('\n' + 'ā'.repeat(60));
|
|
239
|
+
return parts.join('\n');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Prompt for configuration review and editing
|
|
244
|
+
* When preview is provided, displays a compact summary; otherwise dumps full YAML.
|
|
245
|
+
* @async
|
|
246
|
+
* @param {Object} opts - Options
|
|
247
|
+
* @param {Object|null} [opts.preview] - Preview data from getPreview (null = fallback to YAML)
|
|
248
|
+
* @param {Object} opts.systemConfig - System configuration
|
|
249
|
+
* @param {Object[]} opts.datasourceConfigs - Array of datasource configurations
|
|
250
|
+
* @returns {Promise<Object>} Object with action ('accept'|'cancel') and optionally edited configs
|
|
251
|
+
*/
|
|
252
|
+
async function promptForConfigReview({ preview, systemConfig, datasourceConfigs }) {
|
|
253
|
+
const hasSummary = preview && (preview.systemSummary || preview.datasourceSummary || (preview.datasourceSummaries && preview.datasourceSummaries.length > 0));
|
|
254
|
+
const summaryToShow = hasSummary ? preview : derivePreviewFromConfig(systemConfig, datasourceConfigs);
|
|
255
|
+
const canShowSummary = summaryToShow.systemSummary || summaryToShow.datasourceSummary ||
|
|
256
|
+
(summaryToShow.datasourceSummaries && summaryToShow.datasourceSummaries.length > 0);
|
|
257
|
+
|
|
258
|
+
if (canShowSummary) {
|
|
259
|
+
// eslint-disable-next-line no-console
|
|
260
|
+
console.log(formatPreviewSummary(summaryToShow));
|
|
261
|
+
} else {
|
|
262
|
+
// eslint-disable-next-line no-console
|
|
263
|
+
console.log('\nš Generated Configuration:\nSystem Configuration:');
|
|
264
|
+
// eslint-disable-next-line no-console
|
|
265
|
+
console.log(yaml.dump(systemConfig, { lineWidth: -1 }));
|
|
266
|
+
// eslint-disable-next-line no-console
|
|
267
|
+
console.log('Datasource Configurations:');
|
|
268
|
+
(datasourceConfigs || []).forEach((ds, index) => {
|
|
269
|
+
// eslint-disable-next-line no-console
|
|
270
|
+
console.log(`\nDatasource ${index + 1}:\n${yaml.dump(ds, { lineWidth: -1 })}`);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
const { action } = await inquirer.prompt([
|
|
274
|
+
{
|
|
275
|
+
type: 'list',
|
|
276
|
+
name: 'action',
|
|
277
|
+
message: 'What would you like to do?',
|
|
278
|
+
choices: [
|
|
279
|
+
{ name: 'Accept and save', value: 'accept' },
|
|
280
|
+
{ name: 'Cancel', value: 'cancel' }
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
]);
|
|
284
|
+
return action === 'cancel' ? { action: 'cancel' } : { action: 'accept' };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = {
|
|
288
|
+
promptForCredentialIdOrKeyRetry,
|
|
289
|
+
promptForKnownPlatform,
|
|
290
|
+
promptForEntitySelection,
|
|
291
|
+
promptForConfigReview,
|
|
292
|
+
derivePreviewFromConfig,
|
|
293
|
+
formatPreviewSummary
|
|
294
|
+
};
|