@aifabrix/builder 2.22.2 → 2.31.0
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.
- package/jest.config.coverage.js +37 -0
- package/lib/api/pipeline.api.js +10 -9
- package/lib/app-deploy.js +36 -14
- package/lib/app-list.js +191 -71
- package/lib/app-prompts.js +77 -26
- package/lib/app-readme.js +123 -5
- package/lib/app-rotate-secret.js +101 -57
- package/lib/app-run-helpers.js +200 -172
- package/lib/app-run.js +137 -68
- package/lib/audit-logger.js +8 -7
- package/lib/build.js +161 -250
- package/lib/cli.js +73 -65
- package/lib/commands/login.js +45 -31
- package/lib/commands/logout.js +181 -0
- package/lib/commands/secure.js +59 -24
- package/lib/config.js +79 -45
- package/lib/datasource-deploy.js +89 -29
- package/lib/deployer.js +164 -129
- package/lib/diff.js +63 -21
- package/lib/environment-deploy.js +36 -19
- package/lib/external-system-deploy.js +134 -66
- package/lib/external-system-download.js +244 -171
- package/lib/external-system-test.js +199 -164
- package/lib/generator-external.js +145 -72
- package/lib/generator-helpers.js +49 -17
- package/lib/generator-split.js +105 -58
- package/lib/infra.js +101 -131
- package/lib/schema/application-schema.json +895 -896
- package/lib/schema/env-config.yaml +11 -4
- package/lib/template-validator.js +13 -4
- package/lib/utils/api.js +8 -8
- package/lib/utils/app-register-auth.js +36 -18
- package/lib/utils/app-run-containers.js +140 -0
- package/lib/utils/auth-headers.js +6 -6
- package/lib/utils/build-copy.js +60 -2
- package/lib/utils/build-helpers.js +94 -0
- package/lib/utils/cli-utils.js +177 -76
- package/lib/utils/compose-generator.js +12 -2
- package/lib/utils/config-tokens.js +151 -9
- package/lib/utils/deployment-errors.js +137 -69
- package/lib/utils/deployment-validation-helpers.js +103 -0
- package/lib/utils/docker-build.js +57 -0
- package/lib/utils/dockerfile-utils.js +13 -3
- package/lib/utils/env-copy.js +163 -94
- package/lib/utils/env-map.js +226 -86
- package/lib/utils/error-formatters/network-errors.js +0 -1
- package/lib/utils/external-system-display.js +14 -19
- package/lib/utils/external-system-env-helpers.js +107 -0
- package/lib/utils/external-system-test-helpers.js +144 -0
- package/lib/utils/health-check.js +10 -8
- package/lib/utils/infra-status.js +123 -0
- package/lib/utils/paths.js +228 -49
- package/lib/utils/schema-loader.js +125 -57
- package/lib/utils/token-manager.js +3 -3
- package/lib/utils/yaml-preserve.js +55 -16
- package/lib/validate.js +87 -89
- package/package.json +4 -4
- package/scripts/ci-fix.sh +19 -0
- package/scripts/ci-simulate.sh +19 -0
- package/templates/applications/miso-controller/test.yaml +1 -0
- package/templates/python/Dockerfile.hbs +8 -45
- package/templates/typescript/Dockerfile.hbs +8 -42
package/lib/app-run-helpers.js
CHANGED
|
@@ -25,119 +25,21 @@ const logger = require('./utils/logger');
|
|
|
25
25
|
const { waitForHealthCheck } = require('./utils/health-check');
|
|
26
26
|
const composeGenerator = require('./utils/compose-generator');
|
|
27
27
|
const dockerUtils = require('./utils/docker');
|
|
28
|
+
const containerHelpers = require('./utils/app-run-containers');
|
|
28
29
|
|
|
29
30
|
const execAsync = promisify(exec);
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* @param {boolean} [debug=false] - Enable debug logging
|
|
36
|
-
* @returns {Promise<boolean>} True if image exists
|
|
37
|
-
*/
|
|
38
|
-
async function checkImageExists(imageName, tag = 'latest', debug = false) {
|
|
39
|
-
try {
|
|
40
|
-
const fullImageName = `${imageName}:${tag}`;
|
|
41
|
-
const cmd = `docker images --format "{{.Repository}}:{{.Tag}}" --filter "reference=${fullImageName}"`;
|
|
42
|
-
if (debug) {
|
|
43
|
-
logger.log(chalk.gray(`[DEBUG] Executing: ${cmd}`));
|
|
44
|
-
}
|
|
45
|
-
const { stdout } = await execAsync(cmd);
|
|
46
|
-
const lines = stdout.trim().split('\n').filter(line => line.trim() !== '');
|
|
47
|
-
const exists = lines.some(line => line.trim() === fullImageName);
|
|
48
|
-
if (debug) {
|
|
49
|
-
logger.log(chalk.gray(`[DEBUG] Image ${fullImageName} exists: ${exists}`));
|
|
50
|
-
}
|
|
51
|
-
return exists;
|
|
52
|
-
} catch (error) {
|
|
53
|
-
if (debug) {
|
|
54
|
-
logger.log(chalk.gray(`[DEBUG] Image check failed: ${error.message}`));
|
|
55
|
-
}
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Checks if container is already running
|
|
62
|
-
* @param {string} appName - Application name
|
|
63
|
-
* @param {number|string} developerId - Developer ID (0 = default infra, > 0 = developer-specific; string allowed)
|
|
64
|
-
* @param {boolean} [debug=false] - Enable debug logging
|
|
65
|
-
* @returns {Promise<boolean>} True if container is running
|
|
66
|
-
*/
|
|
67
|
-
async function checkContainerRunning(appName, developerId, debug = false) {
|
|
68
|
-
try {
|
|
69
|
-
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
70
|
-
const containerName = idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
|
|
71
|
-
const cmd = `docker ps --filter "name=${containerName}" --format "{{.Names}}"`;
|
|
72
|
-
if (debug) {
|
|
73
|
-
logger.log(chalk.gray(`[DEBUG] Executing: ${cmd}`));
|
|
74
|
-
}
|
|
75
|
-
const { stdout } = await execAsync(cmd);
|
|
76
|
-
const isRunning = stdout.trim() === containerName;
|
|
77
|
-
if (debug) {
|
|
78
|
-
logger.log(chalk.gray(`[DEBUG] Container ${containerName} running: ${isRunning}`));
|
|
79
|
-
if (isRunning) {
|
|
80
|
-
const statusCmd = `docker ps --filter "name=${containerName}" --format "{{.Status}}"`;
|
|
81
|
-
const { stdout: status } = await execAsync(statusCmd);
|
|
82
|
-
const portsCmd = `docker ps --filter "name=${containerName}" --format "{{.Ports}}"`;
|
|
83
|
-
const { stdout: ports } = await execAsync(portsCmd);
|
|
84
|
-
logger.log(chalk.gray(`[DEBUG] Container status: ${status.trim()}`));
|
|
85
|
-
logger.log(chalk.gray(`[DEBUG] Container ports: ${ports.trim()}`));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return isRunning;
|
|
89
|
-
} catch (error) {
|
|
90
|
-
if (debug) {
|
|
91
|
-
logger.log(chalk.gray(`[DEBUG] Container check failed: ${error.message}`));
|
|
92
|
-
}
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
32
|
+
// Re-export container helper functions
|
|
33
|
+
const checkImageExists = containerHelpers.checkImageExists;
|
|
34
|
+
const checkContainerRunning = containerHelpers.checkContainerRunning;
|
|
35
|
+
const stopAndRemoveContainer = containerHelpers.stopAndRemoveContainer;
|
|
96
36
|
|
|
97
37
|
/**
|
|
98
|
-
*
|
|
38
|
+
* Check if running from builder directory
|
|
99
39
|
* @param {string} appName - Application name
|
|
100
|
-
* @
|
|
101
|
-
* @param {boolean} [debug=false] - Enable debug logging
|
|
40
|
+
* @throws {Error} If running from builder directory
|
|
102
41
|
*/
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
106
|
-
const containerName = idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
|
|
107
|
-
logger.log(chalk.yellow(`Stopping existing container ${containerName}...`));
|
|
108
|
-
const stopCmd = `docker stop ${containerName}`;
|
|
109
|
-
if (debug) {
|
|
110
|
-
logger.log(chalk.gray(`[DEBUG] Executing: ${stopCmd}`));
|
|
111
|
-
}
|
|
112
|
-
await execAsync(stopCmd);
|
|
113
|
-
const rmCmd = `docker rm ${containerName}`;
|
|
114
|
-
if (debug) {
|
|
115
|
-
logger.log(chalk.gray(`[DEBUG] Executing: ${rmCmd}`));
|
|
116
|
-
}
|
|
117
|
-
await execAsync(rmCmd);
|
|
118
|
-
logger.log(chalk.green(`✓ Container ${containerName} stopped and removed`));
|
|
119
|
-
} catch (error) {
|
|
120
|
-
if (debug) {
|
|
121
|
-
logger.log(chalk.gray(`[DEBUG] Stop/remove container error: ${error.message}`));
|
|
122
|
-
}
|
|
123
|
-
const idNum2 = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
124
|
-
const containerName = idNum2 === 0 ? `aifabrix-${appName}` : `aifabrix-dev${developerId}-${appName}`;
|
|
125
|
-
logger.log(chalk.gray(`Container ${containerName} was not running`));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Validates app name and loads configuration
|
|
131
|
-
* @async
|
|
132
|
-
* @param {string} appName - Application name
|
|
133
|
-
* @returns {Promise<Object>} Application configuration
|
|
134
|
-
* @throws {Error} If validation fails
|
|
135
|
-
*/
|
|
136
|
-
async function validateAppConfiguration(appName) {
|
|
137
|
-
if (!appName || typeof appName !== 'string') {
|
|
138
|
-
throw new Error('Application name is required');
|
|
139
|
-
}
|
|
140
|
-
|
|
42
|
+
function checkBuilderDirectory(appName) {
|
|
141
43
|
const currentDir = process.cwd();
|
|
142
44
|
const normalizedPath = currentDir.replace(/\\/g, '/');
|
|
143
45
|
const expectedBuilderPath = `builder/${appName}`;
|
|
@@ -152,8 +54,17 @@ async function validateAppConfiguration(appName) {
|
|
|
152
54
|
` aifabrix run ${appName}`
|
|
153
55
|
);
|
|
154
56
|
}
|
|
57
|
+
}
|
|
155
58
|
|
|
156
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Load and validate config file exists
|
|
61
|
+
* @param {string} appName - Application name
|
|
62
|
+
* @returns {Object} Application configuration
|
|
63
|
+
* @throws {Error} If config file not found
|
|
64
|
+
*/
|
|
65
|
+
function loadAppConfig(appName) {
|
|
66
|
+
const currentDir = process.cwd();
|
|
67
|
+
const configPath = path.join(currentDir, 'builder', appName, 'variables.yaml');
|
|
157
68
|
if (!fsSync.existsSync(configPath)) {
|
|
158
69
|
const expectedDir = path.join(currentDir, 'builder', appName);
|
|
159
70
|
throw new Error(
|
|
@@ -166,29 +77,58 @@ async function validateAppConfiguration(appName) {
|
|
|
166
77
|
}
|
|
167
78
|
|
|
168
79
|
const configContent = fsSync.readFileSync(configPath, 'utf8');
|
|
169
|
-
|
|
80
|
+
return yaml.load(configContent);
|
|
81
|
+
}
|
|
170
82
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Format validation errors
|
|
85
|
+
* @param {Object} validation - Validation result
|
|
86
|
+
* @returns {Array<string>} Formatted error messages
|
|
87
|
+
*/
|
|
88
|
+
function formatValidationErrors(validation) {
|
|
89
|
+
const allErrors = [];
|
|
174
90
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
91
|
+
if (validation.variables && validation.variables.errors && validation.variables.errors.length > 0) {
|
|
92
|
+
allErrors.push('variables.yaml:');
|
|
93
|
+
allErrors.push(...validation.variables.errors.map(err => ` ${err}`));
|
|
94
|
+
}
|
|
95
|
+
if (validation.rbac && validation.rbac.errors && validation.rbac.errors.length > 0) {
|
|
96
|
+
allErrors.push('rbac.yaml:');
|
|
97
|
+
allErrors.push(...validation.rbac.errors.map(err => ` ${err}`));
|
|
98
|
+
}
|
|
99
|
+
if (validation.env && validation.env.errors && validation.env.errors.length > 0) {
|
|
100
|
+
allErrors.push('env.template:');
|
|
101
|
+
allErrors.push(...validation.env.errors.map(err => ` ${err}`));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return allErrors;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Validates app name and loads configuration
|
|
109
|
+
* @async
|
|
110
|
+
* @param {string} appName - Application name
|
|
111
|
+
* @returns {Promise<Object>} Application configuration
|
|
112
|
+
* @throws {Error} If validation fails
|
|
113
|
+
*/
|
|
114
|
+
async function validateAppConfiguration(appName) {
|
|
115
|
+
if (!appName || typeof appName !== 'string') {
|
|
116
|
+
throw new Error('Application name is required');
|
|
117
|
+
}
|
|
187
118
|
|
|
119
|
+
// Check if running from builder directory
|
|
120
|
+
checkBuilderDirectory(appName);
|
|
121
|
+
|
|
122
|
+
// Load config
|
|
123
|
+
const appConfig = loadAppConfig(appName);
|
|
124
|
+
|
|
125
|
+
// Validate configuration
|
|
126
|
+
const validation = await validator.validateApplication(appName);
|
|
127
|
+
if (!validation.valid) {
|
|
128
|
+
const allErrors = formatValidationErrors(validation);
|
|
188
129
|
if (allErrors.length === 0) {
|
|
189
130
|
throw new Error('Configuration validation failed');
|
|
190
131
|
}
|
|
191
|
-
|
|
192
132
|
throw new Error(`Configuration validation failed:\n${allErrors.join('\n')}`);
|
|
193
133
|
}
|
|
194
134
|
|
|
@@ -235,22 +175,27 @@ async function checkPrerequisites(appName, appConfig, debug = false) {
|
|
|
235
175
|
}
|
|
236
176
|
|
|
237
177
|
/**
|
|
238
|
-
*
|
|
178
|
+
* Ensure dev directory exists
|
|
239
179
|
* @async
|
|
240
180
|
* @param {string} appName - Application name
|
|
241
|
-
* @param {
|
|
242
|
-
* @
|
|
243
|
-
* @returns {Promise<string>} Path to generated compose file
|
|
181
|
+
* @param {string} developerId - Developer ID
|
|
182
|
+
* @returns {Promise<string>} Dev directory path
|
|
244
183
|
*/
|
|
245
|
-
async function
|
|
246
|
-
const developerId = await config.getDeveloperId();
|
|
184
|
+
async function ensureDevDirectory(appName, developerId) {
|
|
247
185
|
const devDir = buildCopy.getDevDirectory(appName, developerId);
|
|
248
|
-
|
|
249
186
|
if (!fsSync.existsSync(devDir)) {
|
|
250
187
|
await buildCopy.copyBuilderToDevDirectory(appName, developerId);
|
|
251
188
|
}
|
|
189
|
+
return devDir;
|
|
190
|
+
}
|
|
252
191
|
|
|
253
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Generate or update .env file for Docker
|
|
194
|
+
* @async
|
|
195
|
+
* @param {string} appName - Application name
|
|
196
|
+
* @param {string} builderEnvPath - Path to builder .env file
|
|
197
|
+
*/
|
|
198
|
+
async function ensureEnvFile(appName, builderEnvPath) {
|
|
254
199
|
if (!fsSync.existsSync(builderEnvPath)) {
|
|
255
200
|
logger.log(chalk.yellow('Generating .env file from template...'));
|
|
256
201
|
await secrets.generateEnvFile(appName, null, 'docker');
|
|
@@ -258,60 +203,113 @@ async function prepareEnvironment(appName, appConfig, options) {
|
|
|
258
203
|
logger.log(chalk.blue('Updating .env file for Docker environment...'));
|
|
259
204
|
await secrets.generateEnvFile(appName, null, 'docker');
|
|
260
205
|
}
|
|
206
|
+
}
|
|
261
207
|
|
|
262
|
-
|
|
208
|
+
/**
|
|
209
|
+
* Copy .env file to dev directory
|
|
210
|
+
* @async
|
|
211
|
+
* @param {string} builderEnvPath - Path to builder .env file
|
|
212
|
+
* @param {string} devEnvPath - Path to dev .env file
|
|
213
|
+
*/
|
|
214
|
+
async function copyEnvToDev(builderEnvPath, devEnvPath) {
|
|
263
215
|
if (fsSync.existsSync(builderEnvPath)) {
|
|
264
216
|
await fs.copyFile(builderEnvPath, devEnvPath);
|
|
265
217
|
}
|
|
218
|
+
}
|
|
266
219
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
220
|
+
/**
|
|
221
|
+
* Handle envOutputPath configuration
|
|
222
|
+
* @async
|
|
223
|
+
* @param {string} appName - Application name
|
|
224
|
+
* @param {string} variablesPath - Path to variables.yaml
|
|
225
|
+
* @param {string} builderEnvPath - Path to builder .env file
|
|
226
|
+
* @param {string} devEnvPath - Path to dev .env file
|
|
227
|
+
*/
|
|
228
|
+
async function handleEnvOutputPath(appName, variablesPath, builderEnvPath, devEnvPath) {
|
|
229
|
+
if (!fsSync.existsSync(variablesPath)) {
|
|
230
|
+
return;
|
|
279
231
|
}
|
|
280
232
|
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
233
|
+
const variablesContent = fsSync.readFileSync(variablesPath, 'utf8');
|
|
234
|
+
const variables = yaml.load(variablesContent);
|
|
235
|
+
|
|
236
|
+
if (variables?.build?.envOutputPath && variables.build.envOutputPath !== null) {
|
|
237
|
+
logger.log(chalk.blue('Ensuring .env file in apps/ directory is updated for Docker...'));
|
|
238
|
+
await secrets.generateEnvFile(appName, null, 'docker');
|
|
239
|
+
await copyEnvToDev(builderEnvPath, devEnvPath);
|
|
287
240
|
}
|
|
288
|
-
|
|
241
|
+
}
|
|
289
242
|
|
|
290
|
-
|
|
291
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Calculate compose port from options or app config
|
|
245
|
+
* @param {Object} options - Run options
|
|
246
|
+
* @param {Object} appConfig - Application configuration
|
|
247
|
+
* @param {string} developerId - Developer ID
|
|
248
|
+
* @returns {number} Port number
|
|
249
|
+
*/
|
|
250
|
+
function calculateComposePort(options, appConfig, developerId) {
|
|
251
|
+
if (options.port) {
|
|
252
|
+
return options.port;
|
|
292
253
|
}
|
|
254
|
+
const basePort = appConfig.port || 3000;
|
|
255
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
256
|
+
return idNum === 0 ? basePort : basePort + (idNum * 100);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Generate and write Docker Compose file
|
|
261
|
+
* @async
|
|
262
|
+
* @param {string} appName - Application name
|
|
263
|
+
* @param {Object} appConfig - Application configuration
|
|
264
|
+
* @param {Object} composeOptions - Compose options
|
|
265
|
+
* @param {string} devDir - Dev directory path
|
|
266
|
+
* @returns {Promise<string>} Path to compose file
|
|
267
|
+
*/
|
|
268
|
+
async function generateComposeFile(appName, appConfig, composeOptions, devDir) {
|
|
269
|
+
logger.log(chalk.blue('Generating Docker Compose configuration...'));
|
|
270
|
+
const composeContent = await composeGenerator.generateDockerCompose(appName, appConfig, composeOptions);
|
|
293
271
|
const tempComposePath = path.join(devDir, 'docker-compose.yaml');
|
|
294
272
|
await fs.writeFile(tempComposePath, composeContent);
|
|
295
|
-
|
|
296
273
|
return tempComposePath;
|
|
297
274
|
}
|
|
298
275
|
|
|
299
276
|
/**
|
|
300
|
-
*
|
|
277
|
+
* Prepares environment: ensures .env file and generates Docker Compose
|
|
301
278
|
* @async
|
|
302
279
|
* @param {string} appName - Application name
|
|
303
|
-
* @param {string} composePath - Path to Docker Compose file
|
|
304
|
-
* @param {number} port - Application port
|
|
305
280
|
* @param {Object} appConfig - Application configuration
|
|
306
|
-
* @param {
|
|
307
|
-
* @
|
|
281
|
+
* @param {Object} options - Run options
|
|
282
|
+
* @returns {Promise<string>} Path to generated compose file
|
|
308
283
|
*/
|
|
309
|
-
async function
|
|
310
|
-
|
|
284
|
+
async function prepareEnvironment(appName, appConfig, options) {
|
|
285
|
+
const developerId = await config.getDeveloperId();
|
|
286
|
+
const devDir = await ensureDevDirectory(appName, developerId);
|
|
311
287
|
|
|
312
|
-
//
|
|
313
|
-
const
|
|
288
|
+
// Generate/update .env file
|
|
289
|
+
const builderEnvPath = path.join(process.cwd(), 'builder', appName, '.env');
|
|
290
|
+
await ensureEnvFile(appName, builderEnvPath);
|
|
314
291
|
|
|
292
|
+
// Copy .env to dev directory
|
|
293
|
+
const devEnvPath = path.join(devDir, '.env');
|
|
294
|
+
await copyEnvToDev(builderEnvPath, devEnvPath);
|
|
295
|
+
|
|
296
|
+
// Handle envOutputPath if configured
|
|
297
|
+
const variablesPath = path.join(devDir, 'variables.yaml');
|
|
298
|
+
await handleEnvOutputPath(appName, variablesPath, builderEnvPath, devEnvPath);
|
|
299
|
+
|
|
300
|
+
// Generate Docker Compose
|
|
301
|
+
const composeOptions = { ...options };
|
|
302
|
+
composeOptions.port = calculateComposePort(composeOptions, appConfig, developerId);
|
|
303
|
+
return await generateComposeFile(appName, appConfig, composeOptions, devDir);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Prepare environment variables from admin secrets
|
|
308
|
+
* @async
|
|
309
|
+
* @param {boolean} debug - Enable debug logging
|
|
310
|
+
* @returns {Promise<Object>} Environment variables object
|
|
311
|
+
*/
|
|
312
|
+
async function prepareContainerEnv(debug) {
|
|
315
313
|
const adminSecretsPath = await infra.ensureAdminSecrets();
|
|
316
314
|
if (debug) {
|
|
317
315
|
logger.log(chalk.gray(`[DEBUG] Admin secrets path: ${adminSecretsPath}`));
|
|
@@ -331,25 +329,55 @@ async function startContainer(appName, composePath, port, appConfig = null, debu
|
|
|
331
329
|
logger.log(chalk.gray(`[DEBUG] Environment variables: ADMIN_SECRETS_PATH=${adminSecretsPath}, POSTGRES_PASSWORD=${postgresPassword ? '***' : '(not set)'}`));
|
|
332
330
|
}
|
|
333
331
|
|
|
332
|
+
return env;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Execute docker-compose up command
|
|
337
|
+
* @async
|
|
338
|
+
* @param {string} composeCmdBase - Base compose command
|
|
339
|
+
* @param {string} composePath - Path to compose file
|
|
340
|
+
* @param {Object} env - Environment variables
|
|
341
|
+
* @param {boolean} debug - Enable debug logging
|
|
342
|
+
*/
|
|
343
|
+
async function executeComposeUp(composeCmdBase, composePath, env, debug) {
|
|
334
344
|
const composeCmd = `${composeCmdBase} -f "${composePath}" up -d`;
|
|
335
345
|
if (debug) {
|
|
336
346
|
logger.log(chalk.gray(`[DEBUG] Executing: ${composeCmd}`));
|
|
337
347
|
logger.log(chalk.gray(`[DEBUG] Compose file: ${composePath}`));
|
|
338
348
|
}
|
|
339
349
|
await execAsync(composeCmd, { env });
|
|
350
|
+
}
|
|
340
351
|
|
|
352
|
+
/**
|
|
353
|
+
* Starts the container and waits for health check
|
|
354
|
+
* @async
|
|
355
|
+
* @param {string} appName - Application name
|
|
356
|
+
* @param {string} composePath - Path to Docker Compose file
|
|
357
|
+
* @param {number} port - Application port
|
|
358
|
+
* @param {Object} appConfig - Application configuration
|
|
359
|
+
* @param {boolean} [debug=false] - Enable debug logging
|
|
360
|
+
* @throws {Error} If container fails to start or become healthy
|
|
361
|
+
*/
|
|
362
|
+
async function startContainer(appName, composePath, port, appConfig = null, debug = false) {
|
|
363
|
+
logger.log(chalk.blue(`Starting ${appName}...`));
|
|
364
|
+
|
|
365
|
+
// Ensure Docker + Compose available and determine correct compose command
|
|
366
|
+
const composeCmdBase = await dockerUtils.ensureDockerAndCompose().then(() => dockerUtils.getComposeCommand());
|
|
367
|
+
|
|
368
|
+
// Prepare environment variables
|
|
369
|
+
const env = await prepareContainerEnv(debug);
|
|
370
|
+
|
|
371
|
+
// Execute compose up
|
|
372
|
+
await executeComposeUp(composeCmdBase, composePath, env, debug);
|
|
373
|
+
|
|
374
|
+
// Get container name and log status
|
|
341
375
|
const idNum = typeof appConfig.developerId === 'string' ? parseInt(appConfig.developerId, 10) : appConfig.developerId;
|
|
342
376
|
const containerName = idNum === 0 ? `aifabrix-${appName}` : `aifabrix-dev${appConfig.developerId}-${appName}`;
|
|
343
377
|
logger.log(chalk.green(`✓ Container ${containerName} started`));
|
|
344
|
-
|
|
345
|
-
const statusCmd = `docker ps --filter "name=${containerName}" --format "{{.Status}}"`;
|
|
346
|
-
const { stdout: status } = await execAsync(statusCmd);
|
|
347
|
-
const portsCmd = `docker ps --filter "name=${containerName}" --format "{{.Ports}}"`;
|
|
348
|
-
const { stdout: ports } = await execAsync(portsCmd);
|
|
349
|
-
logger.log(chalk.gray(`[DEBUG] Container status: ${status.trim()}`));
|
|
350
|
-
logger.log(chalk.gray(`[DEBUG] Container ports: ${ports.trim()}`));
|
|
351
|
-
}
|
|
378
|
+
await containerHelpers.logContainerStatus(containerName, debug);
|
|
352
379
|
|
|
380
|
+
// Wait for health check
|
|
353
381
|
const healthCheckPath = appConfig?.healthCheck?.path || '/health';
|
|
354
382
|
logger.log(chalk.blue(`Waiting for application to be healthy at http://localhost:${port}${healthCheckPath}...`));
|
|
355
383
|
await waitForHealthCheck(appName, 90, port, appConfig, debug);
|