@aifabrix/builder 2.32.2 → 2.33.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 (130) hide show
  1. package/.cursor/rules/project-rules.mdc +8 -0
  2. package/README.md +36 -8
  3. package/bin/aifabrix.js +6 -8
  4. package/integration/hubspot/README.md +8 -7
  5. package/integration/hubspot/companies.json +2048 -0
  6. package/integration/hubspot/create-hubspot.js +665 -0
  7. package/integration/hubspot/{hubspot-deploy-company.json → hubspot-datasource-company.json} +1 -1
  8. package/integration/hubspot/{hubspot-deploy-contact.json → hubspot-datasource-contact.json} +1 -1
  9. package/integration/hubspot/{hubspot-deploy-deal.json → hubspot-datasource-deal.json} +1 -1
  10. package/integration/hubspot/hubspot-deploy.json +832 -81
  11. package/integration/hubspot/hubspot-system.json +99 -0
  12. package/integration/hubspot/test-artifacts/wizard-hubspot-credential-real.yaml +20 -0
  13. package/integration/hubspot/test-artifacts/wizard-hubspot-env-vars.yaml +9 -0
  14. package/integration/hubspot/test-artifacts/wizard-invalid-add-datasource.yaml +5 -0
  15. package/integration/hubspot/test-artifacts/wizard-invalid-app-name.yaml +5 -0
  16. package/integration/hubspot/test-artifacts/wizard-invalid-credential-create.yaml +7 -0
  17. package/integration/hubspot/test-artifacts/wizard-invalid-credential-select.yaml +7 -0
  18. package/integration/hubspot/test-artifacts/wizard-invalid-known-platform.yaml +4 -0
  19. package/integration/hubspot/test-artifacts/wizard-invalid-missing-app.yaml +4 -0
  20. package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
  21. package/integration/hubspot/test-artifacts/wizard-invalid-mode.yaml +5 -0
  22. package/integration/hubspot/test-artifacts/wizard-invalid-openapi-file.yaml +5 -0
  23. package/integration/hubspot/test-artifacts/wizard-invalid-openapi-url.yaml +4 -0
  24. package/integration/hubspot/test-artifacts/wizard-invalid-source.yaml +4 -0
  25. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-array-test.yaml +5 -0
  26. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
  27. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
  28. package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-test.yaml +5 -0
  29. package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-test.yaml +5 -0
  30. package/integration/hubspot/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +5 -0
  31. package/integration/hubspot/test-dataplane-down-helpers.js +246 -0
  32. package/integration/hubspot/test-dataplane-down-tests.js +419 -0
  33. package/integration/hubspot/test-dataplane-down.js +157 -0
  34. package/integration/hubspot/test.js +1517 -0
  35. package/integration/hubspot/variables.yaml +4 -4
  36. package/integration/hubspot/wizard-hubspot-e2e.yaml +16 -0
  37. package/integration/hubspot/wizard-hubspot-platform.yaml +8 -0
  38. package/lib/api/applications.api.js +1 -0
  39. package/lib/api/index.js +10 -5
  40. package/lib/api/types/wizard.types.js +176 -38
  41. package/lib/api/wizard.api.js +207 -38
  42. package/lib/app/deploy.js +116 -54
  43. package/lib/app/display.js +6 -5
  44. package/lib/app/dockerfile.js +2 -1
  45. package/lib/app/list.js +78 -37
  46. package/lib/app/prompts.js +9 -5
  47. package/lib/app/readme.js +41 -112
  48. package/lib/app/register.js +44 -9
  49. package/lib/app/rotate-secret.js +50 -32
  50. package/lib/cli.js +243 -65
  51. package/lib/commands/app.js +4 -9
  52. package/lib/commands/auth-config.js +125 -0
  53. package/lib/commands/auth-status.js +261 -0
  54. package/lib/commands/datasource.js +3 -6
  55. package/lib/commands/login-credentials.js +4 -4
  56. package/lib/commands/login-device.js +43 -29
  57. package/lib/commands/login.js +22 -13
  58. package/lib/commands/wizard-config-normalizer.js +92 -0
  59. package/lib/commands/wizard-core.js +515 -0
  60. package/lib/commands/wizard-dataplane.js +122 -0
  61. package/lib/commands/wizard-headless.js +115 -0
  62. package/lib/commands/wizard.js +129 -357
  63. package/lib/core/config.js +46 -0
  64. package/lib/core/secrets.js +3 -22
  65. package/lib/core/templates-env.js +1 -1
  66. package/lib/datasource/deploy.js +34 -23
  67. package/lib/datasource/list.js +8 -6
  68. package/lib/deployment/deployer.js +25 -0
  69. package/lib/deployment/environment.js +10 -13
  70. package/lib/external-system/delete.js +151 -0
  71. package/lib/external-system/deploy.js +54 -378
  72. package/lib/external-system/download-helpers.js +45 -65
  73. package/lib/external-system/download.js +34 -13
  74. package/lib/external-system/generator.js +11 -7
  75. package/lib/external-system/test-auth.js +5 -3
  76. package/lib/generator/builders.js +3 -1
  77. package/lib/generator/external-controller-manifest.js +157 -0
  78. package/lib/generator/external-schema-utils.js +236 -0
  79. package/lib/generator/external.js +55 -3
  80. package/lib/generator/index.js +22 -10
  81. package/lib/generator/wizard-prompts.js +33 -10
  82. package/lib/generator/wizard.js +69 -86
  83. package/lib/infrastructure/compose.js +100 -0
  84. package/lib/infrastructure/helpers.js +139 -0
  85. package/lib/infrastructure/index.js +52 -311
  86. package/lib/infrastructure/services.js +168 -0
  87. package/lib/schema/application-schema.json +24 -5
  88. package/lib/schema/external-datasource.schema.json +303 -17
  89. package/lib/schema/external-system.schema.json +1 -1
  90. package/lib/schema/wizard-config.schema.json +234 -0
  91. package/lib/utils/api.js +37 -42
  92. package/lib/utils/app-existence.js +42 -0
  93. package/lib/utils/app-register-config.js +7 -2
  94. package/lib/utils/app-register-display.js +2 -1
  95. package/lib/utils/auth-config-validator.js +92 -0
  96. package/lib/utils/cli-utils.js +3 -1
  97. package/lib/utils/command-header.js +43 -0
  98. package/lib/utils/compose-generator.js +113 -70
  99. package/lib/utils/controller-url.js +115 -0
  100. package/lib/utils/dataplane-health.js +115 -0
  101. package/lib/utils/dataplane-resolver.js +29 -0
  102. package/lib/utils/dev-config.js +6 -2
  103. package/lib/utils/env-copy.js +2 -1
  104. package/lib/utils/env-map.js +2 -1
  105. package/lib/utils/env-ports.js +2 -1
  106. package/lib/utils/env-template.js +1 -1
  107. package/lib/utils/error-formatter.js +149 -28
  108. package/lib/utils/external-readme.js +125 -0
  109. package/lib/utils/help-builder.js +190 -0
  110. package/lib/utils/infra-status.js +13 -3
  111. package/lib/utils/paths.js +17 -2
  112. package/lib/utils/port-resolver.js +111 -0
  113. package/lib/utils/secrets-helpers.js +3 -15
  114. package/lib/utils/secrets-utils.js +2 -2
  115. package/lib/utils/token-manager.js +69 -4
  116. package/lib/utils/variable-transformer.js +7 -2
  117. package/lib/validation/external-manifest-validator.js +202 -0
  118. package/lib/validation/validate-display.js +406 -0
  119. package/lib/validation/validate.js +159 -123
  120. package/lib/validation/validator.js +38 -4
  121. package/lib/validation/wizard-config-validator.js +267 -0
  122. package/package.json +4 -2
  123. package/templates/applications/README.md.hbs +19 -17
  124. package/templates/applications/miso-controller/env.template +1 -1
  125. package/templates/applications/miso-controller/rbac.yaml +7 -7
  126. package/templates/external-system/README.md.hbs +99 -0
  127. package/templates/external-system/external-system.json.hbs +1 -1
  128. package/templates/infra/compose.yaml.hbs +35 -0
  129. package/templates/python/docker-compose.hbs +26 -0
  130. package/templates/typescript/docker-compose.hbs +26 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview Wizard API functions
2
+ * @fileoverview Wizard API functions for external system creation
3
3
  * @author AI Fabrix Team
4
4
  * @version 2.0.0
5
5
  */
@@ -8,59 +8,147 @@ const { ApiClient } = require('./index');
8
8
  const { uploadFile } = require('../utils/file-upload');
9
9
 
10
10
  /**
11
- * Select wizard mode
12
- * POST /api/v1/wizard/mode-selection
11
+ * Create wizard session
12
+ * POST /api/v1/wizard/sessions
13
13
  * @async
14
- * @function selectMode
14
+ * @function createWizardSession
15
15
  * @param {string} dataplaneUrl - Dataplane base URL
16
16
  * @param {Object} authConfig - Authentication configuration
17
17
  * @param {string} mode - Wizard mode ('create-system' | 'add-datasource')
18
- * @returns {Promise<Object>} Mode selection response
18
+ * @param {string} [systemIdOrKey] - Existing system ID or key (required when mode='add-datasource')
19
+ * @returns {Promise<Object>} Session creation response with sessionId
19
20
  * @throws {Error} If request fails
20
21
  */
21
- async function selectMode(dataplaneUrl, authConfig, mode) {
22
+ async function createWizardSession(dataplaneUrl, authConfig, mode, systemIdOrKey = null) {
22
23
  const client = new ApiClient(dataplaneUrl, authConfig);
23
- return await client.post('/api/v1/wizard/mode-selection', {
24
- body: { mode }
24
+ const body = { mode };
25
+ if (systemIdOrKey) {
26
+ body.systemIdOrKey = systemIdOrKey;
27
+ }
28
+ return await client.post('/api/v1/wizard/sessions', {
29
+ body
25
30
  });
26
31
  }
27
32
 
28
33
  /**
29
- * Select wizard source
30
- * POST /api/v1/wizard/source-selection
34
+ * Get wizard session
35
+ * GET /api/v1/wizard/sessions/{sessionId}
31
36
  * @async
32
- * @function selectSource
37
+ * @function getWizardSession
33
38
  * @param {string} dataplaneUrl - Dataplane base URL
39
+ * @param {string} sessionId - Session ID
34
40
  * @param {Object} authConfig - Authentication configuration
35
- * @param {string} sourceType - Source type ('openapi-file' | 'openapi-url' | 'mcp-server' | 'known-platform')
36
- * @param {string} [sourceData] - Source data (file path, URL, etc.)
37
- * @returns {Promise<Object>} Source selection response
41
+ * @returns {Promise<Object>} Session state response
38
42
  * @throws {Error} If request fails
39
43
  */
40
- async function selectSource(dataplaneUrl, authConfig, sourceType, sourceData) {
44
+ async function getWizardSession(dataplaneUrl, sessionId, authConfig) {
41
45
  const client = new ApiClient(dataplaneUrl, authConfig);
42
- return await client.post('/api/v1/wizard/source-selection', {
43
- body: {
44
- sourceType,
45
- sourceData
46
- }
46
+ return await client.get(`/api/v1/wizard/sessions/${sessionId}`);
47
+ }
48
+
49
+ /**
50
+ * Update wizard session
51
+ * PUT /api/v1/wizard/sessions/{sessionId}
52
+ * @async
53
+ * @function updateWizardSession
54
+ * @param {string} dataplaneUrl - Dataplane base URL
55
+ * @param {string} sessionId - Session ID
56
+ * @param {Object} authConfig - Authentication configuration
57
+ * @param {Object} updateData - Session update data
58
+ * @param {number} [updateData.currentStep] - Current wizard step (0-6)
59
+ * @param {string} [updateData.credentialIdOrKey] - Selected credential ID or key
60
+ * @param {Object} [updateData.openapiSpec] - Parsed OpenAPI specification
61
+ * @param {string} [updateData.mcpServerUrl] - MCP server URL
62
+ * @param {Array} [updateData.detectedTypes] - Detected API types
63
+ * @param {string} [updateData.selectedType] - Selected API type
64
+ * @param {string} [updateData.intent] - User intent
65
+ * @param {string} [updateData.fieldOnboardingLevel] - Field onboarding level
66
+ * @param {boolean} [updateData.enableOpenAPIGeneration] - Enable OpenAPI generation
67
+ * @param {Object} [updateData.systemConfig] - Generated system configuration
68
+ * @param {Object} [updateData.datasourceConfig] - Generated datasource configuration
69
+ * @param {Object} [updateData.validationResults] - Validation results
70
+ * @returns {Promise<Object>} Updated session state response
71
+ * @throws {Error} If request fails
72
+ */
73
+ async function updateWizardSession(dataplaneUrl, sessionId, authConfig, updateData) {
74
+ const client = new ApiClient(dataplaneUrl, authConfig);
75
+ return await client.put(`/api/v1/wizard/sessions/${sessionId}`, {
76
+ body: updateData
47
77
  });
48
78
  }
49
79
 
50
80
  /**
51
- * Parse OpenAPI file
81
+ * Delete wizard session
82
+ * DELETE /api/v1/wizard/sessions/{sessionId}
83
+ * @async
84
+ * @function deleteWizardSession
85
+ * @param {string} dataplaneUrl - Dataplane base URL
86
+ * @param {string} sessionId - Session ID
87
+ * @param {Object} authConfig - Authentication configuration
88
+ * @returns {Promise<Object>} Delete response
89
+ * @throws {Error} If request fails
90
+ */
91
+ async function deleteWizardSession(dataplaneUrl, sessionId, authConfig) {
92
+ const client = new ApiClient(dataplaneUrl, authConfig);
93
+ return await client.delete(`/api/v1/wizard/sessions/${sessionId}`);
94
+ }
95
+
96
+ /**
97
+ * Get wizard session progress
98
+ * GET /api/v1/wizard/sessions/{sessionId}/progress
99
+ * @async
100
+ * @function getWizardProgress
101
+ * @param {string} dataplaneUrl - Dataplane base URL
102
+ * @param {string} sessionId - Session ID
103
+ * @param {Object} authConfig - Authentication configuration
104
+ * @returns {Promise<Object>} Session progress response
105
+ * @throws {Error} If request fails
106
+ */
107
+ async function getWizardProgress(dataplaneUrl, sessionId, authConfig) {
108
+ const client = new ApiClient(dataplaneUrl, authConfig);
109
+ return await client.get(`/api/v1/wizard/sessions/${sessionId}/progress`);
110
+ }
111
+
112
+ /**
113
+ * Parse OpenAPI file or URL
52
114
  * POST /api/v1/wizard/parse-openapi
53
115
  * @async
54
116
  * @function parseOpenApi
55
117
  * @param {string} dataplaneUrl - Dataplane base URL
56
118
  * @param {Object} authConfig - Authentication configuration
57
- * @param {string} openApiFilePath - Path to OpenAPI file
119
+ * @param {string} openApiFilePathOrUrl - Path to OpenAPI file or URL
120
+ * @param {boolean} [isUrl=false] - Whether the input is a URL
58
121
  * @returns {Promise<Object>} Parsed OpenAPI response
59
122
  * @throws {Error} If request fails
60
123
  */
61
- async function parseOpenApi(dataplaneUrl, authConfig, openApiFilePath) {
124
+ async function parseOpenApi(dataplaneUrl, authConfig, openApiFilePathOrUrl, isUrl = false) {
125
+ if (isUrl) {
126
+ const client = new ApiClient(dataplaneUrl, authConfig);
127
+ return await client.post(`/api/v1/wizard/parse-openapi?url=${encodeURIComponent(openApiFilePathOrUrl)}`);
128
+ }
62
129
  const url = `${dataplaneUrl.replace(/\/$/, '')}/api/v1/wizard/parse-openapi`;
63
- return await uploadFile(url, openApiFilePath, 'file', authConfig);
130
+ return await uploadFile(url, openApiFilePathOrUrl, 'file', authConfig);
131
+ }
132
+
133
+ /**
134
+ * Credential selection for wizard
135
+ * POST /api/v1/wizard/credential-selection
136
+ * @async
137
+ * @function credentialSelection
138
+ * @param {string} dataplaneUrl - Dataplane base URL
139
+ * @param {Object} authConfig - Authentication configuration
140
+ * @param {Object} selectionData - Credential selection data
141
+ * @param {string} selectionData.action - Action type ('create' | 'select' | 'skip')
142
+ * @param {Object} [selectionData.credentialConfig] - Credential config (required when action='create')
143
+ * @param {string} [selectionData.credentialIdOrKey] - Credential ID or key (required when action='select')
144
+ * @returns {Promise<Object>} Credential selection response
145
+ * @throws {Error} If request fails
146
+ */
147
+ async function credentialSelection(dataplaneUrl, authConfig, selectionData) {
148
+ const client = new ApiClient(dataplaneUrl, authConfig);
149
+ return await client.post('/api/v1/wizard/credential-selection', {
150
+ body: selectionData
151
+ });
64
152
  }
65
153
 
66
154
  /**
@@ -70,14 +158,14 @@ async function parseOpenApi(dataplaneUrl, authConfig, openApiFilePath) {
70
158
  * @function detectType
71
159
  * @param {string} dataplaneUrl - Dataplane base URL
72
160
  * @param {Object} authConfig - Authentication configuration
73
- * @param {Object} openApiSpec - OpenAPI specification object
161
+ * @param {Object} openapiSpec - OpenAPI specification object
74
162
  * @returns {Promise<Object>} Type detection response
75
163
  * @throws {Error} If request fails
76
164
  */
77
- async function detectType(dataplaneUrl, authConfig, openApiSpec) {
165
+ async function detectType(dataplaneUrl, authConfig, openapiSpec) {
78
166
  const client = new ApiClient(dataplaneUrl, authConfig);
79
167
  return await client.post('/api/v1/wizard/detect-type', {
80
- body: { openApiSpec }
168
+ body: { openapiSpec }
81
169
  });
82
170
  }
83
171
 
@@ -89,11 +177,18 @@ async function detectType(dataplaneUrl, authConfig, openApiSpec) {
89
177
  * @param {string} dataplaneUrl - Dataplane base URL
90
178
  * @param {Object} authConfig - Authentication configuration
91
179
  * @param {Object} config - Generation configuration
92
- * @param {string} config.mode - Wizard mode
93
- * @param {string} config.sourceType - Source type
94
- * @param {Object} [config.openApiSpec] - OpenAPI specification (if applicable)
95
- * @param {string} [config.userIntent] - User intent (e.g., 'sales-focused', 'support-focused')
96
- * @param {Object} [config.preferences] - User preferences
180
+ * @param {Object} config.openapiSpec - OpenAPI specification object (required)
181
+ * @param {string} config.detectedType - Detected API type (required, e.g., 'record-based')
182
+ * @param {string} config.intent - User intent (required, any descriptive text)
183
+ * @param {string} config.mode - Wizard mode (required, 'create-system' | 'add-datasource')
184
+ * @param {string} [config.systemIdOrKey] - Existing system ID/key (required for add-datasource)
185
+ * @param {string} [config.credentialIdOrKey] - Credential ID or key
186
+ * @param {string} [config.fieldOnboardingLevel] - Field onboarding level ('full' | 'standard' | 'minimal')
187
+ * @param {boolean} [config.enableOpenAPIGeneration] - Enable OpenAPI operation generation
188
+ * @param {Object} [config.userPreferences] - User preferences
189
+ * @param {boolean} [config.userPreferences.enableMCP] - Enable MCP
190
+ * @param {boolean} [config.userPreferences.enableABAC] - Enable ABAC
191
+ * @param {boolean} [config.userPreferences.enableRBAC] - Enable RBAC
97
192
  * @returns {Promise<Object>} Generated configuration response
98
193
  * @throws {Error} If request fails
99
194
  */
@@ -104,6 +199,24 @@ async function generateConfig(dataplaneUrl, authConfig, config) {
104
199
  });
105
200
  }
106
201
 
202
+ /**
203
+ * Generate configuration via AI (streaming)
204
+ * POST /api/v1/wizard/generate-config-stream
205
+ * @async
206
+ * @function generateConfigStream
207
+ * @param {string} dataplaneUrl - Dataplane base URL
208
+ * @param {Object} authConfig - Authentication configuration
209
+ * @param {Object} config - Generation configuration payload
210
+ * @returns {Promise<Object>} Streaming generation response
211
+ * @throws {Error} If request fails
212
+ */
213
+ async function generateConfigStream(dataplaneUrl, authConfig, config) {
214
+ const client = new ApiClient(dataplaneUrl, authConfig);
215
+ return await client.post('/api/v1/wizard/generate-config-stream', {
216
+ body: config
217
+ });
218
+ }
219
+
107
220
  /**
108
221
  * Validate wizard configuration
109
222
  * POST /api/v1/wizard/validate
@@ -112,20 +225,69 @@ async function generateConfig(dataplaneUrl, authConfig, config) {
112
225
  * @param {string} dataplaneUrl - Dataplane base URL
113
226
  * @param {Object} authConfig - Authentication configuration
114
227
  * @param {Object} systemConfig - System configuration to validate
115
- * @param {Object[]} datasourceConfigs - Array of datasource configurations to validate
228
+ * @param {Object|Object[]} datasourceConfig - Datasource configuration(s) to validate
116
229
  * @returns {Promise<Object>} Validation response
117
230
  * @throws {Error} If request fails
118
231
  */
119
- async function validateWizardConfig(dataplaneUrl, authConfig, systemConfig, datasourceConfigs) {
232
+ async function validateWizardConfig(dataplaneUrl, authConfig, systemConfig, datasourceConfig) {
120
233
  const client = new ApiClient(dataplaneUrl, authConfig);
121
234
  return await client.post('/api/v1/wizard/validate', {
122
235
  body: {
123
236
  systemConfig,
124
- datasourceConfigs
237
+ datasourceConfig
125
238
  }
126
239
  });
127
240
  }
128
241
 
242
+ /**
243
+ * Validate all completed wizard steps
244
+ * GET /api/v1/wizard/sessions/{sessionId}/validate
245
+ * @async
246
+ * @function validateAllSteps
247
+ * @param {string} dataplaneUrl - Dataplane base URL
248
+ * @param {string} sessionId - Session ID
249
+ * @param {Object} authConfig - Authentication configuration
250
+ * @returns {Promise<Object>} Validation response for all steps
251
+ * @throws {Error} If request fails
252
+ */
253
+ async function validateAllSteps(dataplaneUrl, sessionId, authConfig) {
254
+ const client = new ApiClient(dataplaneUrl, authConfig);
255
+ return await client.get(`/api/v1/wizard/sessions/${sessionId}/validate`);
256
+ }
257
+
258
+ /**
259
+ * Validate specific wizard step
260
+ * POST /api/v1/wizard/sessions/{sessionId}/validate-step
261
+ * @async
262
+ * @function validateStep
263
+ * @param {string} dataplaneUrl - Dataplane base URL
264
+ * @param {string} sessionId - Session ID
265
+ * @param {Object} authConfig - Authentication configuration
266
+ * @param {number} stepNumber - Step number to validate (1-7)
267
+ * @returns {Promise<Object>} Validation response for the step
268
+ * @throws {Error} If request fails
269
+ */
270
+ async function validateStep(dataplaneUrl, sessionId, authConfig, stepNumber) {
271
+ const client = new ApiClient(dataplaneUrl, authConfig);
272
+ return await client.post(`/api/v1/wizard/sessions/${sessionId}/validate-step?step=${stepNumber}`);
273
+ }
274
+
275
+ /**
276
+ * Get configuration preview with summaries
277
+ * GET /api/v1/wizard/preview/{sessionId}
278
+ * @async
279
+ * @function getPreview
280
+ * @param {string} dataplaneUrl - Dataplane base URL
281
+ * @param {string} sessionId - Session ID
282
+ * @param {Object} authConfig - Authentication configuration
283
+ * @returns {Promise<Object>} Preview response with system and datasource summaries
284
+ * @throws {Error} If request fails
285
+ */
286
+ async function getPreview(dataplaneUrl, sessionId, authConfig) {
287
+ const client = new ApiClient(dataplaneUrl, authConfig);
288
+ return await client.get(`/api/v1/wizard/preview/${sessionId}`);
289
+ }
290
+
129
291
  /**
130
292
  * Test MCP server connection
131
293
  * POST /api/v1/wizard/test-mcp-connection
@@ -165,13 +327,20 @@ async function getDeploymentDocs(dataplaneUrl, authConfig, systemKey) {
165
327
  }
166
328
 
167
329
  module.exports = {
168
- selectMode,
169
- selectSource,
330
+ createWizardSession,
331
+ getWizardSession,
332
+ updateWizardSession,
333
+ deleteWizardSession,
334
+ getWizardProgress,
170
335
  parseOpenApi,
336
+ credentialSelection,
171
337
  detectType,
172
338
  generateConfig,
339
+ generateConfigStream,
173
340
  validateWizardConfig,
341
+ validateAllSteps,
342
+ validateStep,
343
+ getPreview,
174
344
  testMcpConnection,
175
345
  getDeploymentDocs
176
346
  };
177
-
package/lib/app/deploy.js CHANGED
@@ -18,6 +18,8 @@ const logger = require('../utils/logger');
18
18
  const config = require('../core/config');
19
19
  const { getDeploymentAuth } = require('../utils/token-manager');
20
20
  const { detectAppType } = require('../utils/paths');
21
+ const { resolveControllerUrl } = require('../utils/controller-url');
22
+ const { checkApplicationExists } = require('../utils/app-existence');
21
23
 
22
24
  /**
23
25
  * Validate application name format
@@ -178,15 +180,26 @@ async function loadVariablesFile(variablesPath) {
178
180
  }
179
181
 
180
182
  /**
181
- * Extracts deployment configuration from options and variables
182
- * @param {Object} options - CLI options
183
- * @param {Object} variables - Variables from variables.yaml
184
- * @returns {Object} Extracted configuration
183
+ * Extracts deployment configuration from config.yaml
184
+ * Resolves controller URL using fallback chain: config.controller → logged-in user → developer ID default
185
+ * Resolves environment using fallback chain: config.environment → default 'dev'
186
+ * @async
187
+ * @param {Object} options - CLI options (for poll settings only)
188
+ * @param {Object} _variables - Variables from variables.yaml (unused, kept for compatibility)
189
+ * @returns {Promise<Object>} Extracted configuration with resolved controller URL
185
190
  */
186
- function extractDeploymentConfig(options, variables) {
191
+ async function extractDeploymentConfig(options, _variables) {
192
+ const { resolveEnvironment } = require('../core/config');
193
+
194
+ // Resolve controller URL from config.yaml (no flags, no options)
195
+ const controllerUrl = await resolveControllerUrl();
196
+
197
+ // Resolve environment from config.yaml (no flags, no options)
198
+ const envKey = await resolveEnvironment();
199
+
187
200
  return {
188
- controllerUrl: options.controller || variables.deployment?.controllerUrl,
189
- envKey: options.environment || variables.deployment?.environment,
201
+ controllerUrl,
202
+ envKey,
190
203
  poll: options.poll !== false,
191
204
  pollInterval: options.pollInterval || 5000,
192
205
  pollMaxAttempts: options.pollMaxAttempts || 60
@@ -200,7 +213,7 @@ function extractDeploymentConfig(options, variables) {
200
213
  */
201
214
  function validateDeploymentConfig(deploymentConfig) {
202
215
  if (!deploymentConfig.controllerUrl) {
203
- throw new Error('Controller URL is required. Set it in variables.yaml or use --controller flag');
216
+ throw new Error('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml');
204
217
  }
205
218
  if (!deploymentConfig.auth) {
206
219
  throw new Error('Authentication is required. Run "aifabrix login" first or ensure credentials are in secrets.local.yaml');
@@ -208,19 +221,15 @@ function validateDeploymentConfig(deploymentConfig) {
208
221
  }
209
222
 
210
223
  /**
211
- * Configure deployment environment settings
224
+ * Configure deployment environment settings from config.yaml
212
225
  * @async
213
- * @param {Object} options - CLI options
226
+ * @param {Object} _options - CLI options (unused, kept for compatibility)
214
227
  * @param {Object} deploymentConfig - Deployment configuration to update
215
228
  * @returns {Promise<void>}
216
229
  */
217
- async function configureDeploymentEnvironment(options, deploymentConfig) {
218
- // Update root-level environment if provided
219
- if (options.environment) {
220
- await config.setCurrentEnvironment(options.environment);
221
- }
222
-
223
- // Get current environment from root-level config
230
+ async function configureDeploymentEnvironment(_options, deploymentConfig) {
231
+ // Get current environment from root-level config (already resolved in extractDeploymentConfig)
232
+ // This function is kept for compatibility but no longer updates environment from options
224
233
  const currentEnvironment = await config.getCurrentEnvironment();
225
234
  deploymentConfig.envKey = deploymentConfig.envKey || currentEnvironment;
226
235
  }
@@ -234,9 +243,9 @@ async function configureDeploymentEnvironment(options, deploymentConfig) {
234
243
  * @throws {Error} If authentication fails
235
244
  */
236
245
  async function refreshDeploymentToken(appName, deploymentConfig) {
237
- // Get controller URL
246
+ // Get controller URL (should already be resolved by extractDeploymentConfig)
238
247
  if (!deploymentConfig.controllerUrl) {
239
- throw new Error('Controller URL is required. Set it in variables.yaml or use --controller flag');
248
+ throw new Error('Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml');
240
249
  }
241
250
 
242
251
  // Get deployment authentication (device token → client token → credentials)
@@ -275,7 +284,7 @@ async function loadDeploymentConfig(appName, options) {
275
284
  const variablesPath = path.join(appPath, 'variables.yaml');
276
285
  const variables = await loadVariablesFile(variablesPath);
277
286
 
278
- const deploymentConfig = extractDeploymentConfig(options, variables);
287
+ const deploymentConfig = await extractDeploymentConfig(options, variables);
279
288
 
280
289
  await configureDeploymentEnvironment(options, deploymentConfig);
281
290
  await refreshDeploymentToken(appName, deploymentConfig);
@@ -360,6 +369,80 @@ function displayDeploymentResults(result) {
360
369
  }
361
370
  }
362
371
 
372
+ /**
373
+ * Check if app is external and handle external deployment
374
+ * @async
375
+ * @function handleExternalDeployment
376
+ * @param {string} appName - Application name
377
+ * @param {Object} options - Deployment options
378
+ * @returns {Promise<Object|null>} Deployment result if external, null otherwise
379
+ */
380
+ async function handleExternalDeployment(appName, options) {
381
+ const { isExternal } = await detectAppType(appName);
382
+ if (isExternal) {
383
+ const externalDeploy = require('../external-system/deploy');
384
+ await externalDeploy.deployExternalSystem(appName, options);
385
+ return { success: true, type: 'external' };
386
+ }
387
+ return null;
388
+ }
389
+
390
+ /**
391
+ * Handle deployment errors
392
+ * @async
393
+ * @function handleDeploymentError
394
+ * @param {Error} error - Error that occurred
395
+ * @param {string} appName - Application name
396
+ * @param {string} controllerUrl - Controller URL (from config, or null if not yet resolved)
397
+ * @param {boolean} usedExternalDeploy - Whether external deployment was used
398
+ */
399
+ async function handleDeploymentError(error, appName, controllerUrl, usedExternalDeploy) {
400
+ if (usedExternalDeploy) {
401
+ throw error;
402
+ }
403
+ const alreadyLogged = error._logged === true;
404
+ const url = controllerUrl || 'unknown';
405
+ const deployer = require('../deployment/deployer');
406
+ await deployer.handleDeploymentErrors(error, appName, url, alreadyLogged);
407
+ }
408
+
409
+ /**
410
+ * Execute standard application deployment flow
411
+ * @async
412
+ * @function executeStandardDeployment
413
+ * @param {string} appName - Application name
414
+ * @param {Object} options - Deployment options
415
+ * @returns {Promise<Object>} Deployment result
416
+ */
417
+ async function executeStandardDeployment(appName, options) {
418
+ const config = await loadDeploymentConfig(appName, options);
419
+ const controllerUrl = config.controllerUrl || 'unknown';
420
+
421
+ // Check if application exists before deployment
422
+ const appExists = await checkApplicationExists(appName, controllerUrl, config.envKey, config.auth);
423
+
424
+ const { manifest, manifestPath } = await generateAndValidateManifest(appName);
425
+ displayDeploymentInfo(manifest, manifestPath);
426
+
427
+ try {
428
+ const result = await executeDeployment(manifest, config);
429
+ displayDeploymentResults(result);
430
+ return { result, controllerUrl, appExists };
431
+ } catch (error) {
432
+ // Enhance error if app exists and credentials are invalid
433
+ if (appExists && error.status === 401 && !error.message.includes('rotate-secret')) {
434
+ const enhancedError = new Error(
435
+ `${error.message}\n\n💡 The application '${appName}' exists in environment '${config.envKey}'. ` +
436
+ `To fix invalid credentials, rotate the application secret:\n aifabrix app rotate-secret ${appName}`
437
+ );
438
+ enhancedError.status = 401;
439
+ enhancedError.formatted = error.formatted || enhancedError.message;
440
+ throw enhancedError;
441
+ }
442
+ throw error;
443
+ }
444
+ }
445
+
363
446
  /**
364
447
  * Deploys application to Miso Controller
365
448
  * Orchestrates manifest generation, key creation, and deployment
@@ -368,59 +451,38 @@ function displayDeploymentResults(result) {
368
451
  * @function deployApp
369
452
  * @param {string} appName - Name of the application to deploy
370
453
  * @param {Object} options - Deployment options
371
- * @param {string} options.controller - Controller URL (required)
372
- * @param {string} [options.environment] - Target environment (miso/dev/tst/pro)
373
454
  * @param {boolean} [options.poll] - Poll for deployment status
374
455
  * @param {number} [options.pollInterval] - Polling interval in milliseconds
456
+ * @param {number} [options.pollMaxAttempts] - Max polling attempts
375
457
  * @returns {Promise<Object>} Deployment result
376
458
  * @throws {Error} If deployment fails
377
459
  *
460
+ * Controller and environment come from config.yaml (set via aifabrix login or aifabrix auth config).
461
+ *
378
462
  * @example
379
- * await deployApp('myapp', { controller: 'https://controller.aifabrix.ai', environment: 'dev' });
463
+ * await deployApp('myapp', { poll: true });
380
464
  */
381
465
  async function deployApp(appName, options = {}) {
382
466
  let controllerUrl = null;
383
- let config = null;
467
+ let usedExternalDeploy = false;
384
468
 
385
469
  try {
386
- // 1. Input validation
387
470
  if (!appName || typeof appName !== 'string' || appName.trim().length === 0) {
388
471
  throw new Error('App name is required');
389
472
  }
390
-
391
473
  validateAppName(appName);
392
474
 
393
- // 2. Check if app type is external - use normal deployment flow with application-schema.json
394
- // External systems now deploy via miso controller as normal application (full application file)
395
- // The json command generates application-schema.json which is used for deployment
396
-
397
- // 2. Load deployment configuration
398
- config = await loadDeploymentConfig(appName, options);
399
- controllerUrl = config.controllerUrl || options.controller || 'unknown';
400
-
401
- // 3. Generate and validate manifest
402
- const { manifest, manifestPath } = await generateAndValidateManifest(appName);
403
-
404
- // 4. Display deployment info
405
- displayDeploymentInfo(manifest, manifestPath);
406
-
407
- // 5. Execute deployment
408
- const result = await executeDeployment(manifest, config);
409
-
410
- // 6. Display results
411
- displayDeploymentResults(result);
475
+ const externalResult = await handleExternalDeployment(appName, options);
476
+ if (externalResult) {
477
+ return externalResult;
478
+ }
479
+ usedExternalDeploy = false;
412
480
 
481
+ const { result, controllerUrl: url } = await executeStandardDeployment(appName, options);
482
+ controllerUrl = url;
413
483
  return result;
414
-
415
484
  } catch (error) {
416
- // Use unified error handler from deployer
417
- // Check if error was already logged (from deployer.js)
418
- const alreadyLogged = error._logged === true;
419
- const url = controllerUrl || options.controller || 'unknown';
420
-
421
- const deployer = require('../deployment/deployer');
422
- // handleDeploymentErrors will log, format, and throw the error
423
- await deployer.handleDeploymentErrors(error, appName, url, alreadyLogged);
485
+ await handleDeploymentError(error, appName, controllerUrl, usedExternalDeploy);
424
486
  }
425
487
  }
426
488
 
@@ -25,9 +25,9 @@ function displayExternalSystemSuccess(appName, config, location) {
25
25
  logger.log(chalk.blue(`System Key: ${config.systemKey || appName}`));
26
26
  logger.log(chalk.green('\nNext steps:'));
27
27
  logger.log(chalk.white('1. Edit external system JSON files in ' + location));
28
- logger.log(chalk.white('2. Run: aifabrix app register ' + appName + ' --environment dev'));
29
- logger.log(chalk.white('3. Run: aifabrix build ' + appName + ' (deploys to dataplane)'));
30
- logger.log(chalk.white('4. Run: aifabrix deploy ' + appName + ' (publishes to dataplane)'));
28
+ logger.log(chalk.white('2. Run: aifabrix validate ' + appName + ' --type external'));
29
+ logger.log(chalk.white('3. Run: aifabrix login'));
30
+ logger.log(chalk.white('4. Run: aifabrix deploy ' + appName));
31
31
  }
32
32
 
33
33
  /**
@@ -50,8 +50,9 @@ function displayWebappSuccess(appName, config, envConversionMessage) {
50
50
 
51
51
  logger.log(chalk.green('\nNext steps:'));
52
52
  logger.log(chalk.white('1. Copy env.template to .env and fill in your values'));
53
- logger.log(chalk.white('2. Run: aifabrix build ' + appName));
54
- logger.log(chalk.white('3. Run: aifabrix run ' + appName));
53
+ logger.log(chalk.white('2. Run: aifabrix up'));
54
+ logger.log(chalk.white('3. Run: aifabrix build ' + appName));
55
+ logger.log(chalk.white('4. Run: aifabrix run ' + appName));
55
56
  }
56
57
 
57
58
  /**
@@ -15,6 +15,7 @@ const yaml = require('js-yaml');
15
15
  const build = require('../build');
16
16
  const { validateAppName } = require('./push');
17
17
  const logger = require('../utils/logger');
18
+ const { getContainerPort } = require('../utils/port-resolver');
18
19
 
19
20
  /**
20
21
  * Checks if Dockerfile exists and validates overwrite permission
@@ -52,7 +53,7 @@ async function loadAppConfig(configPath, options) {
52
53
  const variables = yaml.load(yamlContent);
53
54
  return {
54
55
  language: options.language || variables.build?.language || 'typescript',
55
- port: variables.build?.port || variables.port || 3000,
56
+ port: getContainerPort(variables, 3000),
56
57
  ...variables
57
58
  };
58
59
  } catch {