@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
|
@@ -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
|
|
@@ -190,14 +219,16 @@ async function detectType(dataplaneUrl, authConfig, openapiSpec) {
|
|
|
190
219
|
* @param {string} config.detectedType - Detected API type (required, e.g., 'record-based')
|
|
191
220
|
* @param {string} config.intent - User intent (required, any descriptive text)
|
|
192
221
|
* @param {string} config.mode - Wizard mode (required, 'create-system' | 'add-datasource')
|
|
193
|
-
* @param {string} [config.systemIdOrKey] - Existing system ID/key (required for add-datasource)
|
|
222
|
+
* @param {string} [config.systemIdOrKey] - Existing system ID/key (required for add-datasource; must be application/system key, not entity/datasource key)
|
|
194
223
|
* @param {string} [config.credentialIdOrKey] - Credential ID or key
|
|
224
|
+
* @param {string|null} [config.systemDisplayName] - System-level display name for credential (e.g. 'Hubspot Demo'); when OpenAPI title is entity-specific, pass system name here
|
|
195
225
|
* @param {string} [config.fieldOnboardingLevel] - Field onboarding level ('full' | 'standard' | 'minimal')
|
|
196
226
|
* @param {boolean} [config.enableOpenAPIGeneration] - Enable OpenAPI operation generation
|
|
197
227
|
* @param {Object} [config.userPreferences] - User preferences
|
|
198
228
|
* @param {boolean} [config.userPreferences.enableMCP] - Enable MCP
|
|
199
229
|
* @param {boolean} [config.userPreferences.enableABAC] - Enable ABAC
|
|
200
230
|
* @param {boolean} [config.userPreferences.enableRBAC] - Enable RBAC
|
|
231
|
+
* @param {string} [config.entityName] - Entity for multi-entity OpenAPI (from discover-entities)
|
|
201
232
|
* @returns {Promise<Object>} Generated configuration response
|
|
202
233
|
* @throws {Error} If request fails
|
|
203
234
|
*/
|
|
@@ -417,6 +448,9 @@ module.exports = {
|
|
|
417
448
|
parseOpenApi,
|
|
418
449
|
credentialSelection,
|
|
419
450
|
detectType,
|
|
451
|
+
getPlatformDetails,
|
|
452
|
+
discoverEntities,
|
|
453
|
+
getPlatformConfig,
|
|
420
454
|
generateConfig,
|
|
421
455
|
generateConfigStream,
|
|
422
456
|
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
|
@@ -68,7 +68,7 @@ function validateAppNameAndSetup(appName, options) {
|
|
|
68
68
|
throw new Error('Application name is required');
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
const initialType = options.type || '
|
|
71
|
+
const initialType = options.type || 'external';
|
|
72
72
|
const baseDir = getBaseDirForAppType(initialType);
|
|
73
73
|
const appPath = getAppPath(appName, initialType);
|
|
74
74
|
|
|
@@ -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) {
|
|
@@ -177,7 +179,7 @@ async function logApplicationCreation(appName, config, options) {
|
|
|
177
179
|
async function createApp(appName, options = {}) {
|
|
178
180
|
try {
|
|
179
181
|
const { appPath } = validateAppNameAndSetup(appName, options);
|
|
180
|
-
await validateAppCreation(appName, options, appPath, getBaseDirForAppType(options.type || '
|
|
182
|
+
await validateAppCreation(appName, options, appPath, getBaseDirForAppType(options.type || 'external'));
|
|
181
183
|
|
|
182
184
|
const mergedOptions = await handleTemplateSetup(options);
|
|
183
185
|
const config = await promptForOptions(appName, mergedOptions);
|
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
|
|
|
@@ -411,8 +426,8 @@ function mergePromptAnswers(appName, options, answers) {
|
|
|
411
426
|
* @returns {Promise<Object>} Complete configuration
|
|
412
427
|
*/
|
|
413
428
|
async function promptForOptions(appName, options) {
|
|
414
|
-
// Get app type from options (default to
|
|
415
|
-
const appType = options.type || '
|
|
429
|
+
// Get app type from options (default to external)
|
|
430
|
+
const appType = options.type || 'external';
|
|
416
431
|
|
|
417
432
|
// Build questions based on app type
|
|
418
433
|
let questions = [];
|
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,16 +146,20 @@ 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);
|
|
153
|
+
const authType = config.authentication?.type || config.authentication?.method || config.authType;
|
|
149
154
|
return generateExternalReadmeContent({
|
|
150
155
|
appName,
|
|
151
156
|
systemKey,
|
|
152
157
|
systemType: config.systemType,
|
|
153
158
|
displayName: config.systemDisplayName,
|
|
154
159
|
description: config.systemDescription,
|
|
155
|
-
|
|
160
|
+
fileExt: config.fileExt,
|
|
161
|
+
datasources,
|
|
162
|
+
authType
|
|
156
163
|
});
|
|
157
164
|
}
|
|
158
165
|
const context = buildReadmeContext(appName, config);
|
|
@@ -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
|
|