@aifabrix/builder 2.31.1 → 2.32.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 (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 +145 -133
  67. package/lib/schema/external-system.schema.json +42 -0
  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,298 @@
1
+ /**
2
+ * AI Fabrix Builder Application Management
3
+ *
4
+ * This module handles application building, running, and deployment.
5
+ * Includes runtime detection, Dockerfile generation, and container management.
6
+ *
7
+ * @fileoverview Application build and run management for AI Fabrix Builder
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ const fs = require('fs').promises;
13
+ const { readExistingEnv } = require('../core/env-reader');
14
+ const build = require('../build');
15
+ const appRun = require('./run');
16
+ const { promptForOptions } = require('./prompts');
17
+ const { generateConfigFiles } = require('./config');
18
+ const { pushApp } = require('./push');
19
+ const { generateDockerfileForApp } = require('./dockerfile');
20
+ const { loadTemplateVariables, updateTemplateVariables, mergeTemplateVariables } = require('../utils/template-helpers');
21
+ const { validateTemplate } = require('../validation/template');
22
+ const auditLogger = require('../core/audit-logger');
23
+ const { downApp } = require('./down');
24
+ const { getAppPath } = require('../utils/paths');
25
+ const { displaySuccessMessage } = require('./display');
26
+ const {
27
+ validateAppDirectoryNotExists,
28
+ getBaseDirForAppType,
29
+ handleGitHubWorkflows,
30
+ validateAppCreation,
31
+ processTemplateFiles,
32
+ setupAppFiles
33
+ } = require('./helpers');
34
+
35
+ /**
36
+ * Creates new application with scaffolded configuration files
37
+ * Prompts for configuration options and generates builder/ folder structure
38
+ *
39
+ * @async
40
+ * @function createApp
41
+ * @param {string} appName - Name of the application to create
42
+ * @param {Object} options - Creation options
43
+ * @param {number} [options.port] - Application port
44
+ * @param {boolean} [options.database] - Requires database
45
+ * @param {boolean} [options.redis] - Requires Redis
46
+ * @param {boolean} [options.storage] - Requires file storage
47
+ * @param {boolean} [options.authentication] - Requires authentication/RBAC
48
+ * @param {string} [options.language] - Runtime language (typescript/python)
49
+ * @param {string} [options.template] - Template to use (e.g., controller, keycloak)
50
+ * @returns {Promise<void>} Resolves when app is created
51
+ * @throws {Error} If creation fails
52
+ *
53
+ * @example
54
+ * await createApp('myapp', { port: 3000, database: true, language: 'typescript' });
55
+ * // Creates builder/ with variables.yaml, env.template, rbac.yaml
56
+ */
57
+ /**
58
+ * Validates app name and initial setup
59
+ * @function validateAppNameAndSetup
60
+ * @param {string} appName - Application name
61
+ * @param {Object} options - Options
62
+ * @returns {Object} Initial paths and type
63
+ */
64
+ function validateAppNameAndSetup(appName, options) {
65
+ if (!appName || typeof appName !== 'string') {
66
+ throw new Error('Application name is required');
67
+ }
68
+
69
+ const initialType = options.type || 'webapp';
70
+ const baseDir = getBaseDirForAppType(initialType);
71
+ const appPath = getAppPath(appName, initialType);
72
+
73
+ return { initialType, baseDir, appPath };
74
+ }
75
+
76
+ /**
77
+ * Handles template validation and loading
78
+ * @async
79
+ * @function handleTemplateSetup
80
+ * @param {Object} options - Options
81
+ * @returns {Promise<Object>} Merged options with template variables
82
+ */
83
+ async function handleTemplateSetup(options) {
84
+ if (options.template) {
85
+ await validateTemplate(options.template);
86
+ }
87
+ const templateVariables = await loadTemplateVariables(options.template);
88
+ return mergeTemplateVariables(options, templateVariables);
89
+ }
90
+
91
+ /**
92
+ * Validates and prepares final app path
93
+ * @async
94
+ * @function prepareFinalAppPath
95
+ * @param {string} appName - Application name
96
+ * @param {Object} config - Configuration
97
+ * @param {string} initialAppPath - Initial app path
98
+ * @returns {Promise<string>} Final app path
99
+ */
100
+ async function prepareFinalAppPath(appName, config, initialAppPath) {
101
+ const finalBaseDir = getBaseDirForAppType(config.type);
102
+ const finalAppPath = getAppPath(appName, config.type);
103
+
104
+ // If path changed, validate the new path
105
+ if (finalAppPath !== initialAppPath) {
106
+ await validateAppDirectoryNotExists(finalAppPath, appName, finalBaseDir);
107
+ }
108
+
109
+ return finalAppPath;
110
+ }
111
+
112
+ /**
113
+ * Generates all application files
114
+ * @async
115
+ * @function generateApplicationFiles
116
+ * @param {string} finalAppPath - Final app path
117
+ * @param {string} appName - Application name
118
+ * @param {Object} config - Configuration
119
+ * @param {Object} options - Options
120
+ * @returns {Promise<string>} Environment conversion message
121
+ */
122
+ async function generateApplicationFiles(finalAppPath, appName, config, options) {
123
+ await fs.mkdir(finalAppPath, { recursive: true });
124
+ await processTemplateFiles(options.template, finalAppPath, appName, options, config);
125
+
126
+ const existingEnv = await readExistingEnv(process.cwd());
127
+ const envConversionMessage = existingEnv
128
+ ? '\n✓ Found existing .env file - sensitive values will be converted to kv:// references'
129
+ : '';
130
+
131
+ await generateConfigFiles(finalAppPath, appName, config, existingEnv);
132
+
133
+ // Generate external system files if type is external
134
+ if (config.type === 'external') {
135
+ const externalGenerator = require('../external-system/generator');
136
+ await externalGenerator.generateExternalSystemFiles(finalAppPath, appName, config);
137
+ }
138
+
139
+ if (options.app) {
140
+ await setupAppFiles(appName, finalAppPath, config, options);
141
+ }
142
+
143
+ await handleGitHubWorkflows(options, config);
144
+ return envConversionMessage;
145
+ }
146
+
147
+ /**
148
+ * Logs application creation for audit
149
+ * @async
150
+ * @function logApplicationCreation
151
+ * @param {string} appName - Application name
152
+ * @param {Object} config - Configuration
153
+ * @param {Object} options - Options
154
+ */
155
+ async function logApplicationCreation(appName, config, options) {
156
+ await auditLogger.logApplicationCreation(appName, {
157
+ language: config.language,
158
+ port: config.port,
159
+ database: config.database,
160
+ redis: config.redis,
161
+ storage: config.storage,
162
+ authentication: config.authentication,
163
+ template: options.template,
164
+ api: null // Local operation, no API involved
165
+ });
166
+ }
167
+
168
+ async function createApp(appName, options = {}) {
169
+ try {
170
+ const { appPath } = validateAppNameAndSetup(appName, options);
171
+ await validateAppCreation(appName, options, appPath, getBaseDirForAppType(options.type || 'webapp'));
172
+
173
+ const mergedOptions = await handleTemplateSetup(options);
174
+ const config = await promptForOptions(appName, mergedOptions);
175
+
176
+ const finalAppPath = await prepareFinalAppPath(appName, config, appPath);
177
+ const envConversionMessage = await generateApplicationFiles(finalAppPath, appName, config, options);
178
+
179
+ displaySuccessMessage(appName, config, envConversionMessage, options.app, finalAppPath);
180
+ await logApplicationCreation(appName, config, options);
181
+ } catch (error) {
182
+ throw new Error(`Failed to create application: ${error.message}`);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Builds a container image for the specified application
188
+ * Auto-detects runtime and generates Dockerfile if needed
189
+ *
190
+ * @async
191
+ * @function buildApp
192
+ * @param {string} appName - Name of the application to build
193
+ * @param {Object} options - Build options
194
+ * @param {string} [options.language] - Override language detection
195
+ * @param {boolean} [options.forceTemplate] - Force rebuild from template
196
+ * @param {string} [options.tag] - Image tag (default: latest)
197
+ * @returns {Promise<string>} Image tag that was built
198
+ * @throws {Error} If build fails or app configuration is invalid
199
+ *
200
+ * @example
201
+ * const imageTag = await buildApp('myapp', { language: 'typescript' });
202
+ * // Returns: 'myapp:latest'
203
+ */
204
+ async function buildApp(appName, options = {}) {
205
+ return build.buildApp(appName, options);
206
+ }
207
+
208
+ /**
209
+ * Detects the runtime language of an application
210
+ * Analyzes project files to determine TypeScript, Python, etc.
211
+ *
212
+ * @function detectLanguage
213
+ * @param {string} appPath - Path to application directory
214
+ * @returns {string} Detected language ('typescript', 'python', etc.)
215
+ * @throws {Error} If language cannot be detected
216
+ *
217
+ * @example
218
+ * const language = detectLanguage('./myapp');
219
+ * // Returns: 'typescript'
220
+ */
221
+ function detectLanguage(appPath) {
222
+ return build.detectLanguage(appPath);
223
+ }
224
+
225
+ /**
226
+ * Generates a Dockerfile from template based on detected language
227
+ * Uses Handlebars templates to create optimized Dockerfiles
228
+ *
229
+ * @async
230
+ * @function generateDockerfile
231
+ * @param {string} appPath - Path to application directory
232
+ * @param {string} language - Target language ('typescript', 'python')
233
+ * @param {Object} config - Application configuration from variables.yaml
234
+ * @returns {Promise<string>} Path to generated Dockerfile
235
+ * @throws {Error} If template generation fails
236
+ *
237
+ * @example
238
+ * const dockerfilePath = await generateDockerfile('./myapp', 'typescript', config);
239
+ * // Returns: './myapp/.aifabrix/Dockerfile.typescript'
240
+ */
241
+ async function generateDockerfile(appPath, language, config) {
242
+ return build.generateDockerfile(appPath, language, config);
243
+ }
244
+
245
+ /**
246
+ * Runs the application locally using Docker
247
+ * Starts container with proper port mapping and environment
248
+ *
249
+ * @async
250
+ * @function runApp
251
+ * @param {string} appName - Name of the application to run
252
+ * @param {Object} options - Run options
253
+ * @param {number} [options.port] - Override local port
254
+ * @param {boolean} [options.debug] - Enable debug output
255
+ * @returns {Promise<void>} Resolves when app is running
256
+ * @throws {Error} If run fails or app is not built
257
+ *
258
+ * @example
259
+ * await runApp('myapp', { port: 3001 });
260
+ * // Application is now running on localhost:3001
261
+ */
262
+ async function runApp(appName, options = {}) {
263
+ return appRun.runApp(appName, options);
264
+ }
265
+
266
+ /**
267
+ * Deploys application to controller
268
+ * @async
269
+ * @function deployApp
270
+ * @param {string} appName - Name of the application
271
+ * @param {Object} options - Deployment options
272
+ * @returns {Promise<void>} Resolves when deployment is complete
273
+ */
274
+ async function deployApp(appName, options = {}) {
275
+ const appDeploy = require('./deploy');
276
+ return appDeploy.deployApp(appName, options);
277
+ }
278
+
279
+ module.exports = {
280
+ createApp,
281
+ buildApp,
282
+ runApp,
283
+ downApp,
284
+ detectLanguage,
285
+ generateDockerfile,
286
+ generateDockerfileForApp,
287
+ pushApp,
288
+ deployApp,
289
+ loadTemplateVariables,
290
+ updateTemplateVariables,
291
+ mergeTemplateVariables,
292
+ checkImageExists: appRun.checkImageExists,
293
+ checkContainerRunning: appRun.checkContainerRunning,
294
+ stopAndRemoveContainer: appRun.stopAndRemoveContainer,
295
+ checkPortAvailable: appRun.checkPortAvailable,
296
+ generateDockerCompose: appRun.generateDockerCompose,
297
+ waitForHealthCheck: appRun.waitForHealthCheck
298
+ };
@@ -9,12 +9,12 @@
9
9
  */
10
10
 
11
11
  const chalk = require('chalk');
12
- const { getConfig, normalizeControllerUrl } = require('./config');
13
- const { getOrRefreshDeviceToken } = require('./utils/token-manager');
14
- const { listEnvironmentApplications } = require('./api/environments.api');
15
- const { formatApiError } = require('./utils/api-error-handler');
16
- const { formatAuthenticationError } = require('./utils/error-formatters/http-status-errors');
17
- const logger = require('./utils/logger');
12
+ const { getConfig, normalizeControllerUrl } = require('../core/config');
13
+ const { getOrRefreshDeviceToken } = require('../utils/token-manager');
14
+ const { listEnvironmentApplications } = require('../api/environments.api');
15
+ const { formatApiError } = require('../utils/api-error-handler');
16
+ const { formatAuthenticationError } = require('../utils/error-formatters/http-status-errors');
17
+ const logger = require('../utils/logger');
18
18
 
19
19
  /**
20
20
  * Extract wrapped array format: { success: true, data: { success: true, data: [...] } }
@@ -12,8 +12,8 @@ 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 pushUtils = require('./push');
16
- const logger = require('./utils/logger');
15
+ const pushUtils = require('../deployment/push');
16
+ const logger = require('../utils/logger');
17
17
 
18
18
  /**
19
19
  * Validate application name format
@@ -70,7 +70,7 @@ function extractImageName(config, appName) {
70
70
  */
71
71
  async function loadPushConfig(appName, options) {
72
72
  // Detect app type and get correct path (integration or builder)
73
- const { detectAppType } = require('./utils/paths');
73
+ const { detectAppType } = require('../utils/paths');
74
74
  const { appPath } = await detectAppType(appName);
75
75
  const configPath = path.join(appPath, 'variables.yaml');
76
76
  try {
@@ -183,7 +183,7 @@ function displayPushResults(registry, imageName, tags) {
183
183
  */
184
184
  async function pushApp(appName, options = {}) {
185
185
  // Check if app type is external - skip push
186
- const { detectAppType } = require('./utils/paths');
186
+ const { detectAppType } = require('../utils/paths');
187
187
  try {
188
188
  const { isExternal } = await detectAppType(appName);
189
189
  if (isExternal) {
@@ -48,7 +48,7 @@ function formatAppDisplayName(appName) {
48
48
  */
49
49
  function _loadReadmeTemplate() {
50
50
  // Use getProjectRoot to reliably find templates in all environments
51
- const { getProjectRoot } = require('./utils/paths');
51
+ const { getProjectRoot } = require('../utils/paths');
52
52
  const projectRoot = getProjectRoot();
53
53
  const templatePath = path.join(projectRoot, 'templates', 'applications', 'README.md.hbs');
54
54
 
@@ -71,32 +71,51 @@ function _loadReadmeTemplate() {
71
71
  * @param {Object} config - Application configuration
72
72
  * @returns {string} README.md content
73
73
  */
74
- function generateReadmeMd(appName, config) {
74
+ /**
75
+ * Extracts service flags from config
76
+ * @function extractServiceFlags
77
+ * @param {Object} config - Application configuration
78
+ * @returns {Object} Service flags object
79
+ */
80
+ function extractServiceFlags(config) {
81
+ return {
82
+ hasDatabase: config.database || config.requires?.database || false,
83
+ hasRedis: config.redis || config.requires?.redis || false,
84
+ hasStorage: config.storage || config.requires?.storage || false,
85
+ hasAuthentication: config.authentication || false
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Builds template context for README generation
91
+ * @function buildReadmeContext
92
+ * @param {string} appName - Application name
93
+ * @param {Object} config - Application configuration
94
+ * @returns {Object} Template context
95
+ */
96
+ function buildReadmeContext(appName, config) {
75
97
  const displayName = formatAppDisplayName(appName);
76
98
  const imageName = `aifabrix/${appName}`;
77
99
  const port = config.port || 3000;
78
100
  // Extract registry from nested structure (config.image.registry) or flattened (config.registry)
79
101
  const registry = config.image?.registry || config.registry || 'myacr.azurecr.io';
80
102
 
81
- const hasDatabase = config.database || config.requires?.database || false;
82
- const hasRedis = config.redis || config.requires?.redis || false;
83
- const hasStorage = config.storage || config.requires?.storage || false;
84
- const hasAuthentication = config.authentication || false;
85
- const hasAnyService = hasDatabase || hasRedis || hasStorage || hasAuthentication;
103
+ const serviceFlags = extractServiceFlags(config);
104
+ const hasAnyService = serviceFlags.hasDatabase || serviceFlags.hasRedis || serviceFlags.hasStorage || serviceFlags.hasAuthentication;
86
105
 
87
- const context = {
106
+ return {
88
107
  appName,
89
108
  displayName,
90
109
  imageName,
91
110
  port,
92
111
  registry,
93
- hasDatabase,
94
- hasRedis,
95
- hasStorage,
96
- hasAuthentication,
112
+ ...serviceFlags,
97
113
  hasAnyService
98
114
  };
115
+ }
99
116
 
117
+ function generateReadmeMd(appName, config) {
118
+ const context = buildReadmeContext(appName, config);
100
119
  // Always generate comprehensive README programmatically to ensure consistency
101
120
  // regardless of template file content
102
121
  return generateComprehensiveReadme(context);
@@ -220,10 +239,12 @@ For more information, see the [AI Fabrix Builder documentation](https://docs.aif
220
239
  * @throws {Error} If file generation fails
221
240
  */
222
241
  async function generateReadmeMdFile(appPath, appName, config) {
242
+ // Ensure directory exists
243
+ await fs.mkdir(appPath, { recursive: true });
223
244
  const readmePath = path.join(appPath, 'README.md');
224
245
  if (!(await fileExists(readmePath))) {
225
246
  const readmeContent = generateReadmeMd(appName, config);
226
- await fs.writeFile(readmePath, readmeContent);
247
+ await fs.writeFile(readmePath, readmeContent, 'utf8');
227
248
  }
228
249
  }
229
250
 
@@ -9,19 +9,19 @@
9
9
  */
10
10
 
11
11
  const chalk = require('chalk');
12
- const logger = require('./utils/logger');
13
- const { saveLocalSecret, isLocalhost } = require('./utils/local-secrets');
14
- const { updateEnvTemplate } = require('./utils/env-template');
15
- const { generateEnvFile } = require('./secrets');
16
- const { registerApplicationSchema, validateAppRegistrationData } = require('./utils/app-register-validator');
12
+ const logger = require('../utils/logger');
13
+ const { saveLocalSecret, isLocalhost } = require('../utils/local-secrets');
14
+ const { updateEnvTemplate } = require('../utils/env-template');
15
+ const { generateEnvFile } = require('../core/secrets');
16
+ const { registerApplicationSchema, validateAppRegistrationData } = require('../utils/app-register-validator');
17
17
  const {
18
18
  loadVariablesYaml,
19
19
  createMinimalAppIfNeeded,
20
20
  extractAppConfiguration
21
- } = require('./utils/app-register-config');
22
- const { checkAuthentication } = require('./utils/app-register-auth');
23
- const { callRegisterApi } = require('./utils/app-register-api');
24
- const { displayRegistrationResults, getEnvironmentPrefix } = require('./utils/app-register-display');
21
+ } = require('../utils/app-register-config');
22
+ const { checkAuthentication } = require('../utils/app-register-auth');
23
+ const { callRegisterApi } = require('../utils/app-register-api');
24
+ const { displayRegistrationResults, getEnvironmentPrefix } = require('../utils/app-register-display');
25
25
 
26
26
  /**
27
27
  * Build registration data payload from app configuration
@@ -9,16 +9,16 @@
9
9
  */
10
10
 
11
11
  const chalk = require('chalk');
12
- const { getConfig, normalizeControllerUrl } = require('./config');
13
- const { getOrRefreshDeviceToken } = require('./utils/token-manager');
14
- const { rotateApplicationSecret } = require('./api/applications.api');
15
- const { formatApiError } = require('./utils/api-error-handler');
16
- const { formatAuthenticationError } = require('./utils/error-formatters/http-status-errors');
17
- const logger = require('./utils/logger');
18
- const { saveLocalSecret, isLocalhost } = require('./utils/local-secrets');
19
- const { updateEnvTemplate } = require('./utils/env-template');
20
- const { getEnvironmentPrefix } = require('./app-register');
21
- const { generateEnvFile } = require('./secrets');
12
+ const { getConfig, normalizeControllerUrl } = require('../core/config');
13
+ const { getOrRefreshDeviceToken } = require('../utils/token-manager');
14
+ const { rotateApplicationSecret } = require('../api/applications.api');
15
+ const { formatApiError } = require('../utils/api-error-handler');
16
+ const { formatAuthenticationError } = require('../utils/error-formatters/http-status-errors');
17
+ const logger = require('../utils/logger');
18
+ const { saveLocalSecret, isLocalhost } = require('../utils/local-secrets');
19
+ const { updateEnvTemplate } = require('../utils/env-template');
20
+ const { getEnvironmentPrefix } = require('./register');
21
+ const { generateEnvFile } = require('../core/secrets');
22
22
 
23
23
  /**
24
24
  * Find device token from config by trying each stored URL
@@ -16,16 +16,16 @@ const chalk = require('chalk');
16
16
  const yaml = require('js-yaml');
17
17
  const { exec } = require('child_process');
18
18
  const { promisify } = require('util');
19
- const validator = require('./validator');
20
- const infra = require('./infra');
21
- const secrets = require('./secrets');
22
- const config = require('./config');
23
- const buildCopy = require('./utils/build-copy');
24
- const logger = require('./utils/logger');
25
- const { waitForHealthCheck } = require('./utils/health-check');
26
- const composeGenerator = require('./utils/compose-generator');
27
- const dockerUtils = require('./utils/docker');
28
- const containerHelpers = require('./utils/app-run-containers');
19
+ const validator = require('../validation/validator');
20
+ const infra = require('../infrastructure');
21
+ const secrets = require('../core/secrets');
22
+ const config = require('../core/config');
23
+ const buildCopy = require('../utils/build-copy');
24
+ const logger = require('../utils/logger');
25
+ const { waitForHealthCheck } = require('../utils/health-check');
26
+ const composeGenerator = require('../utils/compose-generator');
27
+ const dockerUtils = require('../utils/docker');
28
+ const containerHelpers = require('../utils/app-run-containers');
29
29
 
30
30
  const execAsync = promisify(exec);
31
31
 
@@ -10,12 +10,12 @@
10
10
  */
11
11
 
12
12
  const chalk = require('chalk');
13
- const config = require('./config');
14
- const logger = require('./utils/logger');
15
- const { checkPortAvailable, waitForHealthCheck } = require('./utils/health-check');
16
- const composeGenerator = require('./utils/compose-generator');
13
+ const config = require('../core/config');
14
+ const logger = require('../utils/logger');
15
+ const { checkPortAvailable, waitForHealthCheck } = require('../utils/health-check');
16
+ const composeGenerator = require('../utils/compose-generator');
17
17
  // Helper functions extracted to reduce file size and complexity
18
- const helpers = require('./app-run-helpers');
18
+ const helpers = require('./run-helpers');
19
19
 
20
20
  /**
21
21
  * Validate app for run and check if it's an external system
@@ -31,7 +31,7 @@ async function validateAppForRun(appName, _debug) {
31
31
  }
32
32
 
33
33
  // Check if app type is external - skip Docker run
34
- const { detectAppType } = require('./utils/paths');
34
+ const { detectAppType } = require('../utils/paths');
35
35
  try {
36
36
  const { isExternal } = await detectAppType(appName);
37
37
  if (isExternal) {
@@ -13,18 +13,18 @@
13
13
  const fs = require('fs').promises;
14
14
  const fsSync = require('fs');
15
15
  const path = require('path');
16
- const paths = require('./utils/paths');
17
- const { detectAppType, getProjectRoot } = require('./utils/paths');
16
+ const paths = require('../utils/paths');
17
+ const { detectAppType, getProjectRoot } = require('../utils/paths');
18
18
  const chalk = require('chalk');
19
19
  const yaml = require('js-yaml');
20
- const secrets = require('./secrets');
21
- const config = require('./config');
22
- const logger = require('./utils/logger');
23
- const dockerfileUtils = require('./utils/dockerfile-utils');
24
- const dockerBuild = require('./utils/docker-build');
25
- const buildCopy = require('./utils/build-copy');
26
- const { buildDevImageName } = require('./utils/image-name');
27
- const buildHelpers = require('./utils/build-helpers');
20
+ const secrets = require('../core/secrets');
21
+ const config = require('../core/config');
22
+ const logger = require('../utils/logger');
23
+ const dockerfileUtils = require('../utils/dockerfile-utils');
24
+ const dockerBuild = require('../utils/docker-build');
25
+ const buildCopy = require('../utils/build-copy');
26
+ const { buildDevImageName } = require('../utils/image-name');
27
+ const buildHelpers = require('../utils/build-helpers');
28
28
 
29
29
  /**
30
30
  * Loads variables.yaml configuration for an application
@@ -208,7 +208,7 @@ async function postBuildTasks(appName, buildConfig) {
208
208
  async function checkExternalAppType(appName) {
209
209
  const variables = await loadVariablesYaml(appName);
210
210
  if (variables.app && variables.app.type === 'external') {
211
- const generator = require('./generator');
211
+ const generator = require('../generator');
212
212
  const jsonPath = await generator.generateDeployJson(appName);
213
213
  logger.log(chalk.green(`✓ Generated deployment JSON: ${jsonPath}`));
214
214
  return true;
@@ -224,6 +224,51 @@ async function checkExternalAppType(appName) {
224
224
  * @param {Object} options - Build options
225
225
  * @returns {Promise<Object>} Object with devDir, effectiveImageName, imageName, appConfig, and developerId
226
226
  */
227
+ /**
228
+ * Copies application source files if they exist
229
+ * @async
230
+ * @function copyApplicationSourceFiles
231
+ * @param {string} appName - Application name
232
+ * @param {string} devDir - Developer directory
233
+ * @returns {Promise<boolean>} True if files were copied
234
+ */
235
+ async function copyApplicationSourceFiles(appName, devDir) {
236
+ const appsPath = path.join(process.cwd(), 'apps', appName);
237
+ if (fsSync.existsSync(appsPath)) {
238
+ await buildCopy.copyAppSourceFiles(appsPath, devDir);
239
+ logger.log(chalk.green(`✓ Copied application source files from apps/${appName}`));
240
+ return true;
241
+ }
242
+ return false;
243
+ }
244
+
245
+ /**
246
+ * Copies template files if needed
247
+ * @async
248
+ * @function copyTemplateFilesIfNeeded
249
+ * @param {string} devDir - Developer directory
250
+ * @param {string} language - Language type
251
+ * @param {Object} buildConfig - Build configuration
252
+ * @param {Object} options - Build options
253
+ */
254
+ async function copyTemplateFilesIfNeeded(devDir, language, buildConfig, options) {
255
+ const detectedLanguage = options.language || buildConfig.language || detectLanguage(devDir);
256
+ const packageJsonPath = path.join(devDir, 'package.json');
257
+ const requirementsPath = path.join(devDir, 'requirements.txt');
258
+
259
+ if (detectedLanguage === 'typescript' && !fsSync.existsSync(packageJsonPath)) {
260
+ const projectRoot = getProjectRoot();
261
+ const templatePath = path.join(projectRoot, 'templates', 'typescript');
262
+ await buildCopy.copyTemplateFilesToDevDir(templatePath, devDir, detectedLanguage);
263
+ logger.log(chalk.green(`✓ Generated application files from ${detectedLanguage} template`));
264
+ } else if (detectedLanguage === 'python' && !fsSync.existsSync(requirementsPath)) {
265
+ const projectRoot = getProjectRoot();
266
+ const templatePath = path.join(projectRoot, 'templates', 'python');
267
+ await buildCopy.copyTemplateFilesToDevDir(templatePath, devDir, detectedLanguage);
268
+ logger.log(chalk.green(`✓ Generated application files from ${detectedLanguage} template`));
269
+ }
270
+ }
271
+
227
272
  async function prepareDevDirectory(appName, buildConfig, options) {
228
273
  const developerId = await config.getDeveloperId();
229
274
  const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
@@ -236,27 +281,9 @@ async function prepareDevDirectory(appName, buildConfig, options) {
236
281
  const effectiveImageName = buildDevImageName(imageName, developerId);
237
282
 
238
283
  // Check if application source files exist, if not copy from templates
239
- const appsPath = path.join(process.cwd(), 'apps', appName);
240
- if (fsSync.existsSync(appsPath)) {
241
- await buildCopy.copyAppSourceFiles(appsPath, devDir);
242
- logger.log(chalk.green(`✓ Copied application source files from apps/${appName}`));
243
- } else {
244
- // No apps directory - check if we need to copy template files
245
- const language = options.language || buildConfig.language || detectLanguage(devDir);
246
- const packageJsonPath = path.join(devDir, 'package.json');
247
- const requirementsPath = path.join(devDir, 'requirements.txt');
248
-
249
- if (language === 'typescript' && !fsSync.existsSync(packageJsonPath)) {
250
- const projectRoot = getProjectRoot();
251
- const templatePath = path.join(projectRoot, 'templates', 'typescript');
252
- await buildCopy.copyTemplateFilesToDevDir(templatePath, devDir, language);
253
- logger.log(chalk.green(`✓ Generated application files from ${language} template`));
254
- } else if (language === 'python' && !fsSync.existsSync(requirementsPath)) {
255
- const projectRoot = getProjectRoot();
256
- const templatePath = path.join(projectRoot, 'templates', 'python');
257
- await buildCopy.copyTemplateFilesToDevDir(templatePath, devDir, language);
258
- logger.log(chalk.green(`✓ Generated application files from ${language} template`));
259
- }
284
+ const filesCopied = await copyApplicationSourceFiles(appName, devDir);
285
+ if (!filesCopied) {
286
+ await copyTemplateFilesIfNeeded(devDir, null, buildConfig, options);
260
287
  }
261
288
 
262
289
  return { devDir, effectiveImageName, imageName, appConfig };