@aifabrix/builder 2.40.2 → 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.
Files changed (198) hide show
  1. package/.cursor/rules/docs-rules.mdc +30 -0
  2. package/README.md +7 -5
  3. package/integration/hubspot/README.md +8 -4
  4. package/integration/hubspot/application.json +54 -0
  5. package/integration/hubspot/create-hubspot.js +9 -136
  6. package/integration/hubspot/env.template +3 -4
  7. package/integration/hubspot/hubspot-datasource-company.json +343 -5
  8. package/integration/hubspot/hubspot-datasource-contact.json +413 -5
  9. package/integration/hubspot/hubspot-datasource-deal.json +341 -4
  10. package/integration/hubspot/hubspot-datasource-users.json +116 -0
  11. package/integration/hubspot/hubspot-deploy.json +1250 -108
  12. package/integration/hubspot/hubspot-system.json +15 -32
  13. package/integration/hubspot/test-dataplane-down-tests.js +17 -16
  14. package/integration/hubspot/test-dataplane-down.js +2 -2
  15. package/integration/hubspot/test.js +1 -1
  16. package/jest.config.manual.js +2 -1
  17. package/lib/api/credential.api.js +40 -0
  18. package/lib/api/dev.api.js +423 -0
  19. package/lib/api/external-test.api.js +111 -0
  20. package/lib/api/index.js +42 -19
  21. package/lib/api/pipeline.api.js +66 -120
  22. package/lib/api/types/credential.types.js +23 -0
  23. package/lib/api/types/dev.types.js +140 -0
  24. package/lib/api/types/pipeline.types.js +37 -0
  25. package/lib/api/wizard-platform.api.js +61 -0
  26. package/lib/api/wizard.api.js +34 -1
  27. package/lib/app/config.js +44 -11
  28. package/lib/app/down.js +2 -1
  29. package/lib/app/index.js +12 -1
  30. package/lib/app/prompts.js +44 -29
  31. package/lib/app/push.js +36 -12
  32. package/lib/app/readme.js +9 -6
  33. package/lib/app/run-env-compose.js +264 -0
  34. package/lib/app/run-helpers.js +121 -118
  35. package/lib/app/run.js +148 -28
  36. package/lib/app/show-display.js +1 -1
  37. package/lib/app/show.js +5 -2
  38. package/lib/build/index.js +11 -3
  39. package/lib/cli/setup-app.js +172 -15
  40. package/lib/cli/setup-credential-deployment.js +31 -6
  41. package/lib/cli/setup-dev.js +206 -16
  42. package/lib/cli/setup-environment.js +16 -6
  43. package/lib/cli/setup-external-system.js +89 -24
  44. package/lib/cli/setup-infra.js +82 -15
  45. package/lib/cli/setup-secrets.js +52 -5
  46. package/lib/cli/setup-utility.js +129 -24
  47. package/lib/commands/app-install.js +172 -0
  48. package/lib/commands/app-shell.js +75 -0
  49. package/lib/commands/app-test.js +282 -0
  50. package/lib/commands/app.js +1 -1
  51. package/lib/commands/credential-env.js +162 -0
  52. package/lib/commands/credential-list.js +17 -22
  53. package/lib/commands/credential-push.js +96 -0
  54. package/lib/commands/datasource.js +77 -6
  55. package/lib/commands/dev-cli-handlers.js +141 -0
  56. package/lib/commands/dev-down.js +114 -0
  57. package/lib/commands/dev-init.js +347 -0
  58. package/lib/commands/repair-auth-config.js +99 -0
  59. package/lib/commands/repair-datasource-keys.js +208 -0
  60. package/lib/commands/repair-datasource.js +235 -0
  61. package/lib/commands/repair-env-template.js +348 -0
  62. package/lib/commands/repair-internal.js +85 -0
  63. package/lib/commands/repair-rbac.js +158 -0
  64. package/lib/commands/repair.js +507 -0
  65. package/lib/commands/secrets-list.js +118 -0
  66. package/lib/commands/secrets-remove.js +97 -0
  67. package/lib/commands/secrets-set.js +30 -17
  68. package/lib/commands/secrets-validate.js +50 -0
  69. package/lib/commands/test-e2e-external.js +165 -0
  70. package/lib/commands/up-dataplane.js +2 -2
  71. package/lib/commands/up-miso.js +0 -25
  72. package/lib/commands/upload.js +96 -40
  73. package/lib/commands/wizard-core-helpers.js +226 -4
  74. package/lib/commands/wizard-core.js +67 -29
  75. package/lib/commands/wizard-dataplane.js +1 -1
  76. package/lib/commands/wizard-entity-selection.js +43 -0
  77. package/lib/commands/wizard-headless.js +44 -5
  78. package/lib/commands/wizard-helpers.js +7 -3
  79. package/lib/commands/wizard.js +86 -64
  80. package/lib/core/admin-secrets.js +96 -0
  81. package/lib/core/config.js +7 -1
  82. package/lib/core/secrets-ensure.js +378 -0
  83. package/lib/core/secrets-env-write.js +157 -0
  84. package/lib/core/secrets.js +176 -89
  85. package/lib/datasource/deploy.js +12 -3
  86. package/lib/datasource/field-reference-validator.js +91 -0
  87. package/lib/datasource/test-e2e.js +219 -0
  88. package/lib/datasource/test-integration.js +154 -0
  89. package/lib/datasource/validate.js +21 -3
  90. package/lib/deployment/deployer.js +7 -5
  91. package/lib/deployment/environment-config.js +137 -0
  92. package/lib/deployment/environment.js +21 -98
  93. package/lib/deployment/push.js +32 -2
  94. package/lib/external-system/download.js +188 -203
  95. package/lib/external-system/generator.js +204 -56
  96. package/lib/external-system/test-auth.js +7 -3
  97. package/lib/external-system/test-execution.js +2 -1
  98. package/lib/external-system/test-system-level.js +73 -0
  99. package/lib/external-system/test.js +56 -19
  100. package/lib/generator/external-controller-manifest.js +29 -2
  101. package/lib/generator/external-schema-utils.js +1 -1
  102. package/lib/generator/external.js +10 -3
  103. package/lib/generator/index.js +177 -25
  104. package/lib/generator/split-readme.js +1 -0
  105. package/lib/generator/split-variables.js +7 -1
  106. package/lib/generator/split.js +194 -54
  107. package/lib/generator/wizard-prompts-secondary.js +294 -0
  108. package/lib/generator/wizard-prompts.js +105 -106
  109. package/lib/generator/wizard-readme.js +88 -0
  110. package/lib/generator/wizard.js +155 -158
  111. package/lib/infrastructure/compose.js +11 -1
  112. package/lib/infrastructure/helpers.js +103 -20
  113. package/lib/infrastructure/index.js +98 -12
  114. package/lib/infrastructure/services.js +88 -22
  115. package/lib/schema/application-schema.json +32 -8
  116. package/lib/schema/external-datasource.schema.json +49 -26
  117. package/lib/schema/external-system.schema.json +509 -411
  118. package/lib/schema/wizard-config.schema.json +16 -0
  119. package/lib/utils/api.js +41 -13
  120. package/lib/utils/app-register-auth.js +25 -3
  121. package/lib/utils/auth-headers.js +8 -7
  122. package/lib/utils/cli-utils.js +20 -0
  123. package/lib/utils/compose-generator.js +77 -76
  124. package/lib/utils/compose-handlebars-helpers.js +54 -0
  125. package/lib/utils/compose-vector-helper.js +18 -0
  126. package/lib/utils/config-format-preference.js +51 -0
  127. package/lib/utils/config-format.js +36 -0
  128. package/lib/utils/config-paths.js +127 -2
  129. package/lib/utils/configuration-env-resolver.js +179 -0
  130. package/lib/utils/credential-display.js +83 -0
  131. package/lib/utils/credential-secrets-env.js +357 -0
  132. package/lib/utils/dataplane-pipeline-warning.js +28 -0
  133. package/lib/utils/deployment-validation-helpers.js +4 -4
  134. package/lib/utils/dev-ca-install.js +139 -0
  135. package/lib/utils/dev-cert-helper.js +122 -0
  136. package/lib/utils/device-code-helpers.js +224 -0
  137. package/lib/utils/device-code.js +37 -336
  138. package/lib/utils/docker-build.js +40 -8
  139. package/lib/utils/env-copy.js +103 -13
  140. package/lib/utils/env-map.js +35 -5
  141. package/lib/utils/env-template.js +6 -5
  142. package/lib/utils/error-formatters/http-status-errors.js +20 -2
  143. package/lib/utils/error-formatters/permission-errors.js +0 -1
  144. package/lib/utils/error-formatters/validation-errors.js +0 -1
  145. package/lib/utils/external-readme.js +56 -29
  146. package/lib/utils/external-system-display.js +59 -1
  147. package/lib/utils/external-system-test-helpers.js +21 -8
  148. package/lib/utils/external-system-validators.js +3 -0
  149. package/lib/utils/file-upload.js +20 -50
  150. package/lib/utils/help-builder.js +16 -2
  151. package/lib/utils/infra-status.js +80 -45
  152. package/lib/utils/local-secrets.js +7 -52
  153. package/lib/utils/mutagen-install.js +195 -0
  154. package/lib/utils/mutagen.js +146 -0
  155. package/lib/utils/paths.js +128 -37
  156. package/lib/utils/port-resolver.js +28 -16
  157. package/lib/utils/remote-dev-auth.js +38 -0
  158. package/lib/utils/remote-docker-env.js +43 -0
  159. package/lib/utils/remote-secrets-loader.js +60 -0
  160. package/lib/utils/secrets-canonical.js +93 -0
  161. package/lib/utils/secrets-generator.js +114 -6
  162. package/lib/utils/secrets-helpers.js +108 -114
  163. package/lib/utils/secrets-path.js +2 -2
  164. package/lib/utils/secrets-utils.js +52 -1
  165. package/lib/utils/secrets-validation.js +84 -0
  166. package/lib/utils/ssh-key-helper.js +116 -0
  167. package/lib/utils/test-log-writer.js +56 -0
  168. package/lib/utils/token-manager-messages.js +90 -0
  169. package/lib/utils/token-manager.js +29 -36
  170. package/lib/utils/variable-transformer.js +3 -3
  171. package/lib/validation/env-template-auth.js +157 -0
  172. package/lib/validation/env-template-kv.js +41 -0
  173. package/lib/validation/external-manifest-validator.js +25 -0
  174. package/lib/validation/external-system-auth-rules.js +86 -0
  175. package/lib/validation/validate-batch.js +149 -0
  176. package/lib/validation/validate-datasource-keys-api.js +33 -0
  177. package/lib/validation/validate-display.js +94 -16
  178. package/lib/validation/validate.js +25 -12
  179. package/lib/validation/validator.js +72 -9
  180. package/lib/validation/wizard-datasource-validation.js +50 -0
  181. package/package.json +8 -3
  182. package/scripts/install-local.js +34 -15
  183. package/templates/README.md +0 -1
  184. package/templates/applications/README.md.hbs +4 -4
  185. package/templates/applications/dataplane/application.yaml +6 -5
  186. package/templates/applications/dataplane/env.template +15 -10
  187. package/templates/applications/dataplane/rbac.yaml +2 -2
  188. package/templates/applications/keycloak/env.template +2 -0
  189. package/templates/applications/miso-controller/application.yaml +1 -0
  190. package/templates/applications/miso-controller/env.template +12 -10
  191. package/templates/external-system/README.md.hbs +65 -25
  192. package/templates/external-system/deploy.js.hbs +4 -2
  193. package/templates/external-system/external-datasource.yaml.hbs +217 -0
  194. package/templates/external-system/external-system.json.hbs +1 -18
  195. package/templates/infra/compose.yaml.hbs +6 -0
  196. package/templates/python/docker-compose.hbs +49 -23
  197. package/templates/typescript/docker-compose.hbs +48 -22
  198. package/integration/hubspot/application.yaml +0 -37
@@ -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 };
@@ -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
- * Generate configuration via AI
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
@@ -31,6 +32,26 @@ async function fileExists(filePath) {
31
32
  }
32
33
  }
33
34
 
35
+ /**
36
+ * Renames legacy variables.yaml to application.yaml if only variables.yaml exists.
37
+ * Ensures create always results in application.yaml.
38
+ * @async
39
+ * @param {string} appPath - Path to application directory
40
+ */
41
+ async function normalizeLegacyVariablesYaml(appPath) {
42
+ const applicationYaml = path.join(appPath, 'application.yaml');
43
+ const applicationYml = path.join(appPath, 'application.yml');
44
+ const applicationJson = path.join(appPath, 'application.json');
45
+ const variablesYaml = path.join(appPath, 'variables.yaml');
46
+ const hasAppYaml = await fileExists(applicationYaml);
47
+ const hasAppYml = await fileExists(applicationYml);
48
+ const hasAppJson = await fileExists(applicationJson);
49
+ const hasVariables = await fileExists(variablesYaml);
50
+ if (hasVariables && !hasAppYaml && !hasAppYml && !hasAppJson) {
51
+ await fs.rename(variablesYaml, applicationYaml);
52
+ }
53
+ }
54
+
34
55
  /**
35
56
  * Generates application.yaml file if no application config exists
36
57
  * @async
@@ -39,6 +60,7 @@ async function fileExists(filePath) {
39
60
  * @param {Object} config - Application configuration
40
61
  */
41
62
  async function generateVariablesYamlFile(appPath, appName, config) {
63
+ await normalizeLegacyVariablesYaml(appPath);
42
64
  const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
43
65
  try {
44
66
  resolveApplicationConfigPath(appPath);
@@ -52,7 +74,8 @@ async function generateVariablesYamlFile(appPath, appName, config) {
52
74
  }
53
75
 
54
76
  /**
55
- * 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.
56
79
  * @param {Object} config - Application configuration with authType and systemKey
57
80
  * @param {string} appName - Application name (used as fallback for systemKey)
58
81
  * @returns {string} Environment template content
@@ -60,23 +83,33 @@ async function generateVariablesYamlFile(appPath, appName, config) {
60
83
  function generateExternalSystemEnvTemplate(config, appName) {
61
84
  const systemKey = config.systemKey || appName;
62
85
  const authType = config.authType || 'apikey';
86
+ const prefix = systemKeyToKvPrefix(systemKey);
87
+ if (!prefix) return '';
88
+
63
89
  const lines = [
64
- `# ${systemKey} ${authType.toUpperCase()} Configuration`,
65
- '# These values are set via the Miso Controller interface or Dataplane portal',
66
- '# 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.',
67
93
  ''
68
94
  ];
69
95
 
70
- if (authType === 'oauth2') {
71
- lines.push('CLIENTID=kv://' + systemKey + '-clientidKeyVault');
72
- lines.push('CLIENTSECRET=kv://' + systemKey + '-clientsecretKeyVault');
73
- lines.push('TOKENURL=https://api.example.com/oauth/token');
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');
74
100
  } else if (authType === 'apikey') {
75
- lines.push('API_KEY=kv://' + systemKey + '-api-keyKeyVault');
101
+ lines.push(`KV_${prefix}_APIKEY=`);
76
102
  } else if (authType === 'basic') {
77
- lines.push('USERNAME=kv://' + systemKey + '-usernameKeyVault');
78
- lines.push('PASSWORD=kv://' + systemKey + '-passwordKeyVault');
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=`);
79
111
  }
112
+ // none: no security keys
80
113
 
81
114
  return lines.join('\n');
82
115
  }
package/lib/app/down.js CHANGED
@@ -118,6 +118,7 @@ async function downApp(appName, options = {}) {
118
118
  }
119
119
 
120
120
  module.exports = {
121
- downApp
121
+ downApp,
122
+ getAppVolumeName
122
123
  };
123
124
 
package/lib/app/index.js CHANGED
@@ -31,6 +31,8 @@ const {
31
31
  processTemplateFiles,
32
32
  setupAppFiles
33
33
  } = require('./helpers');
34
+ const path = require('path');
35
+ const secretsEnsure = require('../core/secrets-ensure');
34
36
 
35
37
  /**
36
38
  * Creates new application with scaffolded configuration files
@@ -130,10 +132,19 @@ async function generateApplicationFiles(finalAppPath, appName, config, options)
130
132
 
131
133
  await generateConfigFiles(finalAppPath, appName, config, existingEnv);
132
134
 
135
+ const envTemplatePath = path.join(finalAppPath, 'env.template');
136
+ try {
137
+ await secretsEnsure.ensureSecretsFromEnvTemplate(envTemplatePath, {});
138
+ } catch (err) {
139
+ if (err.code !== 'ENOENT') throw err;
140
+ }
141
+
133
142
  // Generate external system files if type is external
134
143
  if (config.type === 'external') {
144
+ const configModule = require('../core/config');
145
+ const format = (await configModule.getFormat()) || 'yaml';
135
146
  const externalGenerator = require('../external-system/generator');
136
- await externalGenerator.generateExternalSystemFiles(finalAppPath, appName, config);
147
+ await externalGenerator.generateExternalSystemFiles(finalAppPath, appName, config, format);
137
148
  }
138
149
 
139
150
  if (options.app) {
@@ -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 systemKey = resolveExternalSystemField(options, answers, 'systemKey', undefined);
338
- if (systemKey !== null) {
339
- config.systemKey = systemKey;
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/push.js CHANGED
@@ -40,21 +40,38 @@ function validateAppName(appName) {
40
40
  }
41
41
 
42
42
  /**
43
- * Extracts image name from configuration using the same logic as build command
44
- * @param {Object} config - Configuration object from application.yaml
45
- * @param {string} appName - Application name (fallback)
46
- * @returns {string} Image name
43
+ * Returns effective config (unwrap variables wrapper if present so image/app are at top level).
44
+ * application.yaml may have top-level image/app or a variables: { image, app } wrapper.
45
+ * @param {Object} config - Raw config from loadConfigFile
46
+ * @returns {Object} Config with image and app at top level
47
+ */
48
+ function getEffectiveConfig(config) {
49
+ if (!config || typeof config !== 'object') return config || {};
50
+ if (config.variables && typeof config.variables === 'object' && (config.variables.image !== undefined || config.variables.app !== undefined)) {
51
+ return config.variables;
52
+ }
53
+ return config;
54
+ }
55
+
56
+ /**
57
+ * Extracts image name from configuration using the same logic as build command.
58
+ * Uses image.name (e.g. aifabrix/dataplane) so ACR repository matches application.yaml.
59
+ * @param {Object} config - Configuration object from application.yaml (or effective config)
60
+ * @param {string} appName - Application name (fallback when image not set)
61
+ * @returns {string} Image name (e.g. aifabrix/dataplane, not just dataplane)
47
62
  */
48
63
  function extractImageName(config, appName) {
49
- if (typeof config.image === 'string') {
50
- return config.image.split(':')[0];
51
- } else if (config.image?.name) {
52
- return config.image.name;
53
- } else if (config.app?.key) {
54
- return config.app.key;
64
+ const c = getEffectiveConfig(config);
65
+ if (typeof c.image === 'string') {
66
+ return c.image.split(':')[0];
67
+ }
68
+ if (c.image?.name) {
69
+ return c.image.name;
70
+ }
71
+ if (c.app?.key) {
72
+ return c.app.key;
55
73
  }
56
74
  return appName;
57
-
58
75
  }
59
76
 
60
77
  /**
@@ -72,7 +89,8 @@ async function loadPushConfig(appName, options) {
72
89
  try {
73
90
  const configPath = resolveApplicationConfigPath(appPath);
74
91
  const config = loadConfigFile(configPath);
75
- const registry = options.registry || config.image?.registry;
92
+ const effective = getEffectiveConfig(config);
93
+ const registry = options.registry || effective.image?.registry;
76
94
  if (!registry) {
77
95
  throw new Error('Registry URL is required. Provide via --registry flag or configure in application config under image.registry');
78
96
  }
@@ -110,6 +128,12 @@ async function validatePushConfig(registry, imageName, appName) {
110
128
  if (!await pushUtils.checkAzureCLIInstalled()) {
111
129
  throw new Error('Azure CLI is not installed. Install from: https://docs.microsoft.com/cli/azure/install-azure-cli');
112
130
  }
131
+
132
+ if (!await pushUtils.checkAzureLogin()) {
133
+ throw new Error(
134
+ 'Not logged in to Azure. Run "az login" first, then run push again.'
135
+ );
136
+ }
113
137
  }
114
138
 
115
139
  /**
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}.yaml`
111
+ fileName: `${systemKey}-datasource-${entityType}${ext}`
109
112
  };
110
113
  });
111
114
  }
@@ -120,9 +123,7 @@ function buildExternalDatasourcePlaceholders(systemKey, datasourceCount) {
120
123
  function buildReadmeContext(appName, config) {
121
124
  const displayName = config.displayName || formatAppDisplayName(appName);
122
125
  const port = config.port ?? 3000;
123
- const localPort = (typeof config.build?.localPort === 'number' && config.build.localPort > 0)
124
- ? config.build.localPort
125
- : port;
126
+ const localPort = port;
126
127
  const imageName = config.image?.name || `aifabrix/${appName}`;
127
128
  // Extract registry from nested structure (config.image.registry) or flattened (config.registry)
128
129
  const registry = config.image?.registry || config.registry || 'myacr.azurecr.io';
@@ -145,15 +146,17 @@ function buildReadmeContext(appName, config) {
145
146
  function generateReadmeMd(appName, config) {
146
147
  if (config.type === 'external') {
147
148
  const systemKey = config.systemKey || appName;
149
+ const fileExt = config.fileExt !== undefined ? config.fileExt : '.json';
148
150
  const datasources = Array.isArray(config.datasources) && config.datasources.length > 0
149
151
  ? config.datasources
150
- : buildExternalDatasourcePlaceholders(systemKey, config.datasourceCount);
152
+ : buildExternalDatasourcePlaceholders(systemKey, config.datasourceCount, fileExt);
151
153
  return generateExternalReadmeContent({
152
154
  appName,
153
155
  systemKey,
154
156
  systemType: config.systemType,
155
157
  displayName: config.systemDisplayName,
156
158
  description: config.systemDescription,
159
+ fileExt: config.fileExt,
157
160
  datasources
158
161
  });
159
162
  }