@aifabrix/builder 2.36.2 → 2.37.5

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 (43) hide show
  1. package/.cursor/rules/project-rules.mdc +19 -0
  2. package/README.md +68 -104
  3. package/integration/hubspot/test.js +1 -1
  4. package/lib/api/wizard.api.js +24 -1
  5. package/lib/app/deploy.js +43 -7
  6. package/lib/app/display.js +1 -1
  7. package/lib/app/list.js +3 -1
  8. package/lib/app/run-helpers.js +1 -1
  9. package/lib/build/index.js +3 -4
  10. package/lib/cli/index.js +45 -0
  11. package/lib/cli/setup-app.js +230 -0
  12. package/lib/cli/setup-auth.js +88 -0
  13. package/lib/cli/setup-dev.js +101 -0
  14. package/lib/cli/setup-environment.js +53 -0
  15. package/lib/cli/setup-external-system.js +87 -0
  16. package/lib/cli/setup-infra.js +219 -0
  17. package/lib/cli/setup-secrets.js +48 -0
  18. package/lib/cli/setup-utility.js +202 -0
  19. package/lib/cli.js +7 -961
  20. package/lib/commands/up-common.js +31 -1
  21. package/lib/commands/up-miso.js +6 -2
  22. package/lib/commands/wizard-core.js +32 -7
  23. package/lib/core/config.js +10 -0
  24. package/lib/core/ensure-encryption-key.js +56 -0
  25. package/lib/deployment/deployer-status.js +101 -0
  26. package/lib/deployment/deployer.js +62 -110
  27. package/lib/deployment/environment.js +133 -34
  28. package/lib/external-system/deploy.js +5 -1
  29. package/lib/external-system/test-auth.js +14 -7
  30. package/lib/generator/wizard.js +37 -41
  31. package/lib/infrastructure/helpers.js +1 -1
  32. package/lib/schema/environment-deploy-request.schema.json +64 -0
  33. package/lib/utils/help-builder.js +5 -2
  34. package/lib/utils/paths.js +22 -4
  35. package/lib/utils/secrets-generator.js +23 -8
  36. package/lib/utils/secrets-helpers.js +46 -21
  37. package/package.json +1 -1
  38. package/scripts/install-local.js +11 -2
  39. package/templates/applications/README.md.hbs +3 -3
  40. package/templates/applications/dataplane/variables.yaml +0 -2
  41. package/templates/applications/miso-controller/variables.yaml +0 -2
  42. package/templates/external-system/deploy.js.hbs +69 -0
  43. package/templates/infra/environment-dev.json +10 -0
@@ -30,6 +30,36 @@ async function ensureTemplateAtPath(appName, targetAppPath) {
30
30
  return true;
31
31
  }
32
32
 
33
+ /**
34
+ * Patches variables.yaml to set build.envOutputPath to null for deploy-only (no local code).
35
+ * Use when running up-miso/up-platform so we do not copy .env to repo paths or show that message.
36
+ * Patches both primary builder path and cwd/builder if different.
37
+ *
38
+ * @param {string} appName - Application name (e.g. miso-controller, dataplane)
39
+ */
40
+ function patchEnvOutputPathForDeployOnly(appName) {
41
+ if (!appName || typeof appName !== 'string') return;
42
+ const pathsToPatch = [pathsUtil.getBuilderPath(appName)];
43
+ const cwdBuilderPath = path.join(process.cwd(), 'builder', appName);
44
+ if (path.resolve(cwdBuilderPath) !== path.resolve(pathsToPatch[0])) {
45
+ pathsToPatch.push(cwdBuilderPath);
46
+ }
47
+ const envOutputPathLine = /^(\s*envOutputPath:)\s*.*$/m;
48
+ const replacement = '$1 null # deploy only, no copy';
49
+ for (const appPath of pathsToPatch) {
50
+ const variablesPath = path.join(appPath, 'variables.yaml');
51
+ if (!fs.existsSync(variablesPath)) continue;
52
+ try {
53
+ let content = fs.readFileSync(variablesPath, 'utf8');
54
+ if (!envOutputPathLine.test(content)) continue;
55
+ content = content.replace(envOutputPathLine, replacement);
56
+ fs.writeFileSync(variablesPath, content, 'utf8');
57
+ } catch (err) {
58
+ logger.warn(chalk.yellow(`Could not patch envOutputPath in ${variablesPath}: ${err.message}`));
59
+ }
60
+ }
61
+ }
62
+
33
63
  /**
34
64
  * Ensures builder app directory exists from template if variables.yaml is missing.
35
65
  * If builder/<appName>/variables.yaml does not exist, copies from templates/applications/<appName>.
@@ -69,4 +99,4 @@ async function ensureAppFromTemplate(appName) {
69
99
  return primaryCopied;
70
100
  }
71
101
 
72
- module.exports = { ensureAppFromTemplate };
102
+ module.exports = { ensureAppFromTemplate, patchEnvOutputPathForDeployOnly };
@@ -19,7 +19,7 @@ const secrets = require('../core/secrets');
19
19
  const infra = require('../infrastructure');
20
20
  const app = require('../app');
21
21
  const { saveLocalSecret } = require('../utils/local-secrets');
22
- const { ensureAppFromTemplate } = require('./up-common');
22
+ const { ensureAppFromTemplate, patchEnvOutputPathForDeployOnly } = require('./up-common');
23
23
 
24
24
  /** Keycloak base port (from templates/applications/keycloak/variables.yaml) */
25
25
  const KEYCLOAK_BASE_PORT = 8082;
@@ -126,12 +126,16 @@ async function handleUpMiso(options = {}) {
126
126
  const health = await infra.checkInfraHealth(undefined, { strict: true });
127
127
  const allHealthy = Object.values(health).every(status => status === 'healthy');
128
128
  if (!allHealthy) {
129
- throw new Error('Infrastructure is not up. Run \'aifabrix up\' first.');
129
+ throw new Error('Infrastructure is not up. Run \'aifabrix up-infra\' first.');
130
130
  }
131
131
  logger.log(chalk.green('✓ Infrastructure is up'));
132
132
  await ensureAppFromTemplate('keycloak');
133
133
  await ensureAppFromTemplate('miso-controller');
134
134
  await ensureAppFromTemplate('dataplane');
135
+ // Deploy-only: do not copy .env to repo paths; patch variables so envOutputPath is null
136
+ patchEnvOutputPathForDeployOnly('keycloak');
137
+ patchEnvOutputPathForDeployOnly('miso-controller');
138
+ patchEnvOutputPathForDeployOnly('dataplane');
135
139
  const developerId = await config.getDeveloperId();
136
140
  const devIdNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
137
141
  await setMisoSecretsAndResolve(devIdNum);
@@ -18,7 +18,8 @@ const {
18
18
  detectType,
19
19
  generateConfig,
20
20
  validateWizardConfig,
21
- getDeploymentDocs
21
+ getDeploymentDocs,
22
+ postDeploymentDocs
22
23
  } = require('../api/wizard.api');
23
24
  const { generateWizardFiles } = require('../generator/wizard');
24
25
  const {
@@ -353,23 +354,47 @@ async function handleFileSaving(appName, systemConfig, datasourceConfigs, system
353
354
  logger.log(chalk.blue('\n\uD83D\uDCCB Step 7: Save Files'));
354
355
  const spinner = ora('Saving files...').start();
355
356
  try {
356
- let aiGeneratedReadme = null;
357
- if (systemKey && dataplaneUrl && authConfig) {
357
+ const generatedFiles = await generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, { aiGeneratedReadme: null });
358
+ if (systemKey && dataplaneUrl && authConfig && generatedFiles.appPath) {
358
359
  try {
359
- const docsResponse = await getDeploymentDocs(dataplaneUrl, authConfig, systemKey);
360
- if (docsResponse.success && docsResponse.data?.content) aiGeneratedReadme = docsResponse.data.content;
360
+ const appPath = generatedFiles.appPath;
361
+ const deployKey = appName;
362
+ const variablesPath = path.join(appPath, 'variables.yaml');
363
+ const deployPath = path.join(appPath, `${deployKey}-deploy.json`);
364
+ let variablesYaml = null;
365
+ let deployJson = null;
366
+ try {
367
+ variablesYaml = await fs.readFile(variablesPath, 'utf8');
368
+ } catch {
369
+ // optional
370
+ }
371
+ try {
372
+ const deployContent = await fs.readFile(deployPath, 'utf8');
373
+ deployJson = JSON.parse(deployContent);
374
+ } catch {
375
+ // optional
376
+ }
377
+ const body = (variablesYaml !== null && variablesYaml !== undefined) || (deployJson !== null && deployJson !== undefined) ? { variablesYaml: variablesYaml || null, deployJson: deployJson || null } : null;
378
+ const docsResponse = body
379
+ ? await postDeploymentDocs(dataplaneUrl, authConfig, systemKey, body)
380
+ : await getDeploymentDocs(dataplaneUrl, authConfig, systemKey);
381
+ const content = docsResponse?.data?.content ?? docsResponse?.content;
382
+ if (content && typeof content === 'string') {
383
+ const readmePath = path.join(appPath, 'README.md');
384
+ await fs.writeFile(readmePath, content, 'utf8');
385
+ logger.log(chalk.gray(' Updated README.md from deployment-docs API (variables.yaml + deploy JSON).'));
386
+ }
361
387
  } catch (e) {
362
388
  logger.log(chalk.gray(` Could not fetch AI-generated README: ${e.message}`));
363
389
  }
364
390
  }
365
- const generatedFiles = await generateWizardFiles(appName, systemConfig, datasourceConfigs, systemKey, { aiGeneratedReadme });
366
391
  spinner.stop();
367
392
  logger.log(chalk.green('\n\u2713 Wizard completed successfully!'));
368
393
  logger.log(chalk.green(`\nFiles created in: ${generatedFiles.appPath}`));
369
394
  logger.log(chalk.blue('\nNext steps:'));
370
395
  logger.log(chalk.gray(` 1. Review the generated files in integration/${appName}/`));
371
396
  logger.log(chalk.gray(' 2. Update env.template with your authentication details'));
372
- logger.log(chalk.gray(` 3. Deploy using: ./deploy.sh or aifabrix deploy ${appName}`));
397
+ logger.log(chalk.gray(` 3. Deploy using: node deploy.js or aifabrix deploy ${appName}`));
373
398
  return generatedFiles;
374
399
  } catch (error) {
375
400
  spinner.stop();
@@ -396,6 +396,15 @@ async function setSecretsEncryptionKey(key) {
396
396
  await saveConfig(config);
397
397
  }
398
398
 
399
+ /**
400
+ * Ensure secrets encryption key exists (empty install). Delegates to ensure-encryption-key module.
401
+ * @returns {Promise<void>}
402
+ */
403
+ async function ensureSecretsEncryptionKey() {
404
+ const { ensureSecretsEncryptionKey: run } = require('./ensure-encryption-key');
405
+ await run({ getSecretsEncryptionKey, setSecretsEncryptionKey, getSecretsPath });
406
+ }
407
+
399
408
  async function getSecretsPath() {
400
409
  const config = await getConfig();
401
410
  return config['aifabrix-secrets'] || config['secrets-path'] || null;
@@ -427,6 +436,7 @@ const exportsObj = {
427
436
  decryptTokenValue,
428
437
  getSecretsEncryptionKey,
429
438
  setSecretsEncryptionKey,
439
+ ensureSecretsEncryptionKey,
430
440
  getSecretsPath,
431
441
  setSecretsPath,
432
442
  normalizeControllerUrl,
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Ensure secrets encryption key exists on empty install.
3
+ * If missing from config and from user/project secrets, generates and saves one. Never logs the key.
4
+ *
5
+ * @fileoverview Encryption key bootstrap for empty installation
6
+ * @author AI Fabrix Team
7
+ * @version 2.0.0
8
+ */
9
+
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+ const yaml = require('js-yaml');
13
+ const crypto = require('crypto');
14
+ const pathsUtil = require('../utils/paths');
15
+ const { saveLocalSecret } = require('../utils/local-secrets');
16
+
17
+ const ENCRYPTION_KEY = 'secrets-encryptionKeyVault';
18
+
19
+ function readKeyFromFile(filePath) {
20
+ try {
21
+ if (!fs.existsSync(filePath)) return null;
22
+ const content = fs.readFileSync(filePath, 'utf8');
23
+ const data = yaml.load(content);
24
+ if (data && typeof data[ENCRYPTION_KEY] === 'string') return data[ENCRYPTION_KEY];
25
+ } catch {
26
+ // Ignore
27
+ }
28
+ return null;
29
+ }
30
+
31
+ /**
32
+ * Ensure secrets encryption key exists. If config already has it, do nothing.
33
+ * If key exists in user or project secrets file, set config. Otherwise generate, write to user secrets, set config.
34
+ * @param {Object} config - Config module (getSecretsEncryptionKey, setSecretsEncryptionKey, getSecretsPath)
35
+ * @returns {Promise<void>}
36
+ */
37
+ async function ensureSecretsEncryptionKey(config) {
38
+ const existing = await config.getSecretsEncryptionKey();
39
+ if (existing) return;
40
+
41
+ const userSecretsPath = path.join(pathsUtil.getAifabrixHome(), 'secrets.local.yaml');
42
+ const projectSecretsPath = await config.getSecretsPath();
43
+
44
+ let key = readKeyFromFile(userSecretsPath);
45
+ if (!key && projectSecretsPath) key = readKeyFromFile(path.resolve(projectSecretsPath));
46
+ if (key) {
47
+ await config.setSecretsEncryptionKey(key);
48
+ return;
49
+ }
50
+
51
+ const newKey = crypto.randomBytes(32).toString('hex');
52
+ await saveLocalSecret(ENCRYPTION_KEY, newKey);
53
+ await config.setSecretsEncryptionKey(newKey);
54
+ }
55
+
56
+ module.exports = { ensureSecretsEncryptionKey };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Deployment status and polling helpers for deployer.
3
+ *
4
+ * @fileoverview Deployment status checks and polling utilities
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const chalk = require('chalk');
10
+ const logger = require('../utils/logger');
11
+
12
+ /**
13
+ * Checks if deployment status is terminal
14
+ * @param {string} status - Deployment status
15
+ * @returns {boolean} True if status is terminal
16
+ */
17
+ function isTerminalStatus(status) {
18
+ return status === 'completed' || status === 'failed' || status === 'cancelled';
19
+ }
20
+
21
+ /**
22
+ * Convert authConfig to pipeline auth config format
23
+ * @param {Object} authConfig - Authentication configuration
24
+ * @returns {Object} Pipeline auth config
25
+ */
26
+ function convertToPipelineAuthConfig(authConfig) {
27
+ return authConfig.type === 'bearer'
28
+ ? { type: 'bearer', token: authConfig.token }
29
+ : { type: 'client-credentials', clientId: authConfig.clientId, clientSecret: authConfig.clientSecret };
30
+ }
31
+
32
+ /**
33
+ * Handles error response from deployment status check
34
+ * @param {Object} response - API response
35
+ * @param {string} deploymentId - Deployment ID
36
+ * @throws {Error} Appropriate error message
37
+ */
38
+ function handleDeploymentStatusError(response, deploymentId) {
39
+ if (response.status === 404) {
40
+ throw new Error(`Deployment ${deploymentId || response.deploymentId || 'unknown'} not found`);
41
+ }
42
+ throw new Error(`Status check failed: ${response.formattedError || response.error || 'Unknown error'}`);
43
+ }
44
+
45
+ /**
46
+ * Extracts deployment data from response
47
+ * @param {Object} response - API response
48
+ * @returns {Object} Deployment data
49
+ */
50
+ function extractDeploymentData(response) {
51
+ const responseData = response.data;
52
+ return responseData.data || responseData;
53
+ }
54
+
55
+ /**
56
+ * Logs deployment progress
57
+ * @param {Object} deploymentData - Deployment data
58
+ * @param {number} attempt - Current attempt
59
+ * @param {number} maxAttempts - Maximum attempts
60
+ */
61
+ function logDeploymentProgress(deploymentData, attempt, maxAttempts) {
62
+ const status = deploymentData.status;
63
+ const progress = deploymentData.progress || 0;
64
+ logger.log(chalk.blue(` Status: ${status} (${progress}%) (attempt ${attempt + 1}/${maxAttempts})`));
65
+ }
66
+
67
+ /**
68
+ * Process deployment status response
69
+ * @param {Object} response - API response
70
+ * @param {number} attempt - Current attempt number
71
+ * @param {number} maxAttempts - Maximum attempts
72
+ * @param {number} interval - Polling interval
73
+ * @param {string} deploymentId - Deployment ID for error messages
74
+ * @returns {Promise<Object|null>} Deployment data if terminal, null if needs to continue polling
75
+ */
76
+ async function processDeploymentStatusResponse(response, attempt, maxAttempts, interval, deploymentId) {
77
+ if (!response.success || !response.data) {
78
+ handleDeploymentStatusError(response, deploymentId);
79
+ }
80
+
81
+ const deploymentData = extractDeploymentData(response);
82
+ if (isTerminalStatus(deploymentData.status)) {
83
+ return deploymentData;
84
+ }
85
+
86
+ logDeploymentProgress(deploymentData, attempt, maxAttempts);
87
+ if (attempt < maxAttempts - 1) {
88
+ await new Promise(resolve => setTimeout(resolve, interval));
89
+ }
90
+
91
+ return null;
92
+ }
93
+
94
+ module.exports = {
95
+ isTerminalStatus,
96
+ convertToPipelineAuthConfig,
97
+ handleDeploymentStatusError,
98
+ extractDeploymentData,
99
+ logDeploymentProgress,
100
+ processDeploymentStatusResponse
101
+ };
@@ -16,6 +16,23 @@ const { validateControllerUrl, validateEnvironmentKey } = require('../utils/depl
16
16
  const { handleDeploymentError, handleDeploymentErrors } = require('../utils/deployment-errors');
17
17
  const { validatePipeline, deployPipeline, getPipelineDeployment } = require('../api/pipeline.api');
18
18
  const { handleValidationResponse } = require('../utils/deployment-validation-helpers');
19
+ const {
20
+ convertToPipelineAuthConfig,
21
+ processDeploymentStatusResponse
22
+ } = require('./deployer-status');
23
+
24
+ /**
25
+ * For external systems, send full manifest (application + inline system + full dataSources).
26
+ * No transform - controller receives complete application, external system, and data sources.
27
+ * @param {Object} manifest - Full manifest (type 'external', system, dataSources as full objects)
28
+ * @returns {Object} Manifest to send to pipeline (external sent as-is)
29
+ */
30
+ function transformExternalManifestForPipeline(manifest) {
31
+ if (!manifest) {
32
+ return manifest;
33
+ }
34
+ return manifest;
35
+ }
19
36
 
20
37
  /**
21
38
  * Build validation data for deployment
@@ -28,27 +45,42 @@ const { handleValidationResponse } = require('../utils/deployment-validation-hel
28
45
  */
29
46
  async function buildValidationData(manifest, validatedEnvKey, authConfig, options) {
30
47
  const tokenManager = require('../utils/token-manager');
31
- const { clientId, clientSecret } = await tokenManager.extractClientCredentials(
32
- authConfig,
33
- manifest.key,
34
- validatedEnvKey,
35
- options
36
- );
48
+ let clientId;
49
+ let clientSecret;
50
+ let pipelineAuthConfig;
51
+
52
+ try {
53
+ const credentials = await tokenManager.extractClientCredentials(
54
+ authConfig,
55
+ manifest.key,
56
+ validatedEnvKey,
57
+ options
58
+ );
59
+ clientId = credentials.clientId;
60
+ clientSecret = credentials.clientSecret;
61
+ pipelineAuthConfig = {
62
+ type: 'client-credentials',
63
+ clientId,
64
+ clientSecret
65
+ };
66
+ } catch (credError) {
67
+ if (authConfig.type === 'bearer' && authConfig.token) {
68
+ pipelineAuthConfig = { type: 'bearer', token: authConfig.token };
69
+ clientId = manifest.key;
70
+ clientSecret = '';
71
+ } else {
72
+ throw credError;
73
+ }
74
+ }
37
75
 
38
76
  const repositoryUrl = options.repositoryUrl || `https://github.com/aifabrix/${manifest.key}`;
39
77
  const validationData = {
40
- clientId: clientId,
41
- clientSecret: clientSecret,
78
+ clientId: clientId || '',
79
+ clientSecret: clientSecret || '',
42
80
  repositoryUrl: repositoryUrl,
43
81
  applicationConfig: manifest
44
82
  };
45
83
 
46
- const pipelineAuthConfig = {
47
- type: 'client-credentials',
48
- clientId: clientId,
49
- clientSecret: clientSecret
50
- };
51
-
52
84
  return { validationData, pipelineAuthConfig };
53
85
  }
54
86
 
@@ -130,7 +162,7 @@ function validateDeploymentCredentials(authConfig) {
130
162
  }
131
163
 
132
164
  /**
133
- * Build deployment data and auth config
165
+ * Build deployment data and auth config (supports bearer-only when no client credentials)
134
166
  * @param {string} validateToken - Validation token
135
167
  * @param {Object} authConfig - Authentication configuration
136
168
  * @param {Object} options - Deployment options
@@ -143,11 +175,13 @@ function buildDeploymentData(validateToken, authConfig, options) {
143
175
  imageTag: imageTag
144
176
  };
145
177
 
146
- const pipelineAuthConfig = {
147
- type: 'client-credentials',
148
- clientId: authConfig.clientId,
149
- clientSecret: authConfig.clientSecret
150
- };
178
+ const pipelineAuthConfig = authConfig.type === 'bearer' && authConfig.token && !authConfig.clientId
179
+ ? { type: 'bearer', token: authConfig.token }
180
+ : {
181
+ type: 'client-credentials',
182
+ clientId: authConfig.clientId,
183
+ clientSecret: authConfig.clientSecret
184
+ };
151
185
 
152
186
  return { deployData, pipelineAuthConfig };
153
187
  }
@@ -212,10 +246,12 @@ async function sendDeploymentRequest(url, envKey, validateToken, authConfig, opt
212
246
  const validatedEnvKey = validateEnvironmentKey(envKey);
213
247
  const maxRetries = options.maxRetries || 3;
214
248
 
215
- // Validate credentials
216
- validateDeploymentCredentials(authConfig);
249
+ const useBearerOnly = authConfig.type === 'bearer' && authConfig.token && !authConfig.clientId;
250
+ if (!useBearerOnly) {
251
+ validateDeploymentCredentials(authConfig);
252
+ }
217
253
 
218
- // Build deployment data
254
+ // Build deployment data (supports bearer-only when no client credentials)
219
255
  const { deployData, pipelineAuthConfig } = buildDeploymentData(validateToken, authConfig, options);
220
256
 
221
257
  // Wrap API call with retry logic
@@ -237,92 +273,6 @@ async function sendDeploymentRequest(url, envKey, validateToken, authConfig, opt
237
273
  throwDeploymentError(lastError, maxRetries);
238
274
  }
239
275
 
240
- /**
241
- * Checks if deployment status is terminal
242
- * @function isTerminalStatus
243
- * @param {string} status - Deployment status
244
- * @returns {boolean} True if status is terminal
245
- */
246
- function isTerminalStatus(status) {
247
- return status === 'completed' || status === 'failed' || status === 'cancelled';
248
- }
249
-
250
- /**
251
- * Convert authConfig to pipeline auth config format
252
- * @param {Object} authConfig - Authentication configuration
253
- * @returns {Object} Pipeline auth config
254
- */
255
- function convertToPipelineAuthConfig(authConfig) {
256
- return authConfig.type === 'bearer'
257
- ? { type: 'bearer', token: authConfig.token }
258
- : { type: 'client-credentials', clientId: authConfig.clientId, clientSecret: authConfig.clientSecret };
259
- }
260
-
261
- /**
262
- * Process deployment status response
263
- * @param {Object} response - API response
264
- * @param {number} attempt - Current attempt number
265
- * @param {number} maxAttempts - Maximum attempts
266
- * @param {number} interval - Polling interval
267
- * @param {string} deploymentId - Deployment ID for error messages
268
- * @returns {Object|null} Deployment data if terminal, null if needs to continue polling
269
- */
270
- /**
271
- * Handles error response from deployment status check
272
- * @function handleDeploymentStatusError
273
- * @param {Object} response - API response
274
- * @param {string} deploymentId - Deployment ID
275
- * @throws {Error} Appropriate error message
276
- */
277
- function handleDeploymentStatusError(response, deploymentId) {
278
- if (response.status === 404) {
279
- throw new Error(`Deployment ${deploymentId || response.deploymentId || 'unknown'} not found`);
280
- }
281
- throw new Error(`Status check failed: ${response.formattedError || response.error || 'Unknown error'}`);
282
- }
283
-
284
- /**
285
- * Extracts deployment data from response
286
- * @function extractDeploymentData
287
- * @param {Object} response - API response
288
- * @returns {Object} Deployment data
289
- */
290
- function extractDeploymentData(response) {
291
- const responseData = response.data;
292
- return responseData.data || responseData;
293
- }
294
-
295
- /**
296
- * Logs deployment progress
297
- * @function logDeploymentProgress
298
- * @param {Object} deploymentData - Deployment data
299
- * @param {number} attempt - Current attempt
300
- * @param {number} maxAttempts - Maximum attempts
301
- */
302
- function logDeploymentProgress(deploymentData, attempt, maxAttempts) {
303
- const status = deploymentData.status;
304
- const progress = deploymentData.progress || 0;
305
- logger.log(chalk.blue(` Status: ${status} (${progress}%) (attempt ${attempt + 1}/${maxAttempts})`));
306
- }
307
-
308
- async function processDeploymentStatusResponse(response, attempt, maxAttempts, interval, deploymentId) {
309
- if (!response.success || !response.data) {
310
- handleDeploymentStatusError(response, deploymentId);
311
- }
312
-
313
- const deploymentData = extractDeploymentData(response);
314
- if (isTerminalStatus(deploymentData.status)) {
315
- return deploymentData;
316
- }
317
-
318
- logDeploymentProgress(deploymentData, attempt, maxAttempts);
319
- if (attempt < maxAttempts - 1) {
320
- await new Promise(resolve => setTimeout(resolve, interval));
321
- }
322
-
323
- return null;
324
- }
325
-
326
276
  /**
327
277
  * Polls deployment status from controller
328
278
  * Uses pipeline endpoint for CI/CD monitoring with minimal deployment info
@@ -470,9 +420,11 @@ async function deployToController(manifest, controllerUrl, envKey, authConfig, o
470
420
  // Log deployment attempt for audit
471
421
  await auditLogger.logDeploymentAttempt(manifest.key, url, options);
472
422
 
423
+ const pipelineManifest = transformExternalManifestForPipeline(manifest);
424
+
473
425
  try {
474
426
  // Send deployment request
475
- const result = await sendDeployment(url, validatedEnvKey, manifest, authConfig, options);
427
+ const result = await sendDeployment(url, validatedEnvKey, pipelineManifest, authConfig, options);
476
428
 
477
429
  // Poll for deployment status if enabled
478
430
  return await pollDeployment(result, url, validatedEnvKey, authConfig, options);