@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.
- 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 +210 -80
- 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 +7 -5
- package/scripts/ci-fix.sh +19 -0
- package/scripts/ci-simulate.sh +19 -0
- package/scripts/install-local.js +210 -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/infra.js
CHANGED
|
@@ -21,6 +21,7 @@ const logger = require('./utils/logger');
|
|
|
21
21
|
const containerUtils = require('./utils/infra-containers');
|
|
22
22
|
const dockerUtils = require('./utils/docker');
|
|
23
23
|
const paths = require('./utils/paths');
|
|
24
|
+
const statusHelpers = require('./utils/infra-status');
|
|
24
25
|
|
|
25
26
|
// Register Handlebars helper for equality check
|
|
26
27
|
// Handles both strict equality and numeric string comparisons
|
|
@@ -129,20 +130,13 @@ function generatePgAdminConfig(infraDir, postgresPassword) {
|
|
|
129
130
|
fs.writeFileSync(pgpassPath, pgpassContent, { mode: 0o600 });
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const idNum = devIdNum;
|
|
140
|
-
|
|
141
|
-
const templatePath = path.join(__dirname, '..', 'templates', 'infra', 'compose.yaml.hbs');
|
|
142
|
-
if (!fs.existsSync(templatePath)) {
|
|
143
|
-
throw new Error(`Compose template not found: ${templatePath}`);
|
|
144
|
-
}
|
|
145
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Prepare infrastructure directory and extract postgres password
|
|
135
|
+
* @param {string} devId - Developer ID
|
|
136
|
+
* @param {string} adminSecretsPath - Path to admin secrets file
|
|
137
|
+
* @returns {Object} Object with infraDir and postgresPassword
|
|
138
|
+
*/
|
|
139
|
+
function prepareInfraDirectory(devId, adminSecretsPath) {
|
|
146
140
|
const aifabrixDir = paths.getAifabrixHome();
|
|
147
141
|
const infraDirName = getInfraDirName(devId);
|
|
148
142
|
const infraDir = path.join(aifabrixDir, infraDirName);
|
|
@@ -155,6 +149,13 @@ async function startInfra(developerId = null) {
|
|
|
155
149
|
const postgresPassword = postgresPasswordMatch ? postgresPasswordMatch[1] : '';
|
|
156
150
|
generatePgAdminConfig(infraDir, postgresPassword);
|
|
157
151
|
|
|
152
|
+
return { infraDir, postgresPassword };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Register Handlebars helper for equality comparison
|
|
157
|
+
*/
|
|
158
|
+
function registerHandlebarsHelper() {
|
|
158
159
|
handlebars.registerHelper('eq', (a, b) => {
|
|
159
160
|
if (a === null || a === undefined) a = '0';
|
|
160
161
|
if (b === null || b === undefined) b = '0';
|
|
@@ -165,6 +166,18 @@ async function startInfra(developerId = null) {
|
|
|
165
166
|
}
|
|
166
167
|
return a === b;
|
|
167
168
|
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Generate docker-compose file from template
|
|
173
|
+
* @param {string} templatePath - Path to compose template
|
|
174
|
+
* @param {string} devId - Developer ID
|
|
175
|
+
* @param {number} idNum - Developer ID number
|
|
176
|
+
* @param {Object} ports - Port configuration
|
|
177
|
+
* @param {string} infraDir - Infrastructure directory
|
|
178
|
+
* @returns {string} Path to generated compose file
|
|
179
|
+
*/
|
|
180
|
+
function generateComposeFile(templatePath, devId, idNum, ports, infraDir) {
|
|
168
181
|
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
169
182
|
const template = handlebars.compile(templateContent);
|
|
170
183
|
const networkName = idNum === 0 ? 'infra-aifabrix-network' : `infra-dev${devId}-aifabrix-network`;
|
|
@@ -183,31 +196,83 @@ async function startInfra(developerId = null) {
|
|
|
183
196
|
});
|
|
184
197
|
const composePath = path.join(infraDir, 'compose.yaml');
|
|
185
198
|
fs.writeFileSync(composePath, composeContent);
|
|
199
|
+
return composePath;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Start Docker services using docker-compose
|
|
204
|
+
* @async
|
|
205
|
+
* @param {string} composePath - Path to compose file
|
|
206
|
+
* @param {string} projectName - Docker project name
|
|
207
|
+
* @param {string} adminSecretsPath - Path to admin secrets file
|
|
208
|
+
* @param {string} infraDir - Infrastructure directory
|
|
209
|
+
*/
|
|
210
|
+
async function startDockerServices(composePath, projectName, adminSecretsPath, infraDir) {
|
|
211
|
+
logger.log(`Using compose file: ${composePath}`);
|
|
212
|
+
logger.log('Starting infrastructure services...');
|
|
213
|
+
const composeCmd = await dockerUtils.getComposeCommand();
|
|
214
|
+
await execAsyncWithCwd(`${composeCmd} -f "${composePath}" -p ${projectName} --env-file "${adminSecretsPath}" up -d`, { cwd: infraDir });
|
|
215
|
+
logger.log('Infrastructure services started successfully');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Copy pgAdmin4 configuration files into container
|
|
220
|
+
* @async
|
|
221
|
+
* @param {string} pgadminContainerName - pgAdmin container name
|
|
222
|
+
* @param {string} serversJsonPath - Path to servers.json file
|
|
223
|
+
* @param {string} pgpassPath - Path to pgpass file
|
|
224
|
+
*/
|
|
225
|
+
async function copyPgAdminConfig(pgadminContainerName, serversJsonPath, pgpassPath) {
|
|
226
|
+
try {
|
|
227
|
+
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait for container to be ready
|
|
228
|
+
if (fs.existsSync(serversJsonPath)) {
|
|
229
|
+
await execAsync(`docker cp "${serversJsonPath}" ${pgadminContainerName}:/pgadmin4/servers.json`);
|
|
230
|
+
}
|
|
231
|
+
if (fs.existsSync(pgpassPath)) {
|
|
232
|
+
await execAsync(`docker cp "${pgpassPath}" ${pgadminContainerName}:/pgpass`);
|
|
233
|
+
await execAsync(`docker exec ${pgadminContainerName} chmod 600 /pgpass`);
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
// Ignore copy errors - files might already be there or container not ready
|
|
237
|
+
logger.log('Note: Could not copy pgAdmin4 config files (this is OK if container was just restarted)');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function startInfra(developerId = null) {
|
|
242
|
+
await checkDockerAvailability();
|
|
243
|
+
const adminSecretsPath = await ensureAdminSecrets();
|
|
244
|
+
|
|
245
|
+
const devId = developerId || await config.getDeveloperId();
|
|
246
|
+
const devIdNum = typeof devId === 'string' ? parseInt(devId, 10) : devId;
|
|
247
|
+
const ports = devConfig.getDevPorts(devIdNum);
|
|
248
|
+
const idNum = devIdNum;
|
|
249
|
+
|
|
250
|
+
const templatePath = path.join(__dirname, '..', 'templates', 'infra', 'compose.yaml.hbs');
|
|
251
|
+
if (!fs.existsSync(templatePath)) {
|
|
252
|
+
throw new Error(`Compose template not found: ${templatePath}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Prepare infrastructure directory
|
|
256
|
+
const { infraDir } = prepareInfraDirectory(devId, adminSecretsPath);
|
|
257
|
+
|
|
258
|
+
// Register Handlebars helper
|
|
259
|
+
registerHandlebarsHelper();
|
|
260
|
+
|
|
261
|
+
// Generate compose file
|
|
262
|
+
const composePath = generateComposeFile(templatePath, devId, idNum, ports, infraDir);
|
|
186
263
|
|
|
187
264
|
try {
|
|
188
|
-
|
|
189
|
-
logger.log(`Starting infrastructure services for developer ${devId}...`);
|
|
265
|
+
// Start Docker services
|
|
190
266
|
const projectName = getInfraProjectName(devId);
|
|
191
|
-
|
|
192
|
-
await execAsyncWithCwd(`${composeCmd} -f "${composePath}" -p ${projectName} --env-file "${adminSecretsPath}" up -d`, { cwd: infraDir });
|
|
193
|
-
logger.log('Infrastructure services started successfully');
|
|
267
|
+
await startDockerServices(composePath, projectName, adminSecretsPath, infraDir);
|
|
194
268
|
|
|
195
|
-
// Copy pgAdmin4 config files
|
|
269
|
+
// Copy pgAdmin4 config files
|
|
196
270
|
const pgadminContainerName = idNum === 0 ? 'aifabrix-pgadmin' : `aifabrix-dev${devId}-pgadmin`;
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
await execAsync(`docker cp "${serversJsonPath}" ${pgadminContainerName}:/pgadmin4/servers.json`);
|
|
201
|
-
}
|
|
202
|
-
if (fs.existsSync(pgpassPath)) {
|
|
203
|
-
await execAsync(`docker cp "${pgpassPath}" ${pgadminContainerName}:/pgpass`);
|
|
204
|
-
await execAsync(`docker exec ${pgadminContainerName} chmod 600 /pgpass`);
|
|
205
|
-
}
|
|
206
|
-
} catch (error) {
|
|
207
|
-
// Ignore copy errors - files might already be there or container not ready
|
|
208
|
-
logger.log('Note: Could not copy pgAdmin4 config files (this is OK if container was just restarted)');
|
|
209
|
-
}
|
|
271
|
+
const serversJsonPath = path.join(infraDir, 'servers.json');
|
|
272
|
+
const pgpassPath = path.join(infraDir, 'pgpass');
|
|
273
|
+
await copyPgAdminConfig(pgadminContainerName, serversJsonPath, pgpassPath);
|
|
210
274
|
|
|
275
|
+
// Wait for services to be healthy
|
|
211
276
|
await waitForServices(devId);
|
|
212
277
|
logger.log('All services are healthy and ready');
|
|
213
278
|
} finally {
|
|
@@ -321,62 +386,9 @@ async function checkInfraHealth(devId = null) {
|
|
|
321
386
|
return health;
|
|
322
387
|
}
|
|
323
388
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
*
|
|
328
|
-
* @async
|
|
329
|
-
* @function getInfraStatus
|
|
330
|
-
* @returns {Promise<Object>} Status information for each service
|
|
331
|
-
*
|
|
332
|
-
* @example
|
|
333
|
-
* const status = await getInfraStatus();
|
|
334
|
-
* // Returns: { postgres: { status: 'running', port: 5432, url: 'localhost:5432' }, ... }
|
|
335
|
-
*/
|
|
336
|
-
async function getInfraStatus() {
|
|
337
|
-
const devId = await config.getDeveloperId();
|
|
338
|
-
// Convert string developer ID to number for getDevPorts
|
|
339
|
-
const devIdNum = parseInt(devId, 10);
|
|
340
|
-
const ports = devConfig.getDevPorts(devIdNum);
|
|
341
|
-
const services = {
|
|
342
|
-
postgres: { port: ports.postgres, url: `localhost:${ports.postgres}` },
|
|
343
|
-
redis: { port: ports.redis, url: `localhost:${ports.redis}` },
|
|
344
|
-
pgadmin: { port: ports.pgadmin, url: `http://localhost:${ports.pgadmin}` },
|
|
345
|
-
'redis-commander': { port: ports.redisCommander, url: `http://localhost:${ports.redisCommander}` }
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
const status = {};
|
|
349
|
-
|
|
350
|
-
for (const [serviceName, serviceConfig] of Object.entries(services)) {
|
|
351
|
-
try {
|
|
352
|
-
const containerName = await containerUtils.findContainer(serviceName, devId);
|
|
353
|
-
if (containerName) {
|
|
354
|
-
const { stdout } = await execAsync(`docker inspect --format='{{.State.Status}}' ${containerName}`);
|
|
355
|
-
// Normalize status value (trim whitespace and remove quotes)
|
|
356
|
-
const normalizedStatus = stdout.trim().replace(/['"]/g, '');
|
|
357
|
-
status[serviceName] = {
|
|
358
|
-
status: normalizedStatus,
|
|
359
|
-
port: serviceConfig.port,
|
|
360
|
-
url: serviceConfig.url
|
|
361
|
-
};
|
|
362
|
-
} else {
|
|
363
|
-
status[serviceName] = {
|
|
364
|
-
status: 'not running',
|
|
365
|
-
port: serviceConfig.port,
|
|
366
|
-
url: serviceConfig.url
|
|
367
|
-
};
|
|
368
|
-
}
|
|
369
|
-
} catch (error) {
|
|
370
|
-
status[serviceName] = {
|
|
371
|
-
status: 'not running',
|
|
372
|
-
port: serviceConfig.port,
|
|
373
|
-
url: serviceConfig.url
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return status;
|
|
379
|
-
}
|
|
389
|
+
// Re-export status helper functions
|
|
390
|
+
const getInfraStatus = statusHelpers.getInfraStatus;
|
|
391
|
+
const getAppStatus = statusHelpers.getAppStatus;
|
|
380
392
|
|
|
381
393
|
/**
|
|
382
394
|
* Restarts a specific infrastructure service
|
|
@@ -451,48 +463,6 @@ async function waitForServices(devId = null) {
|
|
|
451
463
|
|
|
452
464
|
throw new Error('Services failed to become healthy within timeout period');
|
|
453
465
|
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Gets status of running application containers
|
|
457
|
-
* Finds all containers matching pattern aifabrix-dev{id}-* (excluding infrastructure)
|
|
458
|
-
*
|
|
459
|
-
* @async
|
|
460
|
-
* @function getAppStatus
|
|
461
|
-
* @returns {Promise<Array>} Array of application status objects
|
|
462
|
-
*
|
|
463
|
-
* @example
|
|
464
|
-
* const apps = await getAppStatus();
|
|
465
|
-
* // Returns: [{ name: 'myapp', container: 'aifabrix-dev1-myapp', port: '3100:3000', status: 'running', url: 'http://localhost:3100' }]
|
|
466
|
-
*/
|
|
467
|
-
async function getAppStatus() {
|
|
468
|
-
const devId = await config.getDeveloperId();
|
|
469
|
-
const apps = [];
|
|
470
|
-
|
|
471
|
-
try {
|
|
472
|
-
const filterPattern = devId === 0 ? 'aifabrix-' : `aifabrix-dev${devId}-`;
|
|
473
|
-
const { stdout } = await execAsync(`docker ps --filter "name=${filterPattern}" --format "{{.Names}}\t{{.Ports}}\t{{.Status}}"`);
|
|
474
|
-
const lines = stdout.trim().split('\n').filter(line => line.trim() !== '');
|
|
475
|
-
const infraContainers = devId === 0
|
|
476
|
-
? ['aifabrix-postgres', 'aifabrix-redis', 'aifabrix-pgadmin', 'aifabrix-redis-commander']
|
|
477
|
-
: [`aifabrix-dev${devId}-postgres`, `aifabrix-dev${devId}-redis`, `aifabrix-dev${devId}-pgadmin`, `aifabrix-dev${devId}-redis-commander`];
|
|
478
|
-
for (const line of lines) {
|
|
479
|
-
const [containerName, ports, status] = line.split('\t');
|
|
480
|
-
if (infraContainers.includes(containerName)) continue;
|
|
481
|
-
const pattern = devId === 0 ? /^aifabrix-(.+)$/ : new RegExp(`^aifabrix-dev${devId}-(.+)$`);
|
|
482
|
-
const appNameMatch = containerName.match(pattern);
|
|
483
|
-
if (!appNameMatch) continue;
|
|
484
|
-
const appName = appNameMatch[1];
|
|
485
|
-
const portMatch = ports.match(/:(\d+)->\d+\//);
|
|
486
|
-
const hostPort = portMatch ? portMatch[1] : 'unknown';
|
|
487
|
-
const url = hostPort !== 'unknown' ? `http://localhost:${hostPort}` : 'unknown';
|
|
488
|
-
apps.push({ name: appName, container: containerName, port: ports, status: status.trim(), url: url });
|
|
489
|
-
}
|
|
490
|
-
} catch (error) {
|
|
491
|
-
return [];
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
return apps;
|
|
495
|
-
}
|
|
496
466
|
module.exports = {
|
|
497
467
|
startInfra, stopInfra, stopInfraWithVolumes, checkInfraHealth,
|
|
498
468
|
getInfraStatus, getAppStatus, restartService, ensureAdminSecrets
|