@aifabrix/builder 2.22.2 → 2.31.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 (63) hide show
  1. package/jest.config.coverage.js +37 -0
  2. package/lib/api/pipeline.api.js +10 -9
  3. package/lib/app-deploy.js +36 -14
  4. package/lib/app-list.js +191 -71
  5. package/lib/app-prompts.js +77 -26
  6. package/lib/app-readme.js +123 -5
  7. package/lib/app-rotate-secret.js +210 -80
  8. package/lib/app-run-helpers.js +200 -172
  9. package/lib/app-run.js +137 -68
  10. package/lib/audit-logger.js +8 -7
  11. package/lib/build.js +161 -250
  12. package/lib/cli.js +73 -65
  13. package/lib/commands/login.js +45 -31
  14. package/lib/commands/logout.js +181 -0
  15. package/lib/commands/secure.js +59 -24
  16. package/lib/config.js +79 -45
  17. package/lib/datasource-deploy.js +89 -29
  18. package/lib/deployer.js +164 -129
  19. package/lib/diff.js +63 -21
  20. package/lib/environment-deploy.js +36 -19
  21. package/lib/external-system-deploy.js +134 -66
  22. package/lib/external-system-download.js +244 -171
  23. package/lib/external-system-test.js +199 -164
  24. package/lib/generator-external.js +145 -72
  25. package/lib/generator-helpers.js +49 -17
  26. package/lib/generator-split.js +105 -58
  27. package/lib/infra.js +101 -131
  28. package/lib/schema/application-schema.json +895 -896
  29. package/lib/schema/env-config.yaml +11 -4
  30. package/lib/template-validator.js +13 -4
  31. package/lib/utils/api.js +8 -8
  32. package/lib/utils/app-register-auth.js +36 -18
  33. package/lib/utils/app-run-containers.js +140 -0
  34. package/lib/utils/auth-headers.js +6 -6
  35. package/lib/utils/build-copy.js +60 -2
  36. package/lib/utils/build-helpers.js +94 -0
  37. package/lib/utils/cli-utils.js +177 -76
  38. package/lib/utils/compose-generator.js +12 -2
  39. package/lib/utils/config-tokens.js +151 -9
  40. package/lib/utils/deployment-errors.js +137 -69
  41. package/lib/utils/deployment-validation-helpers.js +103 -0
  42. package/lib/utils/docker-build.js +57 -0
  43. package/lib/utils/dockerfile-utils.js +13 -3
  44. package/lib/utils/env-copy.js +163 -94
  45. package/lib/utils/env-map.js +226 -86
  46. package/lib/utils/error-formatters/network-errors.js +0 -1
  47. package/lib/utils/external-system-display.js +14 -19
  48. package/lib/utils/external-system-env-helpers.js +107 -0
  49. package/lib/utils/external-system-test-helpers.js +144 -0
  50. package/lib/utils/health-check.js +10 -8
  51. package/lib/utils/infra-status.js +123 -0
  52. package/lib/utils/paths.js +228 -49
  53. package/lib/utils/schema-loader.js +125 -57
  54. package/lib/utils/token-manager.js +3 -3
  55. package/lib/utils/yaml-preserve.js +55 -16
  56. package/lib/validate.js +87 -89
  57. package/package.json +7 -5
  58. package/scripts/ci-fix.sh +19 -0
  59. package/scripts/ci-simulate.sh +19 -0
  60. package/scripts/install-local.js +210 -0
  61. package/templates/applications/miso-controller/test.yaml +1 -0
  62. package/templates/python/Dockerfile.hbs +8 -45
  63. package/templates/typescript/Dockerfile.hbs +8 -42
package/lib/app-run.js CHANGED
@@ -17,6 +17,131 @@ const composeGenerator = require('./utils/compose-generator');
17
17
  // Helper functions extracted to reduce file size and complexity
18
18
  const helpers = require('./app-run-helpers');
19
19
 
20
+ /**
21
+ * Validate app for run and check if it's an external system
22
+ * @async
23
+ * @param {string} appName - Application name
24
+ * @param {boolean} _debug - Debug flag (unused)
25
+ * @returns {Promise<boolean>} True if should continue, false if external system
26
+ * @throws {Error} If app name is invalid
27
+ */
28
+ async function validateAppForRun(appName, _debug) {
29
+ if (!appName || typeof appName !== 'string') {
30
+ throw new Error('Application name is required');
31
+ }
32
+
33
+ // Check if app type is external - skip Docker run
34
+ const { detectAppType } = require('./utils/paths');
35
+ try {
36
+ const { isExternal } = await detectAppType(appName);
37
+ if (isExternal) {
38
+ logger.log(chalk.yellow('⚠️ External systems don\'t run as Docker containers.'));
39
+ logger.log(chalk.blue('Use "aifabrix build" to deploy to dataplane, then test via OpenAPI endpoints.'));
40
+ return false;
41
+ }
42
+ } catch (error) {
43
+ // If detection fails, continue with normal run
44
+ // (detectAppType throws if app doesn't exist, which is fine for run command)
45
+ }
46
+
47
+ return true;
48
+ }
49
+
50
+ /**
51
+ * Check if container is running and stop it if needed
52
+ * @async
53
+ * @param {string} appName - Application name
54
+ * @param {number|string} developerId - Developer ID
55
+ * @param {boolean} debug - Debug flag
56
+ * @returns {Promise<void>}
57
+ */
58
+ async function checkAndStopContainer(appName, developerId, debug) {
59
+ const containerRunning = await helpers.checkContainerRunning(appName, developerId, debug);
60
+ if (!containerRunning) {
61
+ return;
62
+ }
63
+
64
+ // Dev 0: aifabrix-{appName} (no dev-0 suffix), Dev > 0: aifabrix-dev{id}-{appName}
65
+ const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
66
+ const containerName = idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
67
+ logger.log(chalk.yellow(`Container ${containerName} is already running`));
68
+ await helpers.stopAndRemoveContainer(appName, developerId, debug);
69
+ }
70
+
71
+ /**
72
+ * Calculate host port and validate it's available
73
+ * @async
74
+ * @param {Object} appConfig - Application configuration
75
+ * @param {Object} options - Run options
76
+ * @param {number} [options.port] - Override port
77
+ * @param {boolean} debug - Debug flag
78
+ * @returns {Promise<number>} Host port
79
+ * @throws {Error} If port is not available
80
+ */
81
+ async function calculateHostPort(appConfig, options, debug) {
82
+ const basePort = appConfig.port || 3000;
83
+ const idNum = typeof appConfig.developerId === 'string' ? parseInt(appConfig.developerId, 10) : appConfig.developerId;
84
+ const hostPort = options.port || (idNum === 0 ? basePort : basePort + (idNum * 100));
85
+
86
+ if (debug) {
87
+ logger.log(chalk.gray(`[DEBUG] Host port: ${hostPort} (${options.port ? 'CLI override' : 'dev-specific'}), Container port: ${appConfig.build?.containerPort || appConfig.port || 3000} (unchanged)`));
88
+ }
89
+
90
+ const portAvailable = await checkPortAvailable(hostPort);
91
+ if (debug) {
92
+ logger.log(chalk.gray(`[DEBUG] Port ${hostPort} available: ${portAvailable}`));
93
+ }
94
+
95
+ if (!portAvailable) {
96
+ throw new Error(`Port ${hostPort} is already in use. Try --port <alternative>`);
97
+ }
98
+
99
+ return hostPort;
100
+ }
101
+
102
+ /**
103
+ * Load and configure application
104
+ * @async
105
+ * @param {string} appName - Application name
106
+ * @param {boolean} debug - Debug flag
107
+ * @returns {Promise<Object>} Application configuration with developerId
108
+ */
109
+ async function loadAndConfigureApp(appName, debug) {
110
+ const appConfig = await helpers.validateAppConfiguration(appName);
111
+ const developerId = await config.getDeveloperId();
112
+ appConfig.developerId = developerId;
113
+
114
+ if (debug) {
115
+ logger.log(chalk.gray(`[DEBUG] Configuration loaded: port=${appConfig.port || 'default'}, healthCheck.path=${appConfig.healthCheck?.path || '/health'}, developerId=${appConfig.developerId}`));
116
+ }
117
+
118
+ return appConfig;
119
+ }
120
+
121
+ /**
122
+ * Start application container and display status
123
+ * @async
124
+ * @param {string} appName - Application name
125
+ * @param {string} tempComposePath - Path to compose file
126
+ * @param {number} hostPort - Host port
127
+ * @param {Object} appConfig - Application configuration
128
+ * @param {boolean} debug - Debug flag
129
+ * @throws {Error} If container start fails
130
+ */
131
+ async function startAppContainer(appName, tempComposePath, hostPort, appConfig, debug) {
132
+ try {
133
+ await helpers.startContainer(appName, tempComposePath, hostPort, appConfig, debug);
134
+ await helpers.displayRunStatus(appName, hostPort, appConfig);
135
+ } catch (error) {
136
+ logger.log(chalk.yellow(`\n⚠️ Compose file preserved at: ${tempComposePath}`));
137
+ logger.log(chalk.yellow(' Review the file to debug issues'));
138
+ if (debug) {
139
+ logger.log(chalk.gray(`[DEBUG] Error during container start: ${error.message}`));
140
+ }
141
+ throw error;
142
+ }
143
+ }
144
+
20
145
  /**
21
146
  * Runs the application locally using Docker
22
147
  * Starts container with proper port mapping and environment
@@ -43,65 +168,23 @@ async function runApp(appName, options = {}) {
43
168
  }
44
169
 
45
170
  try {
46
- // Validate app name first
47
- if (!appName || typeof appName !== 'string') {
48
- throw new Error('Application name is required');
49
- }
50
-
51
- // Check if app type is external - skip Docker run
52
- const { detectAppType } = require('./utils/paths');
53
- try {
54
- const { isExternal } = await detectAppType(appName);
55
- if (isExternal) {
56
- logger.log(chalk.yellow('⚠️ External systems don\'t run as Docker containers.'));
57
- logger.log(chalk.blue('Use "aifabrix build" to deploy to dataplane, then test via OpenAPI endpoints.'));
58
- return;
59
- }
60
- } catch (error) {
61
- // If detection fails, continue with normal run
62
- // (detectAppType throws if app doesn't exist, which is fine for run command)
171
+ // Validate app for run and check if external
172
+ const shouldContinue = await validateAppForRun(appName, debug);
173
+ if (!shouldContinue) {
174
+ return;
63
175
  }
64
176
 
65
- // Validate app name and load configuration
66
- const appConfig = await helpers.validateAppConfiguration(appName);
67
-
68
- // Load developer ID once from config module - it's now cached and available as config.developerId
69
- // Developer ID: 0 = default infra, > 0 = developer-specific
70
- const developerId = await config.getDeveloperId(); // Load and cache developer ID
71
- appConfig.developerId = developerId; // Use developer ID in config
72
-
73
- if (debug) {
74
- logger.log(chalk.gray(`[DEBUG] Configuration loaded: port=${appConfig.port || 'default'}, healthCheck.path=${appConfig.healthCheck?.path || '/health'}, developerId=${appConfig.developerId}`));
75
- }
177
+ // Load and configure application
178
+ const appConfig = await loadAndConfigureApp(appName, debug);
76
179
 
77
180
  // Check prerequisites: image and infrastructure
78
181
  await helpers.checkPrerequisites(appName, appConfig, debug);
79
182
 
80
- // Check if container is already running
81
- const containerRunning = await helpers.checkContainerRunning(appName, appConfig.developerId, debug);
82
- if (containerRunning) {
83
- // Dev 0: aifabrix-{appName} (no dev-0 suffix), Dev > 0: aifabrix-dev{id}-{appName}
84
- const idNum2 = typeof appConfig.developerId === 'string' ? parseInt(appConfig.developerId, 10) : appConfig.developerId;
85
- const containerName = idNum2 === 0 ? `aifabrix-${appName}` : `aifabrix-dev${appConfig.developerId}-${appName}`;
86
- logger.log(chalk.yellow(`Container ${containerName} is already running`));
87
- await helpers.stopAndRemoveContainer(appName, appConfig.developerId, debug);
88
- }
183
+ // Check if container is already running and stop it if needed
184
+ await checkAndStopContainer(appName, appConfig.developerId, debug);
89
185
 
90
- // Calculate host port: use dev-specific port offset if not overridden
91
- // IMPORTANT: Container port (for Dockerfile) stays unchanged from appConfig.port
92
- const basePort = appConfig.port || 3000;
93
- const idNum3 = typeof appConfig.developerId === 'string' ? parseInt(appConfig.developerId, 10) : appConfig.developerId;
94
- const hostPort = options.port || (idNum3 === 0 ? basePort : basePort + (idNum3 * 100));
95
- if (debug) {
96
- logger.log(chalk.gray(`[DEBUG] Host port: ${hostPort} (${options.port ? 'CLI override' : 'dev-specific'}), Container port: ${appConfig.build?.containerPort || appConfig.port || 3000} (unchanged)`));
97
- }
98
- const portAvailable = await checkPortAvailable(hostPort);
99
- if (debug) {
100
- logger.log(chalk.gray(`[DEBUG] Port ${hostPort} available: ${portAvailable}`));
101
- }
102
- if (!portAvailable) {
103
- throw new Error(`Port ${hostPort} is already in use. Try --port <alternative>`);
104
- }
186
+ // Calculate host port and validate it's available
187
+ const hostPort = await calculateHostPort(appConfig, options, debug);
105
188
 
106
189
  // Prepare environment: ensure .env file and generate Docker Compose
107
190
  const tempComposePath = await helpers.prepareEnvironment(appName, appConfig, options);
@@ -109,22 +192,8 @@ async function runApp(appName, options = {}) {
109
192
  logger.log(chalk.gray(`[DEBUG] Compose file generated: ${tempComposePath}`));
110
193
  }
111
194
 
112
- try {
113
- // Start container and wait for health check
114
- await helpers.startContainer(appName, tempComposePath, hostPort, appConfig, debug);
115
-
116
- // Display success message
117
- await helpers.displayRunStatus(appName, hostPort, appConfig);
118
-
119
- } catch (error) {
120
- // Keep the compose file for debugging - don't delete on error
121
- logger.log(chalk.yellow(`\n⚠️ Compose file preserved at: ${tempComposePath}`));
122
- logger.log(chalk.yellow(' Review the file to debug issues'));
123
- if (debug) {
124
- logger.log(chalk.gray(`[DEBUG] Error during container start: ${error.message}`));
125
- }
126
- throw error;
127
- }
195
+ // Start container and display status
196
+ await startAppContainer(appName, tempComposePath, hostPort, appConfig, debug);
128
197
 
129
198
  } catch (error) {
130
199
  if (debug) {
@@ -258,14 +258,15 @@ async function logApplicationCreation(appName, options = {}) {
258
258
  * Logs API call attempt with full details for audit trail
259
259
  * Logs both successful and failed API calls for troubleshooting
260
260
  *
261
- * @param {string} url - API endpoint URL
262
- * @param {Object} options - Fetch options (method, headers, etc.)
263
- * @param {number} statusCode - HTTP status code
264
- * @param {number} duration - Request duration in milliseconds
265
- * @param {boolean} success - Whether the request was successful
266
- * @param {Object} errorInfo - Error information (if failed)
261
+ * @param {Object} params - Function parameters
262
+ * @param {string} params.url - API endpoint URL
263
+ * @param {Object} params.options - Fetch options (method, headers, etc.)
264
+ * @param {number} params.statusCode - HTTP status code
265
+ * @param {number} params.duration - Request duration in milliseconds
266
+ * @param {boolean} params.success - Whether the request was successful
267
+ * @param {Object} [params.errorInfo] - Error information (if failed)
267
268
  */
268
- async function logApiCall(url, options, statusCode, duration, success, errorInfo = {}) {
269
+ async function logApiCall({ url, options, statusCode, duration, success, errorInfo = {} }) {
269
270
  const method = options.method || 'GET';
270
271
  const path = extractPathFromUrl(url);
271
272