@aifabrix/builder 2.31.1 → 2.32.2

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 (118) hide show
  1. package/README.md +9 -9
  2. package/integration/hubspot/README.md +2 -2
  3. package/integration/hubspot/hubspot-deploy-company.json +17 -14
  4. package/integration/hubspot/hubspot-deploy-contact.json +19 -16
  5. package/integration/hubspot/hubspot-deploy-deal.json +21 -18
  6. package/lib/api/types/datasources.types.js +31 -5
  7. package/lib/api/types/wizard.types.js +142 -0
  8. package/lib/api/wizard.api.js +177 -0
  9. package/lib/{app-config.js → app/config.js} +4 -4
  10. package/lib/{app-deploy.js → app/deploy.js} +8 -8
  11. package/lib/app/display.js +90 -0
  12. package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
  13. package/lib/{app-down.js → app/down.js} +4 -4
  14. package/lib/app/helpers.js +218 -0
  15. package/lib/app/index.js +298 -0
  16. package/lib/{app-list.js → app/list.js} +6 -6
  17. package/lib/{app-push.js → app/push.js} +4 -4
  18. package/lib/{app-readme.js → app/readme.js} +34 -13
  19. package/lib/{app-register.js → app/register.js} +9 -9
  20. package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
  21. package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
  22. package/lib/{app-run.js → app/run.js} +6 -6
  23. package/lib/{build.js → build/index.js} +59 -32
  24. package/lib/build/package.json +7 -0
  25. package/lib/cli.js +245 -179
  26. package/lib/commands/app.js +3 -3
  27. package/lib/commands/datasource.js +4 -4
  28. package/lib/commands/login-credentials.js +209 -0
  29. package/lib/commands/login-device.js +254 -0
  30. package/lib/commands/login.js +67 -378
  31. package/lib/commands/logout.js +1 -1
  32. package/lib/commands/secrets-set.js +1 -1
  33. package/lib/commands/secure.js +2 -2
  34. package/lib/commands/wizard.js +498 -0
  35. package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
  36. package/lib/{config.js → core/config.js} +28 -26
  37. package/lib/{diff.js → core/diff.js} +157 -72
  38. package/lib/{secrets.js → core/secrets.js} +86 -49
  39. package/lib/{templates.js → core/templates-env.js} +14 -222
  40. package/lib/core/templates.js +279 -0
  41. package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
  42. package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
  43. package/lib/datasource/list.js +223 -0
  44. package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
  45. package/lib/{deployer.js → deployment/deployer.js} +48 -18
  46. package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
  47. package/lib/{push.js → deployment/push.js} +1 -1
  48. package/lib/external-system/deploy-helpers.js +145 -0
  49. package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
  50. package/lib/external-system/download-helpers.js +114 -0
  51. package/lib/{external-system-download.js → external-system/download.js} +92 -135
  52. package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
  53. package/lib/external-system/test-auth.js +40 -0
  54. package/lib/external-system/test-execution.js +84 -0
  55. package/lib/external-system/test-helpers.js +109 -0
  56. package/lib/{external-system-test.js → external-system/test.js} +174 -192
  57. package/lib/{generator-builders.js → generator/builders.js} +87 -10
  58. package/lib/{generator-external.js → generator/external.js} +115 -52
  59. package/lib/{github-generator.js → generator/github.js} +116 -15
  60. package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
  61. package/lib/{generator.js → generator/index.js} +49 -22
  62. package/lib/{generator-split.js → generator/split.js} +108 -55
  63. package/lib/generator/wizard-prompts.js +357 -0
  64. package/lib/generator/wizard.js +490 -0
  65. package/lib/{infra.js → infrastructure/index.js} +49 -22
  66. package/lib/schema/external-datasource.schema.json +158 -136
  67. package/lib/schema/external-system.schema.json +43 -1
  68. package/lib/utils/api.js +9 -5
  69. package/lib/utils/app-register-api.js +60 -32
  70. package/lib/utils/app-register-auth.js +172 -47
  71. package/lib/utils/app-register-config.js +130 -59
  72. package/lib/utils/app-run-containers.js +29 -8
  73. package/lib/utils/build-helpers.js +1 -1
  74. package/lib/utils/cli-utils.js +78 -30
  75. package/lib/utils/compose-generator.js +145 -65
  76. package/lib/utils/config-paths.js +2 -0
  77. package/lib/utils/deployment-errors.js +1 -1
  78. package/lib/utils/device-code.js +99 -41
  79. package/lib/utils/env-config-loader.js +1 -1
  80. package/lib/utils/env-copy.js +21 -18
  81. package/lib/utils/env-endpoints.js +115 -67
  82. package/lib/utils/env-map.js +13 -14
  83. package/lib/utils/env-ports.js +45 -25
  84. package/lib/utils/env-template.js +84 -42
  85. package/lib/utils/error-formatter.js +26 -9
  86. package/lib/utils/error-formatters/error-parser.js +90 -4
  87. package/lib/utils/error-formatters/http-status-errors.js +54 -17
  88. package/lib/utils/error-formatters/network-errors.js +103 -26
  89. package/lib/utils/external-system-display.js +184 -90
  90. package/lib/utils/external-system-validators.js +164 -42
  91. package/lib/utils/file-upload.js +109 -0
  92. package/lib/utils/health-check.js +199 -83
  93. package/lib/utils/infra-containers.js +1 -1
  94. package/lib/utils/infra-status.js +66 -15
  95. package/lib/utils/local-secrets.js +45 -25
  96. package/lib/utils/paths.js +45 -33
  97. package/lib/utils/schema-loader.js +42 -25
  98. package/lib/utils/schema-resolver.js +123 -74
  99. package/lib/utils/secrets-encryption.js +62 -25
  100. package/lib/utils/secrets-helpers.js +126 -63
  101. package/lib/utils/secrets-path.js +1 -1
  102. package/lib/utils/secrets-url.js +1 -1
  103. package/lib/utils/token-manager-refresh.js +181 -0
  104. package/lib/utils/token-manager.js +76 -123
  105. package/lib/utils/variable-transformer.js +154 -77
  106. package/lib/utils/yaml-preserve.js +41 -47
  107. package/lib/{template-validator.js → validation/template.js} +54 -23
  108. package/lib/{validate.js → validation/validate.js} +205 -125
  109. package/lib/{validator.js → validation/validator.js} +58 -39
  110. package/package.json +31 -2
  111. package/templates/external-system/deploy.ps1.hbs +34 -0
  112. package/templates/external-system/deploy.sh.hbs +34 -0
  113. package/templates/external-system/external-datasource.json.hbs +31 -12
  114. package/lib/app.js +0 -467
  115. package/lib/datasource-list.js +0 -141
  116. /package/lib/{app-prompts.js → app/prompts.js} +0 -0
  117. /package/lib/{env-reader.js → core/env-reader.js} +0 -0
  118. /package/lib/{key-generator.js → core/key-generator.js} +0 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * @fileoverview Wizard API functions
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const { ApiClient } = require('./index');
8
+ const { uploadFile } = require('../utils/file-upload');
9
+
10
+ /**
11
+ * Select wizard mode
12
+ * POST /api/v1/wizard/mode-selection
13
+ * @async
14
+ * @function selectMode
15
+ * @param {string} dataplaneUrl - Dataplane base URL
16
+ * @param {Object} authConfig - Authentication configuration
17
+ * @param {string} mode - Wizard mode ('create-system' | 'add-datasource')
18
+ * @returns {Promise<Object>} Mode selection response
19
+ * @throws {Error} If request fails
20
+ */
21
+ async function selectMode(dataplaneUrl, authConfig, mode) {
22
+ const client = new ApiClient(dataplaneUrl, authConfig);
23
+ return await client.post('/api/v1/wizard/mode-selection', {
24
+ body: { mode }
25
+ });
26
+ }
27
+
28
+ /**
29
+ * Select wizard source
30
+ * POST /api/v1/wizard/source-selection
31
+ * @async
32
+ * @function selectSource
33
+ * @param {string} dataplaneUrl - Dataplane base URL
34
+ * @param {Object} authConfig - Authentication configuration
35
+ * @param {string} sourceType - Source type ('openapi-file' | 'openapi-url' | 'mcp-server' | 'known-platform')
36
+ * @param {string} [sourceData] - Source data (file path, URL, etc.)
37
+ * @returns {Promise<Object>} Source selection response
38
+ * @throws {Error} If request fails
39
+ */
40
+ async function selectSource(dataplaneUrl, authConfig, sourceType, sourceData) {
41
+ const client = new ApiClient(dataplaneUrl, authConfig);
42
+ return await client.post('/api/v1/wizard/source-selection', {
43
+ body: {
44
+ sourceType,
45
+ sourceData
46
+ }
47
+ });
48
+ }
49
+
50
+ /**
51
+ * Parse OpenAPI file
52
+ * POST /api/v1/wizard/parse-openapi
53
+ * @async
54
+ * @function parseOpenApi
55
+ * @param {string} dataplaneUrl - Dataplane base URL
56
+ * @param {Object} authConfig - Authentication configuration
57
+ * @param {string} openApiFilePath - Path to OpenAPI file
58
+ * @returns {Promise<Object>} Parsed OpenAPI response
59
+ * @throws {Error} If request fails
60
+ */
61
+ async function parseOpenApi(dataplaneUrl, authConfig, openApiFilePath) {
62
+ const url = `${dataplaneUrl.replace(/\/$/, '')}/api/v1/wizard/parse-openapi`;
63
+ return await uploadFile(url, openApiFilePath, 'file', authConfig);
64
+ }
65
+
66
+ /**
67
+ * Detect API type from OpenAPI spec
68
+ * POST /api/v1/wizard/detect-type
69
+ * @async
70
+ * @function detectType
71
+ * @param {string} dataplaneUrl - Dataplane base URL
72
+ * @param {Object} authConfig - Authentication configuration
73
+ * @param {Object} openApiSpec - OpenAPI specification object
74
+ * @returns {Promise<Object>} Type detection response
75
+ * @throws {Error} If request fails
76
+ */
77
+ async function detectType(dataplaneUrl, authConfig, openApiSpec) {
78
+ const client = new ApiClient(dataplaneUrl, authConfig);
79
+ return await client.post('/api/v1/wizard/detect-type', {
80
+ body: { openApiSpec }
81
+ });
82
+ }
83
+
84
+ /**
85
+ * Generate configuration via AI
86
+ * POST /api/v1/wizard/generate-config
87
+ * @async
88
+ * @function generateConfig
89
+ * @param {string} dataplaneUrl - Dataplane base URL
90
+ * @param {Object} authConfig - Authentication configuration
91
+ * @param {Object} config - Generation configuration
92
+ * @param {string} config.mode - Wizard mode
93
+ * @param {string} config.sourceType - Source type
94
+ * @param {Object} [config.openApiSpec] - OpenAPI specification (if applicable)
95
+ * @param {string} [config.userIntent] - User intent (e.g., 'sales-focused', 'support-focused')
96
+ * @param {Object} [config.preferences] - User preferences
97
+ * @returns {Promise<Object>} Generated configuration response
98
+ * @throws {Error} If request fails
99
+ */
100
+ async function generateConfig(dataplaneUrl, authConfig, config) {
101
+ const client = new ApiClient(dataplaneUrl, authConfig);
102
+ return await client.post('/api/v1/wizard/generate-config', {
103
+ body: config
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Validate wizard configuration
109
+ * POST /api/v1/wizard/validate
110
+ * @async
111
+ * @function validateWizardConfig
112
+ * @param {string} dataplaneUrl - Dataplane base URL
113
+ * @param {Object} authConfig - Authentication configuration
114
+ * @param {Object} systemConfig - System configuration to validate
115
+ * @param {Object[]} datasourceConfigs - Array of datasource configurations to validate
116
+ * @returns {Promise<Object>} Validation response
117
+ * @throws {Error} If request fails
118
+ */
119
+ async function validateWizardConfig(dataplaneUrl, authConfig, systemConfig, datasourceConfigs) {
120
+ const client = new ApiClient(dataplaneUrl, authConfig);
121
+ return await client.post('/api/v1/wizard/validate', {
122
+ body: {
123
+ systemConfig,
124
+ datasourceConfigs
125
+ }
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Test MCP server connection
131
+ * POST /api/v1/wizard/test-mcp-connection
132
+ * @async
133
+ * @function testMcpConnection
134
+ * @param {string} dataplaneUrl - Dataplane base URL
135
+ * @param {Object} authConfig - Authentication configuration
136
+ * @param {string} serverUrl - MCP server URL
137
+ * @param {string} token - MCP server authentication token
138
+ * @returns {Promise<Object>} Connection test response
139
+ * @throws {Error} If request fails
140
+ */
141
+ async function testMcpConnection(dataplaneUrl, authConfig, serverUrl, token) {
142
+ const client = new ApiClient(dataplaneUrl, authConfig);
143
+ return await client.post('/api/v1/wizard/test-mcp-connection', {
144
+ body: {
145
+ serverUrl,
146
+ token
147
+ }
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Get deployment documentation for a system
153
+ * GET /api/v1/wizard/deployment-docs/{systemKey}
154
+ * @async
155
+ * @function getDeploymentDocs
156
+ * @param {string} dataplaneUrl - Dataplane base URL
157
+ * @param {Object} authConfig - Authentication configuration
158
+ * @param {string} systemKey - System key identifier
159
+ * @returns {Promise<Object>} Deployment documentation response
160
+ * @throws {Error} If request fails
161
+ */
162
+ async function getDeploymentDocs(dataplaneUrl, authConfig, systemKey) {
163
+ const client = new ApiClient(dataplaneUrl, authConfig);
164
+ return await client.get(`/api/v1/wizard/deployment-docs/${systemKey}`);
165
+ }
166
+
167
+ module.exports = {
168
+ selectMode,
169
+ selectSource,
170
+ parseOpenApi,
171
+ detectType,
172
+ generateConfig,
173
+ validateWizardConfig,
174
+ testMcpConnection,
175
+ getDeploymentDocs
176
+ };
177
+
@@ -11,10 +11,10 @@
11
11
  const fs = require('fs').promises;
12
12
  const path = require('path');
13
13
  const chalk = require('chalk');
14
- const { generateVariablesYaml, generateEnvTemplate, generateRbacYaml } = require('./templates');
15
- const { generateEnvTemplate: generateEnvTemplateFromReader } = require('./env-reader');
16
- const { generateReadmeMdFile } = require('./app-readme');
17
- const logger = require('./utils/logger');
14
+ const { generateVariablesYaml, generateEnvTemplate, generateRbacYaml } = require('../core/templates');
15
+ const { generateEnvTemplate: generateEnvTemplateFromReader } = require('../core/env-reader');
16
+ const { generateReadmeMdFile } = require('./readme');
17
+ const logger = require('../utils/logger');
18
18
 
19
19
  /**
20
20
  * Checks if a file exists
@@ -13,11 +13,11 @@ const fs = require('fs').promises;
13
13
  const path = require('path');
14
14
  const yaml = require('js-yaml');
15
15
  const chalk = require('chalk');
16
- const pushUtils = require('./push');
17
- const logger = require('./utils/logger');
18
- const config = require('./config');
19
- const { getDeploymentAuth } = require('./utils/token-manager');
20
- const { detectAppType } = require('./utils/paths');
16
+ const pushUtils = require('../deployment/push');
17
+ const logger = require('../utils/logger');
18
+ const config = require('../core/config');
19
+ const { getDeploymentAuth } = require('../utils/token-manager');
20
+ const { detectAppType } = require('../utils/paths');
21
21
 
22
22
  /**
23
23
  * Validate application name format
@@ -294,7 +294,7 @@ async function loadDeploymentConfig(appName, options) {
294
294
  */
295
295
  async function generateAndValidateManifest(appName) {
296
296
  logger.log(chalk.blue(`\n📋 Generating deployment manifest for ${appName}...`));
297
- const generator = require('./generator');
297
+ const generator = require('../generator');
298
298
 
299
299
  // generateDeployJson already validates against schema and throws on error
300
300
  const manifestPath = await generator.generateDeployJson(appName);
@@ -327,7 +327,7 @@ function displayDeploymentInfo(manifest, manifestPath) {
327
327
  */
328
328
  async function executeDeployment(manifest, deploymentConfig) {
329
329
  logger.log(chalk.blue(`\n🚀 Deploying to ${deploymentConfig.controllerUrl} (environment: ${deploymentConfig.envKey})...`));
330
- const deployer = require('./deployer');
330
+ const deployer = require('../deployment/deployer');
331
331
  return await deployer.deployToController(
332
332
  manifest,
333
333
  deploymentConfig.controllerUrl,
@@ -418,7 +418,7 @@ async function deployApp(appName, options = {}) {
418
418
  const alreadyLogged = error._logged === true;
419
419
  const url = controllerUrl || options.controller || 'unknown';
420
420
 
421
- const deployer = require('./deployer');
421
+ const deployer = require('../deployment/deployer');
422
422
  // handleDeploymentErrors will log, format, and throw the error
423
423
  await deployer.handleDeploymentErrors(error, appName, url, alreadyLogged);
424
424
  }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Application Display Utilities
3
+ *
4
+ * Handles display of success messages and user guidance
5
+ * for application creation.
6
+ *
7
+ * @fileoverview Display utilities for application creation
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ const path = require('path');
13
+ const chalk = require('chalk');
14
+ const logger = require('../utils/logger');
15
+
16
+ /**
17
+ * Displays external system success message
18
+ * @function displayExternalSystemSuccess
19
+ * @param {string} appName - Application name
20
+ * @param {Object} config - Configuration
21
+ * @param {string} location - Application location
22
+ */
23
+ function displayExternalSystemSuccess(appName, config, location) {
24
+ logger.log(chalk.blue('Type: External System'));
25
+ logger.log(chalk.blue(`System Key: ${config.systemKey || appName}`));
26
+ logger.log(chalk.green('\nNext steps:'));
27
+ logger.log(chalk.white('1. Edit external system JSON files in ' + location));
28
+ logger.log(chalk.white('2. Run: aifabrix app register ' + appName + ' --environment dev'));
29
+ logger.log(chalk.white('3. Run: aifabrix build ' + appName + ' (deploys to dataplane)'));
30
+ logger.log(chalk.white('4. Run: aifabrix deploy ' + appName + ' (publishes to dataplane)'));
31
+ }
32
+
33
+ /**
34
+ * Displays webapp success message
35
+ * @function displayWebappSuccess
36
+ * @param {string} appName - Application name
37
+ * @param {Object} config - Configuration
38
+ * @param {string} envConversionMessage - Environment conversion message
39
+ */
40
+ function displayWebappSuccess(appName, config, envConversionMessage) {
41
+ logger.log(chalk.blue(`Language: ${config.language}`));
42
+ logger.log(chalk.blue(`Port: ${config.port}`));
43
+
44
+ if (config.database) logger.log(chalk.yellow(' - Database enabled'));
45
+ if (config.redis) logger.log(chalk.yellow(' - Redis enabled'));
46
+ if (config.storage) logger.log(chalk.yellow(' - Storage enabled'));
47
+ if (config.authentication) logger.log(chalk.yellow(' - Authentication enabled'));
48
+
49
+ logger.log(chalk.gray(envConversionMessage));
50
+
51
+ logger.log(chalk.green('\nNext steps:'));
52
+ logger.log(chalk.white('1. Copy env.template to .env and fill in your values'));
53
+ logger.log(chalk.white('2. Run: aifabrix build ' + appName));
54
+ logger.log(chalk.white('3. Run: aifabrix run ' + appName));
55
+ }
56
+
57
+ /**
58
+ * Displays success message after app creation
59
+ * @param {string} appName - Application name
60
+ * @param {Object} config - Final configuration
61
+ * @param {string} envConversionMessage - Environment conversion message
62
+ * @param {boolean} hasAppFiles - Whether app files were created
63
+ * @param {string} appPath - Application path
64
+ */
65
+ function displaySuccessMessage(appName, config, envConversionMessage, hasAppFiles = false, appPath = null) {
66
+ logger.log(chalk.green('\n✓ Application created successfully!'));
67
+ logger.log(chalk.blue(`\nApplication: ${appName}`));
68
+
69
+ // Determine location based on app type
70
+ const baseDir = config.type === 'external' ? 'integration' : 'builder';
71
+ const location = appPath ? path.relative(process.cwd(), appPath) : `${baseDir}/${appName}/`;
72
+ logger.log(chalk.blue(`Location: ${location}`));
73
+
74
+ if (hasAppFiles) {
75
+ logger.log(chalk.blue(`Application files: apps/${appName}/`));
76
+ }
77
+
78
+ if (config.type === 'external') {
79
+ displayExternalSystemSuccess(appName, config, location);
80
+ } else {
81
+ displayWebappSuccess(appName, config, envConversionMessage);
82
+ }
83
+ }
84
+
85
+ module.exports = {
86
+ displaySuccessMessage,
87
+ displayExternalSystemSuccess,
88
+ displayWebappSuccess
89
+ };
90
+
@@ -12,9 +12,9 @@ const fs = require('fs').promises;
12
12
  const path = require('path');
13
13
  const chalk = require('chalk');
14
14
  const yaml = require('js-yaml');
15
- const build = require('./build');
16
- const { validateAppName } = require('./app-push');
17
- const logger = require('./utils/logger');
15
+ const build = require('../build');
16
+ const { validateAppName } = require('./push');
17
+ const logger = require('../utils/logger');
18
18
 
19
19
  /**
20
20
  * Checks if Dockerfile exists and validates overwrite permission
@@ -85,7 +85,7 @@ async function generateAndCopyDockerfile(appPath, dockerfilePath, config) {
85
85
  */
86
86
  async function generateDockerfileForApp(appName, options = {}) {
87
87
  // Check if app type is external - skip Dockerfile generation
88
- const { detectAppType } = require('./utils/paths');
88
+ const { detectAppType } = require('../utils/paths');
89
89
  try {
90
90
  const { isExternal } = await detectAppType(appName);
91
91
  if (isExternal) {
@@ -13,10 +13,10 @@
13
13
 
14
14
  const chalk = require('chalk');
15
15
  const { exec } = require('child_process');
16
- const logger = require('./utils/logger');
17
- const config = require('./config');
18
- const helpers = require('./app-run-helpers');
19
- const { validateAppName } = require('./app-push');
16
+ const logger = require('../utils/logger');
17
+ const config = require('../core/config');
18
+ const helpers = require('./run-helpers');
19
+ const { validateAppName } = require('./push');
20
20
 
21
21
  /**
22
22
  * Executes a shell command asynchronously.
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Application Helper Utilities
3
+ *
4
+ * Helper functions for application creation and validation
5
+ *
6
+ * @fileoverview Helper utilities for application management
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ const fs = require('fs').promises;
12
+ const path = require('path');
13
+ const chalk = require('chalk');
14
+ const { validateTemplate, copyTemplateFiles, copyAppFiles } = require('../validation/template');
15
+ const logger = require('../utils/logger');
16
+
17
+ /**
18
+ * Validates that app directory doesn't already exist
19
+ * @async
20
+ * @param {string} appPath - Application directory path
21
+ * @param {string} appName - Application name
22
+ * @param {string} baseDir - Base directory name
23
+ * @throws {Error} If directory already exists
24
+ */
25
+ async function validateAppDirectoryNotExists(appPath, appName, baseDir = 'builder') {
26
+ try {
27
+ await fs.access(appPath);
28
+ throw new Error(`Application '${appName}' already exists in ${baseDir}/${appName}/`);
29
+ } catch (error) {
30
+ if (error.code !== 'ENOENT') {
31
+ throw error;
32
+ }
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Gets the base directory path for an app based on its type
38
+ * @param {string} appType - Application type ('external' or other)
39
+ * @returns {string} Base directory path ('integration' or 'builder')
40
+ */
41
+ function getBaseDirForAppType(appType) {
42
+ return appType === 'external' ? 'integration' : 'builder';
43
+ }
44
+
45
+ /**
46
+ * Handles GitHub workflow generation if requested
47
+ * @async
48
+ * @param {Object} options - Creation options
49
+ * @param {Object} config - Final configuration
50
+ */
51
+ async function handleGitHubWorkflows(options, config) {
52
+ if (!options.github) {
53
+ return;
54
+ }
55
+
56
+ const githubGen = require('../generator/github');
57
+
58
+ // Parse github-steps if provided
59
+ const githubSteps = options.githubSteps
60
+ ? options.githubSteps.split(',').map(s => s.trim()).filter(s => s.length > 0)
61
+ : [];
62
+
63
+ const workflowFiles = await githubGen.generateGithubWorkflows(
64
+ process.cwd(),
65
+ config,
66
+ {
67
+ mainBranch: options.mainBranch || 'main',
68
+ uploadCoverage: true,
69
+ githubSteps: githubSteps
70
+ }
71
+ );
72
+
73
+ logger.log(chalk.green('✓ Generated GitHub Actions workflows:'));
74
+ workflowFiles.forEach(file => logger.log(chalk.gray(` - ${file}`)));
75
+ }
76
+
77
+ /**
78
+ * Validates app creation prerequisites
79
+ * @async
80
+ * @function validateAppCreation
81
+ * @param {string} appName - Application name
82
+ * @param {Object} options - Creation options
83
+ * @param {string} appPath - Application directory path
84
+ * @param {string} baseDir - Base directory name
85
+ * @throws {Error} If validation fails
86
+ */
87
+ async function validateAppCreation(appName, options, appPath, baseDir = 'builder') {
88
+ const { validateAppName } = require('./push');
89
+ validateAppName(appName);
90
+ await validateAppDirectoryNotExists(appPath, appName, baseDir);
91
+
92
+ if (!options.app) {
93
+ return;
94
+ }
95
+
96
+ const appsPath = path.join(process.cwd(), 'apps', appName);
97
+ try {
98
+ await fs.access(appsPath);
99
+ throw new Error(`Application '${appName}' already exists in apps/${appName}/`);
100
+ } catch (error) {
101
+ if (error.code !== 'ENOENT') {
102
+ throw error;
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Processes template files if template is specified
109
+ * @async
110
+ * @function processTemplateFiles
111
+ * @param {string} template - Template name
112
+ * @param {string} appPath - Application directory path
113
+ * @param {string} appName - Application name
114
+ * @param {Object} options - Creation options
115
+ * @param {Object} config - Final configuration
116
+ * @throws {Error} If template processing fails
117
+ */
118
+ async function processTemplateFiles(template, appPath, appName, options, config) {
119
+ if (!template) {
120
+ return;
121
+ }
122
+
123
+ await validateTemplate(template);
124
+ const copiedFiles = await copyTemplateFiles(template, appPath);
125
+ logger.log(chalk.green(`✓ Copied ${copiedFiles.length} file(s) from template '${template}'`));
126
+ const { updateTemplateVariables } = require('../utils/template-helpers');
127
+ await updateTemplateVariables(appPath, appName, options, config);
128
+ }
129
+
130
+ /**
131
+ * Updates variables.yaml for --app flag
132
+ * @async
133
+ * @function updateVariablesForAppFlag
134
+ * @param {string} appPath - Application directory path
135
+ * @param {string} appName - Application name
136
+ * @throws {Error} If update fails
137
+ */
138
+ async function updateVariablesForAppFlag(appPath, appName) {
139
+ try {
140
+ const yaml = require('js-yaml');
141
+ const variablesPath = path.join(appPath, 'variables.yaml');
142
+ const variablesContent = await fs.readFile(variablesPath, 'utf8');
143
+ const variables = yaml.load(variablesContent);
144
+
145
+ if (variables.build) {
146
+ variables.build.context = '../..';
147
+ variables.build.envOutputPath = `../../apps/${appName}/.env`;
148
+ } else {
149
+ variables.build = {
150
+ context: '../..',
151
+ envOutputPath: `../../apps/${appName}/.env`
152
+ };
153
+ }
154
+
155
+ await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }));
156
+ } catch (error) {
157
+ logger.warn(chalk.yellow(`⚠️ Warning: Could not update variables.yaml: ${error.message}`));
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Gets language from config or variables.yaml
163
+ * @async
164
+ * @function getLanguageForAppFiles
165
+ * @param {string} language - Language from config
166
+ * @param {string} appPath - Application directory path
167
+ * @returns {Promise<string>} Language to use
168
+ * @throws {Error} If language cannot be determined
169
+ */
170
+ async function getLanguageForAppFiles(language, appPath) {
171
+ if (language) {
172
+ return language;
173
+ }
174
+
175
+ const yaml = require('js-yaml');
176
+ const variablesPath = path.join(appPath, 'variables.yaml');
177
+ const variablesContent = await fs.readFile(variablesPath, 'utf8');
178
+ const variables = yaml.load(variablesContent);
179
+ const languageFromYaml = variables?.build?.language;
180
+
181
+ if (!languageFromYaml) {
182
+ throw new Error('Language not specified and could not be determined from variables.yaml. Use --language flag or ensure variables.yaml contains build.language');
183
+ }
184
+
185
+ return languageFromYaml;
186
+ }
187
+
188
+ /**
189
+ * Sets up apps directory and copies application files
190
+ * @async
191
+ * @function setupAppFiles
192
+ * @param {string} appName - Application name
193
+ * @param {string} appPath - Application directory path
194
+ * @param {Object} config - Final configuration
195
+ * @param {Object} options - Creation options
196
+ * @throws {Error} If setup fails
197
+ */
198
+ async function setupAppFiles(appName, appPath, config, options) {
199
+ const appsPath = path.join(process.cwd(), 'apps', appName);
200
+ await fs.mkdir(appsPath, { recursive: true });
201
+ await updateVariablesForAppFlag(appPath, appName);
202
+
203
+ const language = await getLanguageForAppFiles(config.language || options.language, appPath);
204
+ const copiedFiles = await copyAppFiles(language, appsPath);
205
+ logger.log(chalk.green(`✓ Copied ${copiedFiles.length} application file(s) to apps/${appName}/`));
206
+ }
207
+
208
+ module.exports = {
209
+ validateAppDirectoryNotExists,
210
+ getBaseDirForAppType,
211
+ handleGitHubWorkflows,
212
+ validateAppCreation,
213
+ processTemplateFiles,
214
+ updateVariablesForAppFlag,
215
+ getLanguageForAppFiles,
216
+ setupAppFiles
217
+ };
218
+