@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,431 +1,107 @@
1
1
  /**
2
2
  * External System Deployment Module
3
3
  *
4
- * Handles deployment of external systems and datasources via pipeline API
5
- * for external type applications.
4
+ * Handles deployment of external systems via controller pipeline.
5
+ * Uses unified controller pipeline (same as regular apps) - no direct dataplane calls.
6
6
  *
7
7
  * @fileoverview External system deployment for AI Fabrix Builder
8
8
  * @author AI Fabrix Team
9
9
  * @version 2.0.0
10
10
  */
11
11
 
12
- const fs = require('fs').promises;
13
- const path = require('path');
14
12
  const chalk = require('chalk');
15
- const {
16
- deployExternalSystemViaPipeline,
17
- deployDatasourceViaPipeline,
18
- uploadApplicationViaPipeline,
19
- validateUploadViaPipeline,
20
- publishUploadViaPipeline
21
- } = require('../api/pipeline.api');
22
13
  const { getDeploymentAuth } = require('../utils/token-manager');
23
- const { getConfig } = require('../core/config');
24
14
  const logger = require('../utils/logger');
25
- const { getDataplaneUrl } = require('../datasource/deploy');
26
- const { detectAppType } = require('../utils/paths');
27
- const { generateExternalSystemApplicationSchema } = require('../generator/external');
28
- const {
29
- loadVariablesYaml,
30
- validateSystemFiles,
31
- validateDatasourceFiles,
32
- extractSystemKey
33
- } = require('./deploy-helpers');
15
+ const { resolveControllerUrl } = require('../utils/controller-url');
16
+ const { generateControllerManifest } = require('../generator/external-controller-manifest');
17
+ const { validateExternalSystemComplete } = require('../validation/validate');
18
+ const { displayValidationResults } = require('../validation/validate-display');
34
19
 
35
20
  /**
36
- * Loads variables.yaml for an application
37
- * @async
38
- * @function loadVariablesYaml
39
- * @param {string} appName - Application name
40
- * @returns {Promise<Object>} Variables configuration
41
- * @throws {Error} If file cannot be loaded
42
- */
43
- /**
44
- * Validates external system files exist
45
- * @async
46
- * @function validateExternalSystemFiles
47
- * @param {string} appName - Application name
48
- * @returns {Promise<Object>} Validation result with file paths
49
- * @throws {Error} If validation fails
50
- */
51
-
52
- async function validateExternalSystemFiles(appName) {
53
- const variables = await loadVariablesYaml(appName);
54
-
55
- if (!variables.externalIntegration) {
56
- throw new Error('externalIntegration block not found in variables.yaml');
57
- }
58
-
59
- // Detect app type and get correct path (integration or builder)
60
- const { appPath } = await detectAppType(appName);
61
-
62
- // For new structure, files are in same folder (schemaBasePath is usually './')
63
- // For backward compatibility, support old schemas/ subfolder
64
- const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
65
- const schemasPath = path.isAbsolute(schemaBasePath)
66
- ? schemaBasePath
67
- : path.join(appPath, schemaBasePath);
68
-
69
- // Validate system files
70
- const systemFilesList = variables.externalIntegration.systems || [];
71
- if (systemFilesList.length === 0) {
72
- throw new Error('No external system files specified in externalIntegration.systems');
73
- }
74
- const systemFiles = await validateSystemFiles(systemFilesList, appName, schemasPath);
75
-
76
- // Validate datasource files
77
- const datasourceFilesList = variables.externalIntegration.dataSources || [];
78
- const datasourceFiles = await validateDatasourceFiles(datasourceFilesList, appPath, schemasPath);
79
-
80
- // Extract systemKey from system file
81
- const systemKey = extractSystemKey(systemFiles[0]);
82
-
83
- return {
84
- systemFiles,
85
- datasourceFiles,
86
- systemKey
87
- };
88
- }
89
-
90
- /**
91
- * Deploys external system to dataplane (build step - deploy, not publish)
92
- * @async
93
- * @function buildExternalSystem
94
- * @param {string} appName - Application name
95
- * @param {Object} options - Deployment options
96
- * @returns {Promise<void>} Resolves when deployment completes
97
- * @throws {Error} If deployment fails
98
- */
99
- /**
100
- * Validates and prepares deployment configuration
21
+ * Prepares deployment configuration (auth, controller URL, environment)
101
22
  * @async
102
23
  * @function prepareDeploymentConfig
103
24
  * @param {string} appName - Application name
104
25
  * @param {Object} options - Deployment options
105
26
  * @returns {Promise<Object>} Deployment configuration
106
27
  */
107
- async function prepareDeploymentConfig(appName, options) {
108
- const { systemFiles, datasourceFiles, systemKey } = await validateExternalSystemFiles(appName);
109
-
110
- const config = await getConfig();
111
- const environment = options.environment || 'dev';
112
- const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
113
- const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
114
-
115
- if (!authConfig.token && !authConfig.clientId) {
116
- throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
117
- }
118
-
119
- return { systemFiles, datasourceFiles, systemKey, environment, controllerUrl, authConfig };
120
- }
121
-
122
- /**
123
- * Gets dataplane URL from controller
124
- * @async
125
- * @function getDataplaneUrlForDeployment
126
- * @param {string} controllerUrl - Controller URL
127
- * @param {string} appName - Application name
128
- * @param {string} environment - Environment key
129
- * @param {Object} authConfig - Authentication configuration
130
- * @returns {Promise<string>} Dataplane URL
131
- */
132
- async function getDataplaneUrlForDeployment(controllerUrl, appName, environment, authConfig) {
133
- logger.log(chalk.blue('🌐 Getting dataplane URL from controller...'));
134
- const dataplaneUrl = await getDataplaneUrl(controllerUrl, appName, environment, authConfig);
135
- logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
136
- return dataplaneUrl;
137
- }
138
-
139
- /**
140
- * Deploys external system via pipeline
141
- * @async
142
- * @function deploySystem
143
- * @param {string} dataplaneUrl - Dataplane URL
144
- * @param {Object} authConfig - Authentication configuration
145
- * @param {string} systemFilePath - Path to system file
146
- * @param {string} systemKey - System key
147
- * @returns {Promise<void>}
148
- */
149
- async function deploySystem(dataplaneUrl, authConfig, systemFilePath, systemKey) {
150
- logger.log(chalk.blue(`Deploying external system: ${systemKey}...`));
151
- const systemContent = await fs.readFile(systemFilePath, 'utf8');
152
- const systemJson = JSON.parse(systemContent);
153
-
154
- const systemResponse = await deployExternalSystemViaPipeline(dataplaneUrl, authConfig, systemJson);
155
-
156
- if (!systemResponse.success) {
157
- throw new Error(`Failed to deploy external system: ${systemResponse.error || systemResponse.formattedError}`);
158
- }
159
-
160
- logger.log(chalk.green(`✓ External system deployed: ${systemKey}`));
161
- }
162
-
163
- /**
164
- * Deploys a single datasource via pipeline
165
- * @async
166
- * @function deploySingleDatasource
167
- * @param {string} dataplaneUrl - Dataplane URL
168
- * @param {string} systemKey - System key
169
- * @param {Object} authConfig - Authentication configuration
170
- * @param {string} datasourceFile - Path to datasource file
171
- * @returns {Promise<void>}
172
- */
173
- async function deploySingleDatasource(dataplaneUrl, systemKey, authConfig, datasourceFile) {
174
- const datasourceName = path.basename(datasourceFile, '.json');
175
- logger.log(chalk.blue(`Deploying datasource: ${datasourceName}...`));
176
-
177
- const datasourceContent = await fs.readFile(datasourceFile, 'utf8');
178
- const datasourceJson = JSON.parse(datasourceContent);
179
-
180
- const datasourceResponse = await deployDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig, datasourceJson);
181
-
182
- if (!datasourceResponse.success) {
183
- throw new Error(`Failed to deploy datasource ${datasourceName}: ${datasourceResponse.error || datasourceResponse.formattedError}`);
184
- }
185
-
186
- logger.log(chalk.green(`✓ Datasource deployed: ${datasourceName}`));
187
- }
188
-
189
- /**
190
- * Deploys all datasources
191
- * @async
192
- * @function deployAllDatasources
193
- * @param {string} dataplaneUrl - Dataplane URL
194
- * @param {string} systemKey - System key
195
- * @param {Object} authConfig - Authentication configuration
196
- * @param {string[]} datasourceFiles - Array of datasource file paths
197
- * @returns {Promise<void>}
198
- */
199
- async function deployAllDatasources(dataplaneUrl, systemKey, authConfig, datasourceFiles) {
200
- for (const datasourceFile of datasourceFiles) {
201
- await deploySingleDatasource(dataplaneUrl, systemKey, authConfig, datasourceFile);
202
- }
203
- }
204
-
205
- async function buildExternalSystem(appName, options = {}) {
206
- try {
207
- logger.log(chalk.blue(`\n🔨 Building external system: ${appName}`));
208
-
209
- const { systemFiles, datasourceFiles, systemKey, environment, controllerUrl, authConfig } = await prepareDeploymentConfig(appName, options);
210
- const dataplaneUrl = await getDataplaneUrlForDeployment(controllerUrl, appName, environment, authConfig);
211
-
212
- await deploySystem(dataplaneUrl, authConfig, systemFiles[0], systemKey);
213
- await deployAllDatasources(dataplaneUrl, systemKey, authConfig, datasourceFiles);
214
-
215
- logger.log(chalk.green('\n✅ External system built successfully!'));
216
- logger.log(chalk.blue(`System: ${systemKey}`));
217
- logger.log(chalk.blue(`Datasources: ${datasourceFiles.length}`));
218
- } catch (error) {
219
- throw new Error(`Failed to build external system: ${error.message}`);
220
- }
221
- }
222
-
223
- /**
224
- * Validate deployment prerequisites
225
- * @async
226
- * @param {string} appName - Application name
227
- * @returns {Promise<Object>} Validation result with systemFiles, datasourceFiles, and systemKey
228
- */
229
- async function validateDeploymentPrerequisites(appName) {
230
- const { systemFiles: _systemFiles, datasourceFiles, systemKey } = await validateExternalSystemFiles(appName);
231
- return { systemFiles: _systemFiles, datasourceFiles, systemKey };
232
- }
233
-
234
- /**
235
- * Prepare deployment files and get authentication
236
- * @async
237
- * @param {string} appName - Application name
238
- * @param {Object} options - Deployment options
239
- * @returns {Promise<Object>} Object with applicationSchema, authConfig, controllerUrl, environment, and systemKey
240
- */
241
- async function prepareDeploymentFiles(appName, options) {
242
- logger.log(chalk.blue('📋 Generating application schema...'));
243
- const applicationSchema = await generateExternalSystemApplicationSchema(appName);
244
- logger.log(chalk.green('✓ Application schema generated'));
245
-
246
- const config = await getConfig();
247
- const environment = options.environment || 'dev';
248
- const controllerUrl = options.controller || config.deployment?.controllerUrl || 'http://localhost:3000';
28
+ async function prepareDeploymentConfig(appName, _options) {
29
+ const { resolveEnvironment } = require('../core/config');
30
+ const environment = await resolveEnvironment();
31
+ const controllerUrl = await resolveControllerUrl();
249
32
  const authConfig = await getDeploymentAuth(controllerUrl, environment, appName);
250
33
 
251
34
  if (!authConfig.token && !authConfig.clientId) {
252
35
  throw new Error('Authentication required. Run "aifabrix login" or "aifabrix app register" first.');
253
36
  }
254
37
 
255
- const { systemKey } = await validateDeploymentPrerequisites(appName);
256
-
257
- return { applicationSchema, authConfig, controllerUrl, environment, systemKey };
258
- }
259
-
260
- /**
261
- * Upload application and get upload ID
262
- * @async
263
- * @param {string} dataplaneUrl - Dataplane URL
264
- * @param {Object} authConfig - Authentication configuration
265
- * @param {Object} applicationSchema - Application schema
266
- * @returns {Promise<string>} Upload ID
267
- * @throws {Error} If upload fails
268
- */
269
- async function uploadApplication(dataplaneUrl, authConfig, applicationSchema) {
270
- logger.log(chalk.blue('📤 Uploading application configuration...'));
271
- const uploadResponse = await uploadApplicationViaPipeline(dataplaneUrl, authConfig, applicationSchema);
272
-
273
- if (!uploadResponse.success || !uploadResponse.data) {
274
- throw new Error(`Failed to upload application: ${uploadResponse.error || uploadResponse.formattedError || 'Unknown error'}`);
275
- }
276
-
277
- const uploadData = uploadResponse.data.data || uploadResponse.data;
278
- const uploadId = uploadData.uploadId || uploadData.id;
279
-
280
- if (!uploadId) {
281
- throw new Error('Upload ID not found in upload response');
282
- }
283
-
284
- logger.log(chalk.green(`✓ Upload successful (ID: ${uploadId})`));
285
- return uploadId;
286
- }
287
-
288
- /**
289
- * Validate upload and display changes
290
- * @async
291
- * @param {string} dataplaneUrl - Dataplane URL
292
- * @param {string} uploadId - Upload ID
293
- * @param {Object} authConfig - Authentication configuration
294
- * @returns {Promise<void>}
295
- * @throws {Error} If validation fails
296
- */
297
- /**
298
- * Displays validation changes
299
- * @function displayValidationChanges
300
- * @param {Object[]} changes - Array of changes
301
- */
302
- function displayValidationChanges(changes) {
303
- if (changes && changes.length > 0) {
304
- logger.log(chalk.blue('\n📋 Changes to be published:'));
305
- for (const change of changes) {
306
- const changeType = change.type || 'unknown';
307
- const changeEntity = change.entity || change.key || 'unknown';
308
- const emoji = changeType === 'new' ? '➕' : changeType === 'modified' ? '✏️' : '🗑️';
309
- logger.log(chalk.gray(` ${emoji} ${changeType}: ${changeEntity}`));
310
- }
311
- }
312
- }
313
-
314
- /**
315
- * Validates upload response
316
- * @function validateUploadResponse
317
- * @param {Object} validateResponse - Validation response
318
- * @returns {Object} Validation data
319
- * @throws {Error} If validation failed
320
- */
321
- function validateUploadResponse(validateResponse) {
322
- if (!validateResponse.success || !validateResponse.data) {
323
- throw new Error(`Validation failed: ${validateResponse.error || validateResponse.formattedError || 'Unknown error'}`);
324
- }
325
-
326
- return validateResponse.data.data || validateResponse.data;
327
- }
328
-
329
- async function validateUpload(dataplaneUrl, uploadId, authConfig) {
330
- logger.log(chalk.blue('🔍 Validating upload...'));
331
- const validateResponse = await validateUploadViaPipeline(dataplaneUrl, uploadId, authConfig);
332
-
333
- const validateData = validateUploadResponse(validateResponse);
334
-
335
- displayValidationChanges(validateData.changes);
336
-
337
- if (validateData.summary) {
338
- logger.log(chalk.blue(`\n📊 Summary: ${validateData.summary}`));
339
- }
340
-
341
- logger.log(chalk.green('✓ Validation successful'));
38
+ return { environment, controllerUrl, authConfig };
342
39
  }
343
40
 
344
41
  /**
345
- * Publish application
346
- * @async
347
- * @param {string} dataplaneUrl - Dataplane URL
348
- * @param {string} uploadId - Upload ID
349
- * @param {Object} authConfig - Authentication configuration
350
- * @param {Object} options - Publish options
351
- * @param {boolean} [options.generateMcpContract] - Generate MCP contract (default: true)
352
- * @returns {Promise<Object>} Publish response data
353
- * @throws {Error} If publish fails
354
- */
355
- async function publishApplication(dataplaneUrl, uploadId, authConfig, options) {
356
- const generateMcpContract = options.generateMcpContract !== false; // Default to true
357
- logger.log(chalk.blue(`📢 Publishing application (MCP contract: ${generateMcpContract ? 'enabled' : 'disabled'})...`));
358
-
359
- const publishResponse = await publishUploadViaPipeline(dataplaneUrl, uploadId, authConfig, { generateMcpContract });
360
-
361
- if (!publishResponse.success || !publishResponse.data) {
362
- throw new Error(`Failed to publish application: ${publishResponse.error || publishResponse.formattedError || 'Unknown error'}`);
363
- }
364
-
365
- return publishResponse.data.data || publishResponse.data;
366
- }
367
-
368
- /**
369
- * Publishes external system to dataplane using application-level workflow
370
- * Uses upload → validate → publish workflow for atomic deployment
42
+ * Deploys external system via controller pipeline (same as regular apps)
43
+ * Uses unified controller pipeline - no direct dataplane calls
44
+ *
371
45
  * @async
372
46
  * @function deployExternalSystem
373
47
  * @param {string} appName - Application name
374
48
  * @param {Object} options - Deployment options
375
49
  * @param {string} [options.environment] - Environment (dev, tst, pro)
376
50
  * @param {string} [options.controller] - Controller URL
377
- * @param {boolean} [options.skipValidation] - Skip validation step and go straight to publish
378
- * @param {boolean} [options.generateMcpContract] - Generate MCP contract (default: true)
379
- * @returns {Promise<void>} Resolves when deployment completes
51
+ * @param {boolean} [options.poll] - Poll for deployment status
52
+ * @param {number} [options.pollInterval] - Polling interval in milliseconds (default: 500ms for external systems)
53
+ * @returns {Promise<Object>} Deployment result
380
54
  * @throws {Error} If deployment fails
381
55
  */
382
56
  async function deployExternalSystem(appName, options = {}) {
383
57
  try {
384
- logger.log(chalk.blue(`\n🚀 Publishing external system: ${appName}`));
385
-
386
- // Validate prerequisites
387
- const { datasourceFiles } = await validateDeploymentPrerequisites(appName);
388
-
389
- // Prepare deployment files and get authentication
390
- const { applicationSchema, authConfig, controllerUrl, environment, systemKey } = await prepareDeploymentFiles(appName, options);
58
+ logger.log(chalk.blue(`\n🚀 Deploying external system: ${appName}`));
391
59
 
392
- // Get dataplane URL from controller
393
- logger.log(chalk.blue('🌐 Getting dataplane URL from controller...'));
394
- const dataplaneUrl = await getDataplaneUrl(controllerUrl, appName, environment, authConfig);
395
- logger.log(chalk.green(`✓ Dataplane URL: ${dataplaneUrl}`));
60
+ // Step 0: Validate before deployment (same as validate command)
61
+ logger.log(chalk.blue('🔍 Validating external system before deployment...'));
62
+ const validationResult = await validateExternalSystemComplete(appName);
396
63
 
397
- // Step 1: Upload application
398
- const uploadId = await uploadApplication(dataplaneUrl, authConfig, applicationSchema);
399
-
400
- // Step 2: Validate upload (optional, can be skipped)
401
- if (!options.skipValidation) {
402
- await validateUpload(dataplaneUrl, uploadId, authConfig);
403
- } else {
404
- logger.log(chalk.yellow('⚠ Skipping validation step'));
64
+ if (!validationResult.valid) {
65
+ displayValidationResults(validationResult);
66
+ throw new Error('Validation failed. Fix errors before deploying.');
405
67
  }
406
68
 
407
- // Step 3: Publish application
408
- const publishData = await publishApplication(dataplaneUrl, uploadId, authConfig, options);
69
+ logger.log(chalk.green('✓ Validation passed, proceeding with deployment...'));
70
+
71
+ // Step 1: Generate controller manifest (validated, ready for deployment)
72
+ const manifest = await generateControllerManifest(appName);
73
+
74
+ // Step 2: Get deployment configuration (auth, controller URL, etc.)
75
+ const { environment, controllerUrl, authConfig } = await prepareDeploymentConfig(appName, options);
76
+
77
+ // Step 3: Deploy via controller pipeline (same as regular apps)
78
+ // Use 500ms polling for external systems (faster than web apps which use 5000ms)
79
+ const deployer = require('../deployment/deployer');
80
+ const result = await deployer.deployToController(
81
+ manifest,
82
+ controllerUrl,
83
+ environment,
84
+ authConfig,
85
+ {
86
+ poll: options.poll,
87
+ pollInterval: options.pollInterval !== undefined ? options.pollInterval : 500,
88
+ pollMaxAttempts: options.pollMaxAttempts,
89
+ ...options
90
+ }
91
+ );
409
92
 
410
93
  // Display success summary
411
- logger.log(chalk.green('\n✅ External system published successfully!'));
412
- logger.log(chalk.blue(`System: ${systemKey}`));
413
- if (publishData.systems && publishData.systems.length > 0) {
414
- logger.log(chalk.blue(`Published systems: ${publishData.systems.length}`));
415
- }
416
- if (publishData.dataSources && publishData.dataSources.length > 0) {
417
- logger.log(chalk.blue(`Published datasources: ${publishData.dataSources.length}`));
418
- } else {
419
- logger.log(chalk.blue(`Datasources: ${datasourceFiles.length}`));
420
- }
94
+ logger.log(chalk.green('\n✅ External system deployed successfully!'));
95
+ logger.log(chalk.blue(`System: ${manifest.key}`));
96
+ logger.log(chalk.blue(`Datasources: ${manifest.dataSources.length}`));
97
+
98
+ return result;
421
99
  } catch (error) {
422
100
  throw new Error(`Failed to deploy external system: ${error.message}`);
423
101
  }
424
102
  }
425
103
 
426
104
  module.exports = {
427
- buildExternalSystem,
428
- deployExternalSystem,
429
- validateExternalSystemFiles
105
+ deployExternalSystem
430
106
  };
431
107
 
@@ -8,6 +8,8 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
+ const { generateExternalReadmeContent } = require('../utils/external-readme');
12
+
11
13
  /**
12
14
  * Generates variables.yaml content for downloaded system
13
15
  * @param {string} systemKey - System key
@@ -16,17 +18,31 @@
16
18
  * @returns {Object} Variables YAML object
17
19
  */
18
20
  function generateVariablesYaml(systemKey, application, dataSources) {
19
- const systemFileName = `${systemKey}-deploy.json`;
21
+ const systemFileName = `${systemKey}-system.json`;
20
22
  const datasourceFiles = dataSources.map(ds => {
21
- // Extract entity type from datasource key or use entityType/entityKey (backward compatibility)
22
- const entityType = ds.entityType || ds.entityKey || ds.key.split('-').pop();
23
- return `${systemKey}-deploy-${entityType}.json`;
23
+ // Extract datasource key (remove system key prefix if present)
24
+ const datasourceKey = ds.key || '';
25
+ let datasourceKeyOnly;
26
+ if (datasourceKey.startsWith(`${systemKey}-`)) {
27
+ datasourceKeyOnly = datasourceKey.substring(systemKey.length + 1);
28
+ } else {
29
+ const entityType = ds.entityType || ds.entityKey || datasourceKey.split('-').pop();
30
+ datasourceKeyOnly = entityType;
31
+ }
32
+ return `${systemKey}-datasource-${datasourceKeyOnly}.json`;
24
33
  });
25
34
 
26
35
  return {
27
- name: systemKey,
28
- displayName: application.displayName || systemKey,
29
- description: application.description || `External system integration for ${systemKey}`,
36
+ app: {
37
+ key: systemKey,
38
+ displayName: application.displayName || systemKey,
39
+ description: application.description || `External system integration for ${systemKey}`,
40
+ type: 'external'
41
+ },
42
+ deployment: {
43
+ controllerUrl: '',
44
+ environment: 'dev'
45
+ },
30
46
  externalIntegration: {
31
47
  schemaBasePath: './',
32
48
  systems: [systemFileName],
@@ -45,66 +61,30 @@ function generateVariablesYaml(systemKey, application, dataSources) {
45
61
  * @returns {string} README.md content
46
62
  */
47
63
  function generateReadme(systemKey, application, dataSources) {
48
- const displayName = application.displayName || systemKey;
49
- const description = application.description || `External system integration for ${systemKey}`;
50
- const systemType = application.type || 'unknown';
51
-
52
- const lines = [
53
- `# ${displayName}`,
54
- '',
55
- description,
56
- '',
57
- '## System Information',
58
- '',
59
- `- **System Key**: \`${systemKey}\``,
60
- `- **System Type**: \`${systemType}\``,
61
- `- **Datasources**: ${dataSources.length}`,
62
- '',
63
- '## Files',
64
- '',
65
- '- `variables.yaml` - Application configuration with externalIntegration block',
66
- `- \`${systemKey}-deploy.json\` - External system definition`
67
- ];
68
-
69
- dataSources.forEach(ds => {
70
- const entityType = ds.entityType || ds.entityKey || ds.key.split('-').pop();
71
- lines.push(`- \`${systemKey}-deploy-${entityType}.json\` - Datasource: ${ds.displayName || ds.key}`);
64
+ const datasources = (Array.isArray(dataSources) ? dataSources : []).map((ds, index) => {
65
+ const datasourceKey = ds.key || '';
66
+ let datasourceKeyOnly;
67
+ if (datasourceKey.startsWith(`${systemKey}-`)) {
68
+ datasourceKeyOnly = datasourceKey.substring(systemKey.length + 1);
69
+ } else {
70
+ const entityType = ds.entityType || ds.entityKey || datasourceKey.split('-').pop() || `entity${index + 1}`;
71
+ datasourceKeyOnly = entityType;
72
+ }
73
+ return {
74
+ entityType: datasourceKeyOnly,
75
+ displayName: ds.displayName || ds.name || ds.key || `Datasource ${index + 1}`,
76
+ fileName: `${systemKey}-datasource-${datasourceKeyOnly}.json`
77
+ };
72
78
  });
73
79
 
74
- lines.push(
75
- '- `env.template` - Environment variables template',
76
- '',
77
- '## Setup Instructions',
78
- '',
79
- '1. Review and update configuration files as needed',
80
- '2. Set up environment variables in `env.template`',
81
- '3. Run unit tests: `aifabrix test ${systemKey}`',
82
- '4. Run integration tests: `aifabrix test-integration ${systemKey}`',
83
- '5. Deploy: `aifabrix deploy ${systemKey} --environment dev`',
84
- '',
85
- '## Testing',
86
- '',
87
- '### Unit Tests',
88
- 'Run local validation without API calls:',
89
- '```bash',
90
- `aifabrix test ${systemKey}`,
91
- '```',
92
- '',
93
- '### Integration Tests',
94
- 'Run integration tests via dataplane:',
95
- '```bash',
96
- `aifabrix test-integration ${systemKey} --environment dev`,
97
- '```',
98
- '',
99
- '## Deployment',
100
- '',
101
- 'Deploy to dataplane via miso-controller:',
102
- '```bash',
103
- `aifabrix deploy ${systemKey} --environment dev`,
104
- '```'
105
- );
106
-
107
- return lines.join('\n');
80
+ return generateExternalReadmeContent({
81
+ appName: systemKey,
82
+ systemKey,
83
+ systemType: application.type,
84
+ displayName: application.displayName,
85
+ description: application.description,
86
+ datasources
87
+ });
108
88
  }
109
89
 
110
90
  module.exports = {