@aifabrix/builder 2.32.3 → 2.33.1

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