@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
|
@@ -121,5 +121,42 @@
|
|
|
121
121
|
* @property {string} data.environment - Environment key
|
|
122
122
|
*/
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Dataplane pipeline upload request body (single upload → validate → publish).
|
|
126
|
+
* @typedef {Object} PipelineUploadRequest
|
|
127
|
+
* @property {string} version - Config version (e.g. "1.0.0")
|
|
128
|
+
* @property {Object} application - Application/system config (external-system schema)
|
|
129
|
+
* @property {Object[]} dataSources - Data source configs
|
|
130
|
+
* @property {string} [status="draft"] - "draft" or "published"; Builder uses "draft"
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Dataplane pipeline upload response (publication result; no uploadId).
|
|
135
|
+
* @typedef {Object} PipelineUploadResponse
|
|
136
|
+
* @property {boolean} success - Request success flag
|
|
137
|
+
* @property {Object} [data] - Publication result (system, datasources, warnings)
|
|
138
|
+
* @property {string} [data.systemKey] - Published system key
|
|
139
|
+
* @property {string[]} [data.datasourceKeys] - Published datasource keys
|
|
140
|
+
* @property {string[]} [data.warnings] - Warnings if any
|
|
141
|
+
* @property {string} [formattedError] - Formatted error message on failure
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Dataplane pipeline validate request body (dry-run validation only).
|
|
146
|
+
* @typedef {Object} PipelineValidateConfigRequest
|
|
147
|
+
* @property {Object} config - Full config to validate
|
|
148
|
+
* @property {string} config.version - Config version
|
|
149
|
+
* @property {Object} config.application - Application config
|
|
150
|
+
* @property {Object[]} config.dataSources - Data source configs
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Dataplane pipeline validate response.
|
|
155
|
+
* @typedef {Object} PipelineValidateConfigResponse
|
|
156
|
+
* @property {boolean} isValid - Whether config is valid
|
|
157
|
+
* @property {string[]} [errors] - Validation errors
|
|
158
|
+
* @property {string[]} [warnings] - Validation warnings
|
|
159
|
+
*/
|
|
160
|
+
|
|
124
161
|
module.exports = {};
|
|
125
162
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Wizard platform API - getPlatformDetails, discoverEntities
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { ApiClient } = require('./index');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get platform details including available datasources
|
|
11
|
+
* GET /api/v1/wizard/platforms/{platformKey}
|
|
12
|
+
* @requiresPermission {Dataplane} external-system:read
|
|
13
|
+
* @async
|
|
14
|
+
* @function getPlatformDetails
|
|
15
|
+
* @param {string} dataplaneUrl - Dataplane base URL
|
|
16
|
+
* @param {Object} authConfig - Authentication configuration
|
|
17
|
+
* @param {string} platformKey - Platform key (e.g. 'hubspot')
|
|
18
|
+
* @returns {Promise<Object>} Platform details including datasources: [{ key, displayName, entity }]
|
|
19
|
+
* @throws {Error} If request fails or platform not found (404)
|
|
20
|
+
*/
|
|
21
|
+
async function getPlatformDetails(dataplaneUrl, authConfig, platformKey) {
|
|
22
|
+
const client = new ApiClient(dataplaneUrl, authConfig);
|
|
23
|
+
const response = await client.get(`/api/v1/wizard/platforms/${encodeURIComponent(platformKey)}`);
|
|
24
|
+
if (!response.success) {
|
|
25
|
+
const msg = response.status === 404
|
|
26
|
+
? `Platform '${platformKey}' not found`
|
|
27
|
+
: response.formattedError || response.error || 'Failed to get platform details';
|
|
28
|
+
const err = new Error(msg);
|
|
29
|
+
err.status = response.status;
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
return response;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Discover entities from OpenAPI spec (for multi-entity flows)
|
|
37
|
+
* POST /api/v1/wizard/discover-entities
|
|
38
|
+
* @requiresPermission {Dataplane} external-system:create
|
|
39
|
+
* @async
|
|
40
|
+
* @function discoverEntities
|
|
41
|
+
* @param {string} dataplaneUrl - Dataplane base URL
|
|
42
|
+
* @param {Object} authConfig - Authentication configuration
|
|
43
|
+
* @param {Object} openapiSpec - OpenAPI specification object
|
|
44
|
+
* @returns {Promise<Object>} Response with entities: [{ name, pathCount, schemaMatch }]
|
|
45
|
+
* @throws {Error} If request fails
|
|
46
|
+
*/
|
|
47
|
+
async function discoverEntities(dataplaneUrl, authConfig, openapiSpec) {
|
|
48
|
+
const client = new ApiClient(dataplaneUrl, authConfig);
|
|
49
|
+
const response = await client.post('/api/v1/wizard/discover-entities', {
|
|
50
|
+
body: { openapiSpec }
|
|
51
|
+
});
|
|
52
|
+
if (!response.success) {
|
|
53
|
+
const msg = response.formattedError || response.error || 'Failed to discover entities';
|
|
54
|
+
const err = new Error(msg);
|
|
55
|
+
err.status = response.status;
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
return response;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = { getPlatformDetails, discoverEntities };
|
package/lib/api/wizard.api.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const { ApiClient } = require('./index');
|
|
8
8
|
const { uploadFile } = require('../utils/file-upload');
|
|
9
|
+
const { getPlatformDetails, discoverEntities } = require('./wizard-platform.api');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Create wizard session
|
|
@@ -178,8 +179,36 @@ async function detectType(dataplaneUrl, authConfig, openapiSpec) {
|
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
/**
|
|
181
|
-
*
|
|
182
|
+
* Get platform configuration for a known platform (no OpenAPI parsing)
|
|
183
|
+
* POST /api/v1/wizard/platforms/{platformKey}/config
|
|
184
|
+
* Use this when sourceType=known-platform; do NOT use generate-config which requires openapiSpec.
|
|
185
|
+
* @requiresPermission {Dataplane} external-system:create
|
|
186
|
+
* @async
|
|
187
|
+
* @function getPlatformConfig
|
|
188
|
+
* @param {string} dataplaneUrl - Dataplane base URL
|
|
189
|
+
* @param {Object} authConfig - Authentication configuration
|
|
190
|
+
* @param {string} platformKey - Platform key (e.g. 'hubspot')
|
|
191
|
+
* @param {Object} config - Configuration payload (no openapiSpec)
|
|
192
|
+
* @param {string} config.mode - Wizard mode ('create-system' | 'add-datasource')
|
|
193
|
+
* @param {string} [config.systemIdOrKey] - Existing system ID/key (required for add-datasource)
|
|
194
|
+
* @param {string} [config.credentialIdOrKey] - Credential ID or key
|
|
195
|
+
* @param {string} [config.intent] - User intent
|
|
196
|
+
* @param {string} [config.fieldOnboardingLevel] - Field onboarding level ('full' | 'standard' | 'minimal')
|
|
197
|
+
* @param {Object} [config.userPreferences] - User preferences
|
|
198
|
+
* @returns {Promise<Object>} Generated configuration response
|
|
199
|
+
* @throws {Error} If request fails
|
|
200
|
+
*/
|
|
201
|
+
async function getPlatformConfig(dataplaneUrl, authConfig, platformKey, config) {
|
|
202
|
+
const client = new ApiClient(dataplaneUrl, authConfig);
|
|
203
|
+
return await client.post(`/api/v1/wizard/platforms/${encodeURIComponent(platformKey)}/config`, {
|
|
204
|
+
body: config
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Generate configuration via AI (OpenAPI-based)
|
|
182
210
|
* POST /api/v1/wizard/generate-config
|
|
211
|
+
* Do NOT use for sourceType=known-platform; use getPlatformConfig instead.
|
|
183
212
|
* @requiresPermission {Dataplane} external-system:create
|
|
184
213
|
* @async
|
|
185
214
|
* @function generateConfig
|
|
@@ -198,6 +227,7 @@ async function detectType(dataplaneUrl, authConfig, openapiSpec) {
|
|
|
198
227
|
* @param {boolean} [config.userPreferences.enableMCP] - Enable MCP
|
|
199
228
|
* @param {boolean} [config.userPreferences.enableABAC] - Enable ABAC
|
|
200
229
|
* @param {boolean} [config.userPreferences.enableRBAC] - Enable RBAC
|
|
230
|
+
* @param {string} [config.entityName] - Entity for multi-entity OpenAPI (from discover-entities)
|
|
201
231
|
* @returns {Promise<Object>} Generated configuration response
|
|
202
232
|
* @throws {Error} If request fails
|
|
203
233
|
*/
|
|
@@ -417,6 +447,9 @@ module.exports = {
|
|
|
417
447
|
parseOpenApi,
|
|
418
448
|
credentialSelection,
|
|
419
449
|
detectType,
|
|
450
|
+
getPlatformDetails,
|
|
451
|
+
discoverEntities,
|
|
452
|
+
getPlatformConfig,
|
|
420
453
|
generateConfig,
|
|
421
454
|
generateConfigStream,
|
|
422
455
|
validateWizardConfig,
|
package/lib/app/config.js
CHANGED
|
@@ -15,6 +15,7 @@ const { generateVariablesYaml, generateEnvTemplate, generateRbacYaml } = require
|
|
|
15
15
|
const { generateEnvTemplate: generateEnvTemplateFromReader } = require('../core/env-reader');
|
|
16
16
|
const { generateReadmeMdFile } = require('./readme');
|
|
17
17
|
const logger = require('../utils/logger');
|
|
18
|
+
const { systemKeyToKvPrefix } = require('../utils/credential-secrets-env');
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Checks if a file exists
|
|
@@ -73,7 +74,8 @@ async function generateVariablesYamlFile(appPath, appName, config) {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
/**
|
|
76
|
-
* Generates env.template content for external systems based on authentication type
|
|
77
|
+
* Generates env.template content for external systems based on authentication type.
|
|
78
|
+
* Uses KV_<APPKEY>_<VAR> convention (e.g. KV_HUBSPOT_CLIENTID) for credential push.
|
|
77
79
|
* @param {Object} config - Application configuration with authType and systemKey
|
|
78
80
|
* @param {string} appName - Application name (used as fallback for systemKey)
|
|
79
81
|
* @returns {string} Environment template content
|
|
@@ -81,23 +83,33 @@ async function generateVariablesYamlFile(appPath, appName, config) {
|
|
|
81
83
|
function generateExternalSystemEnvTemplate(config, appName) {
|
|
82
84
|
const systemKey = config.systemKey || appName;
|
|
83
85
|
const authType = config.authType || 'apikey';
|
|
86
|
+
const prefix = systemKeyToKvPrefix(systemKey);
|
|
87
|
+
if (!prefix) return '';
|
|
88
|
+
|
|
84
89
|
const lines = [
|
|
85
|
-
`# ${systemKey} ${authType.toUpperCase()} Configuration`,
|
|
86
|
-
'#
|
|
87
|
-
'# Values are stored in Key Vault automatically by the platform',
|
|
90
|
+
`# ${systemKey} ${String(authType).toUpperCase()} Configuration`,
|
|
91
|
+
'# Use KV_* variables for credential push (aifabrix credential push).',
|
|
92
|
+
'# Values are stored in Key Vault automatically by the platform.',
|
|
88
93
|
''
|
|
89
94
|
];
|
|
90
95
|
|
|
91
|
-
if (authType === 'oauth2') {
|
|
92
|
-
lines.push(
|
|
93
|
-
lines.push(
|
|
94
|
-
lines.push('
|
|
96
|
+
if (authType === 'oauth2' || authType === 'aad') {
|
|
97
|
+
lines.push(`KV_${prefix}_CLIENTID=`);
|
|
98
|
+
lines.push(`KV_${prefix}_CLIENTSECRET=`);
|
|
99
|
+
lines.push('TOKEN_URL=https://api.example.com/oauth/token');
|
|
95
100
|
} else if (authType === 'apikey') {
|
|
96
|
-
lines.push(
|
|
101
|
+
lines.push(`KV_${prefix}_APIKEY=`);
|
|
97
102
|
} else if (authType === 'basic') {
|
|
98
|
-
lines.push(
|
|
99
|
-
lines.push(
|
|
103
|
+
lines.push(`KV_${prefix}_USERNAME=`);
|
|
104
|
+
lines.push(`KV_${prefix}_PASSWORD=`);
|
|
105
|
+
} else if (authType === 'queryParam') {
|
|
106
|
+
lines.push(`KV_${prefix}_PARAMVALUE=`);
|
|
107
|
+
} else if (authType === 'oidc') {
|
|
108
|
+
lines.push('# OIDC: variables only (openIdConfigUrl, clientId); no security keys');
|
|
109
|
+
} else if (authType === 'hmac') {
|
|
110
|
+
lines.push(`KV_${prefix}_SIGNINGSECRET=`);
|
|
100
111
|
}
|
|
112
|
+
// none: no security keys
|
|
101
113
|
|
|
102
114
|
return lines.join('\n');
|
|
103
115
|
}
|
package/lib/app/index.js
CHANGED
|
@@ -141,8 +141,10 @@ async function generateApplicationFiles(finalAppPath, appName, config, options)
|
|
|
141
141
|
|
|
142
142
|
// Generate external system files if type is external
|
|
143
143
|
if (config.type === 'external') {
|
|
144
|
+
const configModule = require('../core/config');
|
|
145
|
+
const format = (await configModule.getFormat()) || 'yaml';
|
|
144
146
|
const externalGenerator = require('../external-system/generator');
|
|
145
|
-
await externalGenerator.generateExternalSystemFiles(finalAppPath, appName, config);
|
|
147
|
+
await externalGenerator.generateExternalSystemFiles(finalAppPath, appName, config, format);
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
if (options.app) {
|
package/lib/app/prompts.js
CHANGED
|
@@ -176,8 +176,13 @@ function buildExternalSystemTypeQuestions(options) {
|
|
|
176
176
|
message: 'What authentication type does the system use?',
|
|
177
177
|
choices: [
|
|
178
178
|
{ name: 'OAuth2', value: 'oauth2' },
|
|
179
|
+
{ name: 'Azure AD', value: 'aad' },
|
|
179
180
|
{ name: 'API Key', value: 'apikey' },
|
|
180
|
-
{ name: 'Basic Auth', value: 'basic' }
|
|
181
|
+
{ name: 'Basic Auth', value: 'basic' },
|
|
182
|
+
{ name: 'Query Parameter', value: 'queryParam' },
|
|
183
|
+
{ name: 'OpenID Connect', value: 'oidc' },
|
|
184
|
+
{ name: 'HMAC Signature', value: 'hmac' },
|
|
185
|
+
{ name: 'None', value: 'none' }
|
|
181
186
|
],
|
|
182
187
|
default: 'apikey'
|
|
183
188
|
});
|
|
@@ -185,6 +190,28 @@ function buildExternalSystemTypeQuestions(options) {
|
|
|
185
190
|
return questions;
|
|
186
191
|
}
|
|
187
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Build entityType question for external system datasources
|
|
195
|
+
* @param {Object} options - Provided options
|
|
196
|
+
* @returns {Array} Array of question objects
|
|
197
|
+
*/
|
|
198
|
+
function buildEntityTypeQuestion(options) {
|
|
199
|
+
if (options.entityType) return [];
|
|
200
|
+
return [{
|
|
201
|
+
type: 'list',
|
|
202
|
+
name: 'entityType',
|
|
203
|
+
message: 'What entity type do the datasources represent?',
|
|
204
|
+
choices: [
|
|
205
|
+
{ name: 'Record storage (CRM, deals, contacts)', value: 'recordStorage' },
|
|
206
|
+
{ name: 'Document storage (with vector)', value: 'documentStorage' },
|
|
207
|
+
{ name: 'Vector store', value: 'vectorStore' },
|
|
208
|
+
{ name: 'Message service', value: 'messageService' },
|
|
209
|
+
{ name: 'None', value: 'none' }
|
|
210
|
+
],
|
|
211
|
+
default: 'recordStorage'
|
|
212
|
+
}];
|
|
213
|
+
}
|
|
214
|
+
|
|
188
215
|
function buildExternalSystemDatasourceQuestion(options) {
|
|
189
216
|
if (options.datasourceCount) return [];
|
|
190
217
|
return [{
|
|
@@ -210,6 +237,7 @@ function buildExternalSystemQuestions(options, appName) {
|
|
|
210
237
|
return [
|
|
211
238
|
...buildExternalSystemIdentityQuestions(options, appName),
|
|
212
239
|
...buildExternalSystemTypeQuestions(options),
|
|
240
|
+
...buildEntityTypeQuestion(options),
|
|
213
241
|
...buildExternalSystemDatasourceQuestion(options)
|
|
214
242
|
];
|
|
215
243
|
}
|
|
@@ -325,6 +353,16 @@ function resolveExternalSystemField(options, answers, fieldName, defaultValue) {
|
|
|
325
353
|
return null;
|
|
326
354
|
}
|
|
327
355
|
|
|
356
|
+
const EXTERNAL_SYSTEM_FIELD_SPECS = [
|
|
357
|
+
{ key: 'systemKey', default: undefined },
|
|
358
|
+
{ key: 'systemDisplayName', default: undefined },
|
|
359
|
+
{ key: 'systemDescription', default: undefined },
|
|
360
|
+
{ key: 'systemType', default: 'openapi' },
|
|
361
|
+
{ key: 'authType', default: 'apikey' },
|
|
362
|
+
{ key: 'entityType', default: 'recordStorage' },
|
|
363
|
+
{ key: 'datasourceCount', default: 1, transform: (v) => parseInt(v, 10) }
|
|
364
|
+
];
|
|
365
|
+
|
|
328
366
|
/**
|
|
329
367
|
* Resolve external system fields and add to config
|
|
330
368
|
* @function resolveExternalSystemFields
|
|
@@ -334,34 +372,11 @@ function resolveExternalSystemField(options, answers, fieldName, defaultValue) {
|
|
|
334
372
|
* @returns {void}
|
|
335
373
|
*/
|
|
336
374
|
function resolveExternalSystemFields(options, answers, config) {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const systemDisplayName = resolveExternalSystemField(options, answers, 'systemDisplayName', undefined);
|
|
343
|
-
if (systemDisplayName !== null) {
|
|
344
|
-
config.systemDisplayName = systemDisplayName;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const systemDescription = resolveExternalSystemField(options, answers, 'systemDescription', undefined);
|
|
348
|
-
if (systemDescription !== null) {
|
|
349
|
-
config.systemDescription = systemDescription;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const systemType = resolveExternalSystemField(options, answers, 'systemType', 'openapi');
|
|
353
|
-
if (systemType !== null) {
|
|
354
|
-
config.systemType = systemType;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const authType = resolveExternalSystemField(options, answers, 'authType', 'apikey');
|
|
358
|
-
if (authType !== null) {
|
|
359
|
-
config.authType = authType;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const datasourceCount = resolveExternalSystemField(options, answers, 'datasourceCount', 1);
|
|
363
|
-
if (datasourceCount !== null) {
|
|
364
|
-
config.datasourceCount = parseInt(datasourceCount, 10);
|
|
375
|
+
for (const { key, default: defaultValue, transform } of EXTERNAL_SYSTEM_FIELD_SPECS) {
|
|
376
|
+
const value = resolveExternalSystemField(options, answers, key, defaultValue);
|
|
377
|
+
if (value !== null) {
|
|
378
|
+
config[key] = transform ? transform(value) : value;
|
|
379
|
+
}
|
|
365
380
|
}
|
|
366
381
|
}
|
|
367
382
|
|
package/lib/app/readme.js
CHANGED
|
@@ -92,20 +92,23 @@ function extractServiceFlags(config) {
|
|
|
92
92
|
/**
|
|
93
93
|
* Builds placeholder datasources for external README generation
|
|
94
94
|
* @function buildExternalDatasourcePlaceholders
|
|
95
|
+
* @param {string} systemKey - System key
|
|
95
96
|
* @param {number} datasourceCount - Datasource count
|
|
97
|
+
* @param {string} [fileExt='.json'] - File extension (e.g. '.json', '.yaml')
|
|
96
98
|
* @returns {Array<Object>} Datasource placeholders
|
|
97
99
|
*/
|
|
98
|
-
function buildExternalDatasourcePlaceholders(systemKey, datasourceCount) {
|
|
100
|
+
function buildExternalDatasourcePlaceholders(systemKey, datasourceCount, fileExt = '.json') {
|
|
99
101
|
const normalizedCount = Number.isInteger(datasourceCount)
|
|
100
102
|
? datasourceCount
|
|
101
103
|
: parseInt(datasourceCount, 10);
|
|
102
104
|
const total = Number.isFinite(normalizedCount) && normalizedCount > 0 ? normalizedCount : 0;
|
|
105
|
+
const ext = fileExt && fileExt.startsWith('.') ? fileExt : `.${fileExt || 'json'}`;
|
|
103
106
|
return Array.from({ length: total }, (_value, index) => {
|
|
104
107
|
const entityType = `entity${index + 1}`;
|
|
105
108
|
return {
|
|
106
109
|
entityType,
|
|
107
110
|
displayName: `Datasource ${index + 1}`,
|
|
108
|
-
fileName: `${systemKey}-datasource-${entityType}
|
|
111
|
+
fileName: `${systemKey}-datasource-${entityType}${ext}`
|
|
109
112
|
};
|
|
110
113
|
});
|
|
111
114
|
}
|
|
@@ -143,15 +146,17 @@ function buildReadmeContext(appName, config) {
|
|
|
143
146
|
function generateReadmeMd(appName, config) {
|
|
144
147
|
if (config.type === 'external') {
|
|
145
148
|
const systemKey = config.systemKey || appName;
|
|
149
|
+
const fileExt = config.fileExt !== undefined ? config.fileExt : '.json';
|
|
146
150
|
const datasources = Array.isArray(config.datasources) && config.datasources.length > 0
|
|
147
151
|
? config.datasources
|
|
148
|
-
: buildExternalDatasourcePlaceholders(systemKey, config.datasourceCount);
|
|
152
|
+
: buildExternalDatasourcePlaceholders(systemKey, config.datasourceCount, fileExt);
|
|
149
153
|
return generateExternalReadmeContent({
|
|
150
154
|
appName,
|
|
151
155
|
systemKey,
|
|
152
156
|
systemType: config.systemType,
|
|
153
157
|
displayName: config.systemDisplayName,
|
|
154
158
|
description: config.systemDescription,
|
|
159
|
+
fileExt: config.fileExt,
|
|
155
160
|
datasources
|
|
156
161
|
});
|
|
157
162
|
}
|
|
@@ -16,6 +16,7 @@ const pathsUtil = require('../utils/paths');
|
|
|
16
16
|
const adminSecrets = require('../core/admin-secrets');
|
|
17
17
|
const secretsEnvWrite = require('../core/secrets-env-write');
|
|
18
18
|
const { getContainerPort } = require('../utils/port-resolver');
|
|
19
|
+
const { getInfraDirName } = require('../infrastructure/helpers');
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Clean applications directory: remove generated docker-compose.yaml and .env.* files.
|
|
@@ -145,16 +146,74 @@ function buildDbInitOnlyEnv(merged) {
|
|
|
145
146
|
return dbInit;
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Return pgpass paths under infra-dev* directories in aifabrix home (for fallback lookup).
|
|
151
|
+
* @param {string} aifabrixDir - Aifabrix home directory
|
|
152
|
+
* @returns {string[]} Paths to pgpass files
|
|
153
|
+
*/
|
|
154
|
+
function getInfraDevPgpassPaths(aifabrixDir) {
|
|
155
|
+
if (!fsSync.existsSync(aifabrixDir)) return [];
|
|
156
|
+
let entries;
|
|
157
|
+
try {
|
|
158
|
+
entries = fsSync.readdirSync(aifabrixDir).sort();
|
|
159
|
+
} catch {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
return entries
|
|
163
|
+
.filter((name) => name.startsWith('infra-dev'))
|
|
164
|
+
.map((name) => path.join(aifabrixDir, name, 'pgpass'));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Read first password from a pgpass file (format host:port:db:user:password).
|
|
169
|
+
* @param {string} pgpassPath - Path to pgpass file
|
|
170
|
+
* @returns {Promise<string|undefined>} Password or undefined
|
|
171
|
+
*/
|
|
172
|
+
async function readPasswordFromPgpassFile(pgpassPath) {
|
|
173
|
+
const content = await fs.readFile(pgpassPath, 'utf8');
|
|
174
|
+
const line = content.split('\n')[0];
|
|
175
|
+
if (!line) return undefined;
|
|
176
|
+
const parts = line.split(':');
|
|
177
|
+
return parts.length >= 5 ? parts[4].trim() : undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Read POSTGRES_PASSWORD from an existing infra pgpass so db-init uses the same password as running Postgres.
|
|
182
|
+
* Tries dev-specific, then default infra, then any infra-dev* dir (e.g. dev 1 run when only infra-dev06 has pgpass).
|
|
183
|
+
* @param {number|string} developerId - Developer ID
|
|
184
|
+
* @returns {Promise<string|undefined>} Password or undefined
|
|
185
|
+
*/
|
|
186
|
+
async function readPostgresPasswordFromPgpass(developerId) {
|
|
187
|
+
const home = pathsUtil.getAifabrixHome();
|
|
188
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
189
|
+
const candidates = [path.join(home, getInfraDirName(developerId), 'pgpass')];
|
|
190
|
+
if (idNum !== 0) candidates.push(path.join(home, getInfraDirName(0), 'pgpass'));
|
|
191
|
+
const extra = getInfraDevPgpassPaths(home).filter((p) => !candidates.includes(p));
|
|
192
|
+
candidates.push(...extra);
|
|
193
|
+
for (const pgpassPath of candidates) {
|
|
194
|
+
if (!fsSync.existsSync(pgpassPath)) continue;
|
|
195
|
+
try {
|
|
196
|
+
const pwd = await readPasswordFromPgpassFile(pgpassPath);
|
|
197
|
+
if (pwd !== undefined) return pwd;
|
|
198
|
+
} catch {
|
|
199
|
+
// ignore
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
148
205
|
/**
|
|
149
206
|
* Build two run env files: .env.run (app-only, no admin secrets) and .env.run.admin (start-only, for db-init).
|
|
150
207
|
* Admin password is never set in the app container; .env.run.admin is used only for start and then deleted.
|
|
208
|
+
* When an infra pgpass exists, POSTGRES_PASSWORD is taken from it so db-init matches the running Postgres.
|
|
151
209
|
* @async
|
|
152
210
|
* @param {string} appName - Application name
|
|
153
211
|
* @param {Object} appConfig - Application configuration
|
|
154
212
|
* @param {string} devDir - Applications directory path
|
|
213
|
+
* @param {number|string} [developerId] - Developer ID (for pgpass lookup)
|
|
155
214
|
* @returns {Promise<{ runEnvPath: string, runEnvAdminPath: string }>} Paths to .env.run and .env.run.admin
|
|
156
215
|
*/
|
|
157
|
-
async function buildMergedRunEnvAndWrite(appName, appConfig, devDir) {
|
|
216
|
+
async function buildMergedRunEnvAndWrite(appName, appConfig, devDir, developerId) {
|
|
158
217
|
const infra = require('../infrastructure');
|
|
159
218
|
const ensureAdminSecretsFn = typeof infra.ensureAdminSecrets === 'function'
|
|
160
219
|
? infra.ensureAdminSecrets
|
|
@@ -167,6 +226,10 @@ async function buildMergedRunEnvAndWrite(appName, appConfig, devDir) {
|
|
|
167
226
|
force: false
|
|
168
227
|
});
|
|
169
228
|
const merged = { ...adminObj, ...appObj };
|
|
229
|
+
if (developerId !== undefined) {
|
|
230
|
+
const pgpassPwd = await readPostgresPasswordFromPgpass(developerId);
|
|
231
|
+
if (pgpassPwd !== undefined) merged.POSTGRES_PASSWORD = pgpassPwd;
|
|
232
|
+
}
|
|
170
233
|
injectDatabaseNamesAndUsers(merged, appConfig);
|
|
171
234
|
injectContainerPortForRun(merged, appConfig, appName);
|
|
172
235
|
|
package/lib/app/run-helpers.js
CHANGED
|
@@ -337,7 +337,7 @@ async function prepareEnvironment(appName, appConfig, options) {
|
|
|
337
337
|
|
|
338
338
|
runEnvCompose.cleanApplicationsDir(developerId);
|
|
339
339
|
logger.log(chalk.blue('Building merged .env (admin + app secrets)...'));
|
|
340
|
-
const { runEnvPath, runEnvAdminPath } = await runEnvCompose.buildMergedRunEnvAndWrite(appName, appConfig, devDir);
|
|
340
|
+
const { runEnvPath, runEnvAdminPath } = await runEnvCompose.buildMergedRunEnvAndWrite(appName, appConfig, devDir, developerId);
|
|
341
341
|
|
|
342
342
|
const composeOptions = {
|
|
343
343
|
...options,
|
package/lib/app/show-display.js
CHANGED
|
@@ -161,7 +161,7 @@ function logExternalSystemMain(ext) {
|
|
|
161
161
|
logger.log(` Credential: ${ext.credentialId ?? '—'}`);
|
|
162
162
|
logger.log(` Status: ${ext.status ?? '—'}`);
|
|
163
163
|
logger.log(` API docs: ${ext.openApiDocsPageUrl ?? ext.apiDocumentUrl ?? '—'}`);
|
|
164
|
-
logger.log(` MCP server:
|
|
164
|
+
logger.log(` MCP server: ${ext.mcpServerUrl ?? '—'}`);
|
|
165
165
|
logger.log(` OpenAPI spec: ${ext.apiDocumentUrl ?? '—'}`);
|
|
166
166
|
}
|
|
167
167
|
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -17,12 +17,20 @@ const { handleCommandError } = require('../utils/cli-utils');
|
|
|
17
17
|
* @param {Object} options - Raw CLI options
|
|
18
18
|
* @returns {Object} Normalized options
|
|
19
19
|
*/
|
|
20
|
+
const VALID_ENTITY_TYPES = ['recordStorage', 'documentStorage', 'vectorStore', 'messageService', 'none'];
|
|
21
|
+
|
|
20
22
|
function normalizeExternalOptions(options) {
|
|
21
23
|
const normalized = { ...options };
|
|
22
24
|
if (options.displayName) normalized.systemDisplayName = options.displayName;
|
|
23
25
|
if (options.description) normalized.systemDescription = options.description;
|
|
24
26
|
if (options.systemType) normalized.systemType = options.systemType;
|
|
25
27
|
if (options.authType) normalized.authType = options.authType;
|
|
28
|
+
if (options.entityType) {
|
|
29
|
+
if (!VALID_ENTITY_TYPES.includes(options.entityType)) {
|
|
30
|
+
throw new Error(`Invalid --entity-type. Must be one of: ${VALID_ENTITY_TYPES.join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
normalized.entityType = options.entityType;
|
|
33
|
+
}
|
|
26
34
|
if (options.datasources !== undefined) {
|
|
27
35
|
const parsedCount = parseInt(options.datasources, 10);
|
|
28
36
|
if (Number.isNaN(parsedCount) || parsedCount < 1 || parsedCount > 10) {
|
|
@@ -48,6 +56,7 @@ function validateNonInteractiveExternalOptions(normalizedOptions) {
|
|
|
48
56
|
if (!normalizedOptions.systemDescription) missing.push('--description');
|
|
49
57
|
if (!normalizedOptions.systemType) missing.push('--system-type');
|
|
50
58
|
if (!normalizedOptions.authType) missing.push('--auth-type');
|
|
59
|
+
if (!normalizedOptions.entityType) missing.push('--entity-type');
|
|
51
60
|
if (!normalizedOptions.datasourceCount) missing.push('--datasources');
|
|
52
61
|
if (missing.length > 0) {
|
|
53
62
|
throw new Error(`Missing required options for non-interactive external create: ${missing.join(', ')}`);
|
|
@@ -110,7 +119,8 @@ function setupCreateCommand(program) {
|
|
|
110
119
|
.option('--display-name <name>', 'External system display name')
|
|
111
120
|
.option('--description <desc>', 'External system description')
|
|
112
121
|
.option('--system-type <type>', 'External system type (openapi, mcp, custom)')
|
|
113
|
-
.option('--auth-type <type>', 'External system auth type (oauth2, apikey, basic)')
|
|
122
|
+
.option('--auth-type <type>', 'External system auth type (oauth2, aad, apikey, basic, queryParam, oidc, hmac, none)')
|
|
123
|
+
.option('--entity-type <type>', 'Entity type for datasources (recordStorage, documentStorage, vectorStore, messageService, none)')
|
|
114
124
|
.option('--datasources <count>', 'Number of datasources to create')
|
|
115
125
|
.action(async(appName, options) => {
|
|
116
126
|
try {
|
|
@@ -130,6 +140,7 @@ Examples:
|
|
|
130
140
|
$ aifabrix wizard my-integration --silent Run headless with integration/my-integration/wizard.yaml (no prompts)
|
|
131
141
|
$ aifabrix wizard -a my-integration Same as above (app name set)
|
|
132
142
|
$ aifabrix wizard --config wizard.yaml Run headless from a wizard config file
|
|
143
|
+
$ aifabrix wizard hubspot-test-v2 --debug Enable debug output and save debug manifests on validation failure
|
|
133
144
|
|
|
134
145
|
Config path: When appName is provided, integration/<appName>/wizard.yaml is used for load/save and error.log.
|
|
135
146
|
To change settings after a run, edit that file and run "aifabrix wizard <app>" again.
|
|
@@ -140,6 +151,7 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`;
|
|
|
140
151
|
.option('-a, --app <app>', 'Application name (synonym for positional appName)')
|
|
141
152
|
.option('--config <file>', 'Run headless using a wizard.yaml file (appName, mode, source, credential, preferences)')
|
|
142
153
|
.option('--silent', 'Run with saved integration/<app>/wizard.yaml only; no prompts (requires app name and existing wizard.yaml)')
|
|
154
|
+
.option('--debug', 'Enable debug output and save debug manifests on validation failure')
|
|
143
155
|
.addHelpText('after', wizardHelp)
|
|
144
156
|
.action(async(positionalAppName, options) => {
|
|
145
157
|
try {
|
|
@@ -280,6 +292,29 @@ function setupShellTestStopCommands(program) {
|
|
|
280
292
|
});
|
|
281
293
|
}
|
|
282
294
|
|
|
295
|
+
async function runTestE2ECommand(appName, options) {
|
|
296
|
+
const pathsUtil = require('../utils/paths');
|
|
297
|
+
const appType = await pathsUtil.detectAppType(appName).catch(() => null);
|
|
298
|
+
if (appType && appType.baseDir === 'integration') {
|
|
299
|
+
const { runTestE2EForExternalSystem } = require('../commands/test-e2e-external');
|
|
300
|
+
const { success, results } = await runTestE2EForExternalSystem(appName, {
|
|
301
|
+
env: options.env,
|
|
302
|
+
debug: options.debug,
|
|
303
|
+
verbose: options.verbose,
|
|
304
|
+
async: options.async !== false
|
|
305
|
+
});
|
|
306
|
+
results.forEach(r => {
|
|
307
|
+
const icon = r.success ? chalk.green('✓') : chalk.red('✗');
|
|
308
|
+
const msg = r.error ? `${r.key}: ${r.error}` : r.key;
|
|
309
|
+
logger.log(` ${icon} ${msg}`);
|
|
310
|
+
});
|
|
311
|
+
if (!success) process.exit(1);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const { runAppTestE2e } = require('../commands/app-test');
|
|
315
|
+
await runAppTestE2e(appName, { env: options.env });
|
|
316
|
+
}
|
|
317
|
+
|
|
283
318
|
function setupInstallTestE2eLintCommands(program) {
|
|
284
319
|
program.command('install <app>')
|
|
285
320
|
.description('Install dependencies in container (builder apps only)')
|
|
@@ -301,18 +336,14 @@ function setupInstallTestE2eLintCommands(program) {
|
|
|
301
336
|
});
|
|
302
337
|
|
|
303
338
|
program.command('test-e2e <app>')
|
|
304
|
-
.description('Run e2e tests in container
|
|
305
|
-
.option('--env <env>', 'dev
|
|
339
|
+
.description('Run e2e tests (builder: in container; external system: all datasources via dataplane)')
|
|
340
|
+
.option('-e, --env <env>', 'Environment: dev, tst, or pro (builder: dev/tst for container)')
|
|
341
|
+
.option('-v, --verbose', 'Show detailed step output and poll progress')
|
|
342
|
+
.option('--debug', 'Include debug output and write log to integration/<app>/logs/')
|
|
343
|
+
.option('--no-async', 'Use sync mode (no polling); single POST per datasource')
|
|
306
344
|
.action(async(appName, options) => {
|
|
307
345
|
try {
|
|
308
|
-
|
|
309
|
-
const appType = await pathsUtil.detectAppType(appName).catch(() => null);
|
|
310
|
-
if (appType && appType.baseDir === 'integration') {
|
|
311
|
-
logger.log(chalk.gray('test-e2e is for builder applications only. Use aifabrix shell <app> then make test:e2e or pnpm test:e2e.'));
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const { runAppTestE2e } = require('../commands/app-test');
|
|
315
|
-
await runAppTestE2e(appName, { env: options.env });
|
|
346
|
+
await runTestE2ECommand(appName, options);
|
|
316
347
|
} catch (error) {
|
|
317
348
|
handleCommandError(error, 'test-e2e');
|
|
318
349
|
process.exit(1);
|