@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
package/lib/commands/wizard.js
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* @author AI Fabrix Team
|
|
4
4
|
* @version 2.0.0
|
|
5
5
|
*/
|
|
6
|
+
/* eslint-disable max-lines */
|
|
6
7
|
|
|
7
8
|
const chalk = require('chalk');
|
|
8
9
|
const logger = require('../utils/logger');
|
|
9
10
|
const {
|
|
10
11
|
promptForMode,
|
|
11
|
-
|
|
12
|
+
promptForExistingSystem,
|
|
12
13
|
promptForSourceType,
|
|
13
14
|
promptForOpenApiFile,
|
|
14
15
|
promptForOpenApiUrl,
|
|
@@ -24,45 +25,30 @@ const {
|
|
|
24
25
|
const {
|
|
25
26
|
validateAndCheckAppDirectory,
|
|
26
27
|
formatDataplaneRejectedTokenMessage,
|
|
28
|
+
extractSessionId,
|
|
27
29
|
handleOpenApiParsing,
|
|
28
30
|
handleCredentialSelection,
|
|
29
31
|
handleTypeDetection,
|
|
32
|
+
handleEntitySelection,
|
|
30
33
|
handleConfigurationGeneration,
|
|
31
34
|
validateWizardConfiguration,
|
|
32
35
|
handleFileSaving,
|
|
33
|
-
setupDataplaneAndAuth
|
|
36
|
+
setupDataplaneAndAuth,
|
|
37
|
+
resolveCredentialConfig,
|
|
38
|
+
fetchSystemsListForAddDatasource,
|
|
39
|
+
resolveExternalSystemForAddDatasource
|
|
34
40
|
} = require('./wizard-core');
|
|
35
41
|
const { handleWizardHeadless } = require('./wizard-headless');
|
|
36
|
-
const { createWizardSession, updateWizardSession, getWizardPlatforms } = require('../api/wizard.api');
|
|
37
|
-
const { getExternalSystem } = require('../api/external-systems.api');
|
|
42
|
+
const { createWizardSession, updateWizardSession, getWizardPlatforms, getPreview } = require('../api/wizard.api');
|
|
38
43
|
const { writeWizardConfig, wizardConfigExists, validateWizardConfig } = require('../validation/wizard-config-validator');
|
|
39
44
|
const { appendWizardError } = require('../utils/cli-utils');
|
|
40
45
|
const {
|
|
41
46
|
buildPreferencesForSave,
|
|
42
47
|
buildWizardStateForSave,
|
|
43
48
|
showWizardConfigSummary,
|
|
44
|
-
ensureIntegrationDir
|
|
45
|
-
isExternalSystemForAddDatasource
|
|
49
|
+
ensureIntegrationDir
|
|
46
50
|
} = require('./wizard-helpers');
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Extract session ID from response data
|
|
50
|
-
* @function extractSessionId
|
|
51
|
-
* @param {Object} responseData - Response data from API
|
|
52
|
-
* @returns {string} Session ID
|
|
53
|
-
* @throws {Error} If session ID not found or invalid
|
|
54
|
-
*/
|
|
55
|
-
function extractSessionId(responseData) {
|
|
56
|
-
let sessionId = responseData?.data?.sessionId || responseData?.sessionId ||
|
|
57
|
-
responseData?.data?.session_id || responseData?.session_id;
|
|
58
|
-
if (sessionId && typeof sessionId === 'object') {
|
|
59
|
-
sessionId = sessionId.id || sessionId.sessionId || sessionId.session_id;
|
|
60
|
-
}
|
|
61
|
-
if (!sessionId || typeof sessionId !== 'string') {
|
|
62
|
-
throw new Error(`Session ID not found: ${JSON.stringify(responseData, null, 2)}`);
|
|
63
|
-
}
|
|
64
|
-
return sessionId;
|
|
65
|
-
}
|
|
51
|
+
const { humanizeAppKey } = require('../generator/wizard-prompts-secondary');
|
|
66
52
|
|
|
67
53
|
/**
|
|
68
54
|
* Create wizard session with given mode and optional systemIdOrKey (no prompts)
|
|
@@ -151,7 +137,8 @@ async function handleInteractiveConfigGeneration(options) {
|
|
|
151
137
|
fieldOnboardingLevel: preferences.fieldOnboardingLevel || 'full',
|
|
152
138
|
enableMCP: preferences.mcp,
|
|
153
139
|
enableABAC: preferences.abac,
|
|
154
|
-
enableRBAC: preferences.rbac
|
|
140
|
+
enableRBAC: preferences.rbac,
|
|
141
|
+
debug: options.debug === true
|
|
155
142
|
};
|
|
156
143
|
|
|
157
144
|
const result = await handleConfigurationGeneration(options.dataplaneUrl, options.authConfig, {
|
|
@@ -160,28 +147,52 @@ async function handleInteractiveConfigGeneration(options) {
|
|
|
160
147
|
detectedType: options.detectedType,
|
|
161
148
|
configPrefs,
|
|
162
149
|
credentialIdOrKey: options.credentialIdOrKey,
|
|
163
|
-
systemIdOrKey: options.systemIdOrKey
|
|
150
|
+
systemIdOrKey: options.systemIdOrKey,
|
|
151
|
+
sourceType: options.sourceType,
|
|
152
|
+
platformKey: options.sourceType === 'known-platform' ? options.sourceData : undefined,
|
|
153
|
+
entityName: options.entityName,
|
|
154
|
+
appName: options.appName,
|
|
155
|
+
systemDisplayName: options.systemDisplayName
|
|
164
156
|
});
|
|
165
157
|
|
|
166
158
|
return {
|
|
167
159
|
...result,
|
|
168
|
-
preferences: buildPreferencesForSave(userIntent, preferences)
|
|
160
|
+
preferences: buildPreferencesForSave(userIntent, preferences, { debug: options.debug })
|
|
169
161
|
};
|
|
170
162
|
}
|
|
171
163
|
|
|
172
164
|
/**
|
|
173
165
|
* Handle configuration review and validation step (interactive mode only)
|
|
166
|
+
* Fetches preview summary from dataplane; falls back to YAML dump if preview unavailable.
|
|
174
167
|
* @async
|
|
175
168
|
* @function handleConfigurationReview
|
|
176
169
|
* @param {string} dataplaneUrl - Dataplane URL
|
|
177
170
|
* @param {Object} authConfig - Authentication configuration
|
|
171
|
+
* @param {string} sessionId - Wizard session ID
|
|
178
172
|
* @param {Object} systemConfig - System configuration
|
|
179
173
|
* @param {Object[]} datasourceConfigs - Datasource configurations
|
|
180
|
-
* @
|
|
174
|
+
* @param {Object} [opts] - Optional options
|
|
175
|
+
* @param {string} [opts.appKey] - App key for debug manifest path
|
|
176
|
+
* @param {boolean} [opts.debug] - When true, save debug manifest on validation failure
|
|
177
|
+
* @returns {Promise<Object|null>} Final configurations or null if cancelled
|
|
181
178
|
*/
|
|
182
|
-
async function handleConfigurationReview(dataplaneUrl, authConfig, systemConfig, datasourceConfigs) {
|
|
179
|
+
async function handleConfigurationReview(dataplaneUrl, authConfig, sessionId, systemConfig, datasourceConfigs, opts = {}) {
|
|
183
180
|
logger.log(chalk.blue('\n\uD83D\uDCCB Step 6-7: Review & Validate'));
|
|
184
|
-
|
|
181
|
+
|
|
182
|
+
let preview = null;
|
|
183
|
+
try {
|
|
184
|
+
const previewResponse = await getPreview(dataplaneUrl, sessionId, authConfig);
|
|
185
|
+
if (previewResponse?.success && previewResponse?.data) {
|
|
186
|
+
preview = previewResponse.data;
|
|
187
|
+
}
|
|
188
|
+
} catch {
|
|
189
|
+
// Fall back to YAML display
|
|
190
|
+
}
|
|
191
|
+
if (!preview) {
|
|
192
|
+
logger.warn('Preview unavailable, showing full configuration.');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const reviewResult = await promptForConfigReview({ preview, systemConfig, datasourceConfigs, appKey: opts.appKey });
|
|
185
196
|
|
|
186
197
|
if (reviewResult.action === 'cancel') {
|
|
187
198
|
logger.log(chalk.yellow('Wizard cancelled.'));
|
|
@@ -191,7 +202,10 @@ async function handleConfigurationReview(dataplaneUrl, authConfig, systemConfig,
|
|
|
191
202
|
const finalSystemConfig = reviewResult.systemConfig || systemConfig;
|
|
192
203
|
const finalDatasourceConfigs = reviewResult.datasourceConfigs || datasourceConfigs;
|
|
193
204
|
|
|
194
|
-
await validateWizardConfiguration(dataplaneUrl, authConfig, finalSystemConfig, finalDatasourceConfigs
|
|
205
|
+
await validateWizardConfiguration(dataplaneUrl, authConfig, finalSystemConfig, finalDatasourceConfigs, {
|
|
206
|
+
debug: opts.debug,
|
|
207
|
+
appName: opts.appKey
|
|
208
|
+
});
|
|
195
209
|
|
|
196
210
|
return { systemConfig: finalSystemConfig, datasourceConfigs: finalDatasourceConfigs };
|
|
197
211
|
}
|
|
@@ -208,7 +222,7 @@ async function handleConfigurationReview(dataplaneUrl, authConfig, systemConfig,
|
|
|
208
222
|
* @returns {Promise<Object>} Collected state (source, credential, preferences) for wizard.yaml save
|
|
209
223
|
*/
|
|
210
224
|
async function doWizardSteps(appKey, dataplaneUrl, authConfig, sessionId, flowOpts, state) {
|
|
211
|
-
const { mode, systemIdOrKey, platforms } = flowOpts;
|
|
225
|
+
const { mode, systemIdOrKey, platforms, debug } = flowOpts;
|
|
212
226
|
const { sourceType, sourceData } = await handleInteractiveSourceSelection(
|
|
213
227
|
dataplaneUrl, sessionId, authConfig, platforms
|
|
214
228
|
);
|
|
@@ -220,13 +234,13 @@ async function doWizardSteps(appKey, dataplaneUrl, authConfig, sessionId, flowOp
|
|
|
220
234
|
|
|
221
235
|
const openapiSpec = await handleOpenApiParsing(dataplaneUrl, authConfig, sourceType, sourceData);
|
|
222
236
|
const credentialAction = await promptForCredentialAction();
|
|
223
|
-
const configCredential =
|
|
224
|
-
? { action: 'skip' }
|
|
225
|
-
: { action: credentialAction.action, credentialIdOrKey: credentialAction.credentialIdOrKey };
|
|
237
|
+
const configCredential = await resolveCredentialConfig(dataplaneUrl, authConfig, credentialAction);
|
|
226
238
|
state.credential = configCredential;
|
|
227
239
|
const credentialIdOrKey = await handleCredentialSelection(dataplaneUrl, authConfig, configCredential);
|
|
228
240
|
|
|
229
241
|
const detectedType = await handleTypeDetection(dataplaneUrl, authConfig, openapiSpec);
|
|
242
|
+
const entityName = openapiSpec && sourceType !== 'known-platform'
|
|
243
|
+
? await handleEntitySelection(dataplaneUrl, authConfig, openapiSpec) : null;
|
|
230
244
|
const genResult = await handleInteractiveConfigGeneration({
|
|
231
245
|
dataplaneUrl,
|
|
232
246
|
authConfig,
|
|
@@ -234,12 +248,25 @@ async function doWizardSteps(appKey, dataplaneUrl, authConfig, sessionId, flowOp
|
|
|
234
248
|
openapiSpec,
|
|
235
249
|
detectedType,
|
|
236
250
|
credentialIdOrKey,
|
|
237
|
-
systemIdOrKey
|
|
251
|
+
systemIdOrKey,
|
|
252
|
+
sourceType,
|
|
253
|
+
sourceData,
|
|
254
|
+
entityName: entityName || undefined,
|
|
255
|
+
appName: appKey,
|
|
256
|
+
debug,
|
|
257
|
+
systemDisplayName: flowOpts.systemDisplayName
|
|
238
258
|
});
|
|
239
259
|
const { systemConfig, datasourceConfigs, systemKey, preferences: savedPrefs } = genResult;
|
|
240
260
|
state.preferences = savedPrefs || {};
|
|
241
261
|
|
|
242
|
-
const finalConfigs = await handleConfigurationReview(
|
|
262
|
+
const finalConfigs = await handleConfigurationReview(
|
|
263
|
+
dataplaneUrl,
|
|
264
|
+
authConfig,
|
|
265
|
+
sessionId,
|
|
266
|
+
systemConfig,
|
|
267
|
+
datasourceConfigs,
|
|
268
|
+
{ appKey, debug }
|
|
269
|
+
);
|
|
243
270
|
if (!finalConfigs) return null;
|
|
244
271
|
|
|
245
272
|
await handleFileSaving(
|
|
@@ -290,18 +317,23 @@ async function runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sess
|
|
|
290
317
|
* @returns {Promise<void>} Resolves when wizard flow completes
|
|
291
318
|
*/
|
|
292
319
|
async function executeWizardFlow(appKey, dataplaneUrl, authConfig, flowOpts = {}) {
|
|
293
|
-
const { mode, systemIdOrKey, configPath } = flowOpts;
|
|
320
|
+
const { mode, systemIdOrKey, configPath, debug, systemDisplayName } = flowOpts;
|
|
294
321
|
|
|
322
|
+
if (debug) {
|
|
323
|
+
logger.log(chalk.gray(`[DEBUG] Wizard debug mode enabled for app: ${appKey}`));
|
|
324
|
+
}
|
|
295
325
|
logger.log(chalk.blue('\n\uD83D\uDCCB Step 1: Create Session'));
|
|
296
326
|
const sessionId = await createSessionFromParams(dataplaneUrl, authConfig, mode, systemIdOrKey, appKey);
|
|
297
327
|
logger.log(chalk.green('\u2713 Session created'));
|
|
298
328
|
|
|
299
|
-
const platforms = await getWizardPlatforms(dataplaneUrl, authConfig);
|
|
329
|
+
const platforms = mode === 'add-datasource' ? [] : await getWizardPlatforms(dataplaneUrl, authConfig);
|
|
300
330
|
const state = await runWizardStepsAfterSession(appKey, dataplaneUrl, authConfig, sessionId, {
|
|
301
331
|
mode,
|
|
302
332
|
systemIdOrKey,
|
|
303
333
|
platforms,
|
|
304
|
-
configPath
|
|
334
|
+
configPath,
|
|
335
|
+
debug: flowOpts.debug,
|
|
336
|
+
systemDisplayName
|
|
305
337
|
});
|
|
306
338
|
if (!state) return;
|
|
307
339
|
|
|
@@ -362,26 +394,15 @@ async function resolveCreateNewPath(options, loadedConfig) {
|
|
|
362
394
|
* @returns {Promise<Object>} { appKey, configPath, dataplaneUrl, authConfig, systemIdOrKey }
|
|
363
395
|
*/
|
|
364
396
|
async function resolveAddDatasourcePath(options, loadedConfig) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
logger.log(chalk.red('Cannot add datasource to a webapp. Please enter an external system ID or key.'));
|
|
375
|
-
systemIdOrKey = await promptForSystemIdOrKey(systemIdOrKey);
|
|
376
|
-
continue;
|
|
377
|
-
}
|
|
378
|
-
break;
|
|
379
|
-
}
|
|
380
|
-
} catch (err) {
|
|
381
|
-
logger.log(chalk.red(`System not found or error: ${err.message}`));
|
|
382
|
-
}
|
|
383
|
-
systemIdOrKey = await promptForSystemIdOrKey(systemIdOrKey);
|
|
384
|
-
}
|
|
397
|
+
const { dataplaneUrl, authConfig } = await setupDataplaneAndAuth(
|
|
398
|
+
options,
|
|
399
|
+
loadedConfig?.systemIdOrKey || loadedConfig?.appKey || 'wizard'
|
|
400
|
+
);
|
|
401
|
+
const systemsList = await fetchSystemsListForAddDatasource(dataplaneUrl, authConfig);
|
|
402
|
+
const initialSystemIdOrKey = loadedConfig?.systemIdOrKey || (await promptForExistingSystem(systemsList, loadedConfig?.systemIdOrKey));
|
|
403
|
+
const { systemResponse, systemIdOrKey } = await resolveExternalSystemForAddDatasource(
|
|
404
|
+
dataplaneUrl, authConfig, systemsList, initialSystemIdOrKey
|
|
405
|
+
);
|
|
385
406
|
const sys = systemResponse?.data || systemResponse;
|
|
386
407
|
const appKey = sys?.key || sys?.systemKey || systemIdOrKey;
|
|
387
408
|
const configPath = await ensureIntegrationDir(appKey);
|
|
@@ -449,15 +470,24 @@ async function handleWizardWithSavedConfig(options, loadedConfig, displayPath) {
|
|
|
449
470
|
}
|
|
450
471
|
|
|
451
472
|
async function handleWizardInteractive(options) {
|
|
452
|
-
const
|
|
473
|
+
const allowAddDatasource = !options.app;
|
|
474
|
+
const mode = allowAddDatasource ? await promptForMode(undefined, true) : 'create-system';
|
|
453
475
|
const resolved = mode === 'create-system'
|
|
454
476
|
? await resolveCreateNewPath(options, null)
|
|
455
477
|
: await resolveAddDatasourcePath(options, null);
|
|
456
478
|
if (!resolved) return;
|
|
457
479
|
const { appKey, configPath, dataplaneUrl, authConfig } = resolved;
|
|
458
480
|
const systemIdOrKey = mode === 'add-datasource' ? resolved.systemIdOrKey : undefined;
|
|
481
|
+
const systemDisplayName = options.systemDisplayName || options.displayName ||
|
|
482
|
+
(mode === 'create-system' ? humanizeAppKey(appKey) : undefined);
|
|
459
483
|
try {
|
|
460
|
-
await executeWizardFlow(appKey, dataplaneUrl, authConfig, {
|
|
484
|
+
await executeWizardFlow(appKey, dataplaneUrl, authConfig, {
|
|
485
|
+
mode,
|
|
486
|
+
systemIdOrKey,
|
|
487
|
+
configPath,
|
|
488
|
+
debug: options.debug,
|
|
489
|
+
systemDisplayName
|
|
490
|
+
});
|
|
461
491
|
logger.log(chalk.gray(`To change settings, edit integration/${appKey}/wizard.yaml and run: aifabrix wizard ${appKey}`));
|
|
462
492
|
} catch (error) {
|
|
463
493
|
await handleWizardError(appKey, configPath, mode, systemIdOrKey, error);
|
|
@@ -480,5 +510,4 @@ async function handleWizard(options = {}) {
|
|
|
480
510
|
}
|
|
481
511
|
return await handleWizardInteractive(options);
|
|
482
512
|
}
|
|
483
|
-
|
|
484
513
|
module.exports = { handleWizard, handleWizardHeadless };
|
package/lib/core/config.js
CHANGED
|
@@ -126,7 +126,8 @@ function getDefaultConfig() {
|
|
|
126
126
|
environment: 'dev',
|
|
127
127
|
controller: undefined,
|
|
128
128
|
environments: {},
|
|
129
|
-
device: {}
|
|
129
|
+
device: {},
|
|
130
|
+
format: undefined
|
|
130
131
|
};
|
|
131
132
|
}
|
|
132
133
|
|
|
@@ -489,4 +490,9 @@ const { createPathConfigFunctions } = require('../utils/config-paths');
|
|
|
489
490
|
const pathConfigFunctions = createPathConfigFunctions(getConfig, saveConfig);
|
|
490
491
|
Object.assign(exportsObj, pathConfigFunctions);
|
|
491
492
|
|
|
493
|
+
// Format preference functions
|
|
494
|
+
const { createFormatFunctions } = require('../utils/config-format-preference');
|
|
495
|
+
const formatFunctions = createFormatFunctions(getConfig, saveConfig);
|
|
496
|
+
Object.assign(exportsObj, formatFunctions);
|
|
497
|
+
|
|
492
498
|
module.exports = exportsObj;
|
package/lib/core/secrets.js
CHANGED
|
@@ -206,6 +206,38 @@ async function loadMergedConfigAndUserSecrets() {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Loads merged secrets using config/user cascade, builder file merge, and default fallback.
|
|
211
|
+
* @async
|
|
212
|
+
* @returns {Promise<Object>} Merged secrets object (not decrypted)
|
|
213
|
+
*/
|
|
214
|
+
async function loadSecretsWithFallbacks() {
|
|
215
|
+
let merged = await loadMergedConfigAndUserSecrets();
|
|
216
|
+
if (!merged || Object.keys(merged).length === 0) {
|
|
217
|
+
merged = loadPrimaryUserSecrets();
|
|
218
|
+
if (Object.keys(merged).length === 0) {
|
|
219
|
+
merged = loadUserSecrets();
|
|
220
|
+
}
|
|
221
|
+
merged = await applyCanonicalSecretsOverride(merged);
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const projectRoot = pathsUtil.getProjectRoot();
|
|
225
|
+
if (projectRoot) {
|
|
226
|
+
const builderPath = path.join(projectRoot, 'builder', 'secrets.local.yaml');
|
|
227
|
+
if (fs.existsSync(builderPath)) {
|
|
228
|
+
const builderSecrets = mergeUserWithConfigFile(merged || {}, builderPath);
|
|
229
|
+
if (builderSecrets) merged = builderSecrets;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
233
|
+
// Ignore (e.g. no project root or read error)
|
|
234
|
+
}
|
|
235
|
+
if (Object.keys(merged).length === 0) {
|
|
236
|
+
merged = loadDefaultSecrets();
|
|
237
|
+
}
|
|
238
|
+
return merged;
|
|
239
|
+
}
|
|
240
|
+
|
|
209
241
|
async function loadSecrets(secretsPath, _appName) {
|
|
210
242
|
if (secretsPath) {
|
|
211
243
|
const resolvedPath = resolveSecretsPath(secretsPath);
|
|
@@ -218,18 +250,7 @@ async function loadSecrets(secretsPath, _appName) {
|
|
|
218
250
|
}
|
|
219
251
|
return await decryptSecretsObject(explicitSecrets);
|
|
220
252
|
}
|
|
221
|
-
|
|
222
|
-
let mergedSecrets = await loadMergedConfigAndUserSecrets();
|
|
223
|
-
if (!mergedSecrets || Object.keys(mergedSecrets).length === 0) {
|
|
224
|
-
mergedSecrets = loadPrimaryUserSecrets();
|
|
225
|
-
if (Object.keys(mergedSecrets).length === 0) {
|
|
226
|
-
mergedSecrets = loadUserSecrets();
|
|
227
|
-
}
|
|
228
|
-
mergedSecrets = await applyCanonicalSecretsOverride(mergedSecrets);
|
|
229
|
-
}
|
|
230
|
-
if (Object.keys(mergedSecrets).length === 0) {
|
|
231
|
-
mergedSecrets = loadDefaultSecrets();
|
|
232
|
-
}
|
|
253
|
+
const mergedSecrets = await loadSecretsWithFallbacks();
|
|
233
254
|
ensureNonEmptySecrets(mergedSecrets);
|
|
234
255
|
return await decryptSecretsObject(mergedSecrets);
|
|
235
256
|
}
|
package/lib/datasource/deploy.js
CHANGED
|
@@ -16,6 +16,11 @@ const { getEnvironmentApplication } = require('../api/environments.api');
|
|
|
16
16
|
const { publishDatasourceViaPipeline } = require('../api/pipeline.api');
|
|
17
17
|
const { formatApiError } = require('../utils/api-error-handler');
|
|
18
18
|
const logger = require('../utils/logger');
|
|
19
|
+
const { logDataplanePipelineWarning } = require('../utils/dataplane-pipeline-warning');
|
|
20
|
+
const {
|
|
21
|
+
buildResolvedEnvMapForIntegration,
|
|
22
|
+
resolveConfigurationValues
|
|
23
|
+
} = require('../utils/configuration-env-resolver');
|
|
19
24
|
const { validateDatasourceFile } = require('./validate');
|
|
20
25
|
|
|
21
26
|
/**
|
|
@@ -50,7 +55,7 @@ async function getDataplaneUrl(controllerUrl, appKey, environment, authConfig) {
|
|
|
50
55
|
if (!dataplaneUrl) {
|
|
51
56
|
const appType = application.configuration?.type || application.type;
|
|
52
57
|
if (appType === 'external') {
|
|
53
|
-
throw new Error('Dataplane URL not found for external system
|
|
58
|
+
throw new Error('Dataplane URL not found for external system in application configuration.');
|
|
54
59
|
}
|
|
55
60
|
throw new Error('Dataplane URL not found in application configuration');
|
|
56
61
|
}
|
|
@@ -107,8 +112,6 @@ async function validateAndLoadDatasourceFile(filePath) {
|
|
|
107
112
|
* @param {string} controllerUrl - Controller URL
|
|
108
113
|
* @param {string} environment - Environment key
|
|
109
114
|
* @param {string} appKey - Application key
|
|
110
|
-
* @param {Object} [options] - Options
|
|
111
|
-
* @param {string} [options.dataplane] - Dataplane URL override
|
|
112
115
|
* @returns {Promise<Object>} Object with authConfig and dataplaneUrl
|
|
113
116
|
*/
|
|
114
117
|
async function setupDeploymentAuth(controllerUrl, environment, appKey) {
|
|
@@ -154,6 +157,7 @@ async function setupDeploymentAuth(controllerUrl, environment, appKey) {
|
|
|
154
157
|
async function publishDatasourceToDataplane(dataplaneUrl, systemKey, authConfig, datasourceConfig) {
|
|
155
158
|
requireBearerForDataplanePipeline(authConfig);
|
|
156
159
|
logger.log(chalk.blue('\n🚀 Publishing datasource to dataplane...'));
|
|
160
|
+
logDataplanePipelineWarning();
|
|
157
161
|
|
|
158
162
|
const publishResponse = await publishDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasourceConfig);
|
|
159
163
|
|
|
@@ -228,6 +232,11 @@ async function deployDatasource(appKey, filePath, _options) {
|
|
|
228
232
|
throw new Error('systemKey is required in datasource configuration');
|
|
229
233
|
}
|
|
230
234
|
|
|
235
|
+
if (Array.isArray(datasourceConfig.configuration) && datasourceConfig.configuration.length > 0) {
|
|
236
|
+
const { envMap, secrets } = await buildResolvedEnvMapForIntegration(systemKey);
|
|
237
|
+
resolveConfigurationValues(datasourceConfig.configuration, envMap, secrets, systemKey);
|
|
238
|
+
}
|
|
239
|
+
|
|
231
240
|
// Setup authentication and get dataplane URL
|
|
232
241
|
const { authConfig, dataplaneUrl } = await setupDeploymentAuth(controllerUrl, environment, appKey);
|
|
233
242
|
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Datasource E2E test - run full E2E test via dataplane external API
|
|
3
|
+
* @fileoverview Datasource E2E test logic (config, credential, sync, data, CIP)
|
|
4
|
+
* @author AI Fabrix Team
|
|
5
|
+
* @version 2.0.0
|
|
6
|
+
*/
|
|
7
|
+
/* eslint-disable max-statements -- Auth setup, API call, polling, debug log */
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const fs = require('fs').promises;
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
const { getIntegrationPath, resolveIntegrationAppKeyFromCwd } = require('../utils/paths');
|
|
14
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
15
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
16
|
+
const { getDeviceOnlyAuth } = require('../utils/token-manager');
|
|
17
|
+
const { testDatasourceE2E, getE2ETestRun } = require('../api/external-test.api');
|
|
18
|
+
const { writeTestLog } = require('../utils/test-log-writer');
|
|
19
|
+
|
|
20
|
+
const DEFAULT_POLL_INTERVAL_MS = 2500;
|
|
21
|
+
const DEFAULT_POLL_TIMEOUT_MS = 15 * 60 * 1000;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve appKey for datasource test-e2e
|
|
25
|
+
* @param {string} [appKey] - Explicit app key from --app
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
function resolveAppKey(appKey) {
|
|
29
|
+
if (appKey) return appKey;
|
|
30
|
+
const fromCwd = resolveIntegrationAppKeyFromCwd();
|
|
31
|
+
if (fromCwd) return fromCwd;
|
|
32
|
+
throw new Error(
|
|
33
|
+
'Could not determine app context. Use --app <appKey> or run from integration/<appKey>/ directory.'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Resolve primaryKeyValue for request body: string as-is, or read and parse JSON from @path
|
|
39
|
+
* @param {string} [value] - Literal value or path prefixed with @ (e.g. @pk.json)
|
|
40
|
+
* @returns {Promise<string|Object|null>} Resolved value for body.primaryKeyValue, or null if absent
|
|
41
|
+
*/
|
|
42
|
+
async function resolvePrimaryKeyValue(value) {
|
|
43
|
+
if (value === null || value === undefined || value === '') return null;
|
|
44
|
+
const str = String(value).trim();
|
|
45
|
+
if (str.startsWith('@')) {
|
|
46
|
+
const filePath = path.resolve(str.slice(1).trim());
|
|
47
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
48
|
+
return JSON.parse(content);
|
|
49
|
+
}
|
|
50
|
+
return str;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build E2E request body from options
|
|
55
|
+
* @param {Object} options - Command options
|
|
56
|
+
* @returns {Promise<Object>} Request body
|
|
57
|
+
*/
|
|
58
|
+
async function buildE2EBody(options) {
|
|
59
|
+
const body = {};
|
|
60
|
+
if (options.debug) body.includeDebug = true;
|
|
61
|
+
if (options.testCrud === true) body.testCrud = true;
|
|
62
|
+
if (options.recordId !== undefined && options.recordId !== null && options.recordId !== '') body.recordId = String(options.recordId);
|
|
63
|
+
if (options.cleanup === false) body.cleanup = false;
|
|
64
|
+
else if (options.cleanup === true) body.cleanup = true;
|
|
65
|
+
const pk = await resolvePrimaryKeyValue(options.primaryKeyValue);
|
|
66
|
+
if (pk !== null && pk !== undefined) body.primaryKeyValue = pk;
|
|
67
|
+
return body;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Poll E2E test run until completed or failed
|
|
72
|
+
* @param {string} dataplaneUrl - Dataplane URL
|
|
73
|
+
* @param {string} sourceIdOrKey - Source ID or key
|
|
74
|
+
* @param {string} testRunId - Test run ID
|
|
75
|
+
* @param {Object} authConfig - Auth config
|
|
76
|
+
* @param {Object} opts - Poll options
|
|
77
|
+
* @param {number} [opts.intervalMs] - Poll interval (ms)
|
|
78
|
+
* @param {number} [opts.timeoutMs] - Max wait (ms)
|
|
79
|
+
* @param {boolean} [opts.verbose] - Log each poll
|
|
80
|
+
* @returns {Promise<Object>} Final poll result (status completed or failed)
|
|
81
|
+
*/
|
|
82
|
+
async function pollE2ETestRun(dataplaneUrl, sourceIdOrKey, testRunId, authConfig, opts = {}) {
|
|
83
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
84
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
|
|
85
|
+
const verbose = opts.verbose === true;
|
|
86
|
+
const deadline = Date.now() + timeoutMs;
|
|
87
|
+
let last;
|
|
88
|
+
while (Date.now() < deadline) {
|
|
89
|
+
last = await getE2ETestRun(dataplaneUrl, sourceIdOrKey, testRunId, authConfig);
|
|
90
|
+
if (last.status === 'completed' || last.status === 'failed') {
|
|
91
|
+
return last;
|
|
92
|
+
}
|
|
93
|
+
if (verbose) {
|
|
94
|
+
const steps = last.completedActions || [];
|
|
95
|
+
logger.log(chalk.gray(` Polling… status: ${last.status}, ${steps.length} step(s) completed`));
|
|
96
|
+
}
|
|
97
|
+
await new Promise(r => setTimeout(r, intervalMs));
|
|
98
|
+
}
|
|
99
|
+
throw new Error(
|
|
100
|
+
`E2E test run did not complete within ${timeoutMs / 1000}s (run ID: ${testRunId})`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Run E2E test for one datasource (Bearer token or API key required; no client credentials).
|
|
106
|
+
* Default: async start + polling until completed/failed. Use options.async === false for sync.
|
|
107
|
+
*
|
|
108
|
+
* @async
|
|
109
|
+
* @param {string} datasourceKey - Datasource key (used as sourceIdOrKey)
|
|
110
|
+
* @param {Object} options - Options
|
|
111
|
+
* @param {string} [options.app] - App key (or resolve from cwd)
|
|
112
|
+
* @param {string} [options.environment] - Environment (dev, tst, pro)
|
|
113
|
+
* @param {boolean} [options.debug] - Include debug, write log file
|
|
114
|
+
* @param {boolean} [options.verbose] - Verbose output (e.g. poll progress)
|
|
115
|
+
* @param {boolean} [options.async] - If false, use sync mode (no polling). Default true.
|
|
116
|
+
* @param {boolean} [options.testCrud] - Set body testCrud true
|
|
117
|
+
* @param {string} [options.recordId] - Set body recordId
|
|
118
|
+
* @param {boolean} [options.cleanup] - Set body cleanup (default true)
|
|
119
|
+
* @param {string} [options.primaryKeyValue] - Set body primaryKeyValue (string or @path to JSON)
|
|
120
|
+
* @param {number} [options.pollIntervalMs] - Poll interval in ms (default 2500)
|
|
121
|
+
* @param {number} [options.pollTimeoutMs] - Poll timeout in ms (default 15 min)
|
|
122
|
+
* @returns {Promise<Object>} E2E test result (steps, success, error, etc.)
|
|
123
|
+
*/
|
|
124
|
+
async function runDatasourceTestE2E(datasourceKey, options = {}) {
|
|
125
|
+
if (!datasourceKey || typeof datasourceKey !== 'string') {
|
|
126
|
+
throw new Error('Datasource key is required');
|
|
127
|
+
}
|
|
128
|
+
const appKey = resolveAppKey(options.app);
|
|
129
|
+
const controllerUrl = await resolveControllerUrl();
|
|
130
|
+
const { resolveEnvironment } = require('../core/config');
|
|
131
|
+
const environment = options.environment || await resolveEnvironment();
|
|
132
|
+
const authConfig = await getDeviceOnlyAuth(controllerUrl);
|
|
133
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
134
|
+
|
|
135
|
+
logger.log(chalk.blue(`\n🧪 Running E2E test for datasource: ${datasourceKey}`));
|
|
136
|
+
|
|
137
|
+
const body = await buildE2EBody(options);
|
|
138
|
+
const useAsync = options.async !== false;
|
|
139
|
+
const requestMeta = {
|
|
140
|
+
sourceIdOrKey: datasourceKey,
|
|
141
|
+
includeDebug: options.debug,
|
|
142
|
+
testCrud: options.testCrud,
|
|
143
|
+
recordId: options.recordId,
|
|
144
|
+
cleanup: options.cleanup,
|
|
145
|
+
primaryKeyValue: options.primaryKeyValue !== undefined && options.primaryKeyValue !== null
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const execOpts = {
|
|
149
|
+
dataplaneUrl,
|
|
150
|
+
datasourceKey,
|
|
151
|
+
authConfig,
|
|
152
|
+
body,
|
|
153
|
+
useAsync,
|
|
154
|
+
verbose: options.verbose,
|
|
155
|
+
pollIntervalMs: options.pollIntervalMs,
|
|
156
|
+
pollTimeoutMs: options.pollTimeoutMs
|
|
157
|
+
};
|
|
158
|
+
let data;
|
|
159
|
+
try {
|
|
160
|
+
data = await executeE2EWithOptionalPoll(execOpts);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (options.debug) {
|
|
163
|
+
const appPath = getIntegrationPath(appKey);
|
|
164
|
+
const integrationDir = path.dirname(appPath);
|
|
165
|
+
await writeTestLog(appKey, { request: requestMeta, error: error.message }, 'test-e2e', integrationDir);
|
|
166
|
+
}
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (options.debug) {
|
|
171
|
+
const appPath = getIntegrationPath(appKey);
|
|
172
|
+
const integrationDir = path.dirname(appPath);
|
|
173
|
+
const logPath = await writeTestLog(appKey, { request: requestMeta, response: data }, 'test-e2e', integrationDir);
|
|
174
|
+
logger.log(chalk.gray(` Debug log: ${logPath}`));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return data;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Call E2E API and optionally poll until completed. On throw, caller should log if debug.
|
|
182
|
+
* @param {Object} opts - Options
|
|
183
|
+
* @param {string} opts.dataplaneUrl - Dataplane URL
|
|
184
|
+
* @param {string} opts.datasourceKey - Source key
|
|
185
|
+
* @param {Object} opts.authConfig - Auth config
|
|
186
|
+
* @param {Object} opts.body - Request body
|
|
187
|
+
* @param {boolean} opts.useAsync - Whether to use async + poll
|
|
188
|
+
* @param {boolean} opts.verbose - Verbose poll progress
|
|
189
|
+
* @param {number} [opts.pollIntervalMs] - Override poll interval (ms)
|
|
190
|
+
* @param {number} [opts.pollTimeoutMs] - Override poll timeout (ms)
|
|
191
|
+
* @returns {Promise<Object>} Final result data
|
|
192
|
+
*/
|
|
193
|
+
/* eslint-disable-next-line max-params -- single opts object; destructuring in body */
|
|
194
|
+
async function executeE2EWithOptionalPoll(opts) {
|
|
195
|
+
const { dataplaneUrl, datasourceKey, authConfig, body, useAsync, verbose, pollIntervalMs, pollTimeoutMs } = opts;
|
|
196
|
+
const response = await testDatasourceE2E(dataplaneUrl, datasourceKey, authConfig, body, {
|
|
197
|
+
asyncRun: useAsync
|
|
198
|
+
});
|
|
199
|
+
let data = response.data || response;
|
|
200
|
+
if (useAsync && data !== null && data !== undefined && data.testRunId) {
|
|
201
|
+
data = await pollE2ETestRun(
|
|
202
|
+
dataplaneUrl,
|
|
203
|
+
datasourceKey,
|
|
204
|
+
data.testRunId,
|
|
205
|
+
authConfig,
|
|
206
|
+
{
|
|
207
|
+
intervalMs: pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
208
|
+
timeoutMs: pollTimeoutMs ?? DEFAULT_POLL_TIMEOUT_MS,
|
|
209
|
+
verbose
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return data;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = {
|
|
217
|
+
runDatasourceTestE2E,
|
|
218
|
+
resolveAppKey
|
|
219
|
+
};
|