@aifabrix/builder 2.3.2 → 2.3.4

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/README.md CHANGED
@@ -11,6 +11,12 @@ Local development infrastructure + Azure deployment tool.
11
11
  npm install -g @aifabrix/builder
12
12
  ```
13
13
 
14
+ OR
15
+
16
+ ```bash
17
+ sudo npm install -g @aifabrix/builder
18
+ ```
19
+
14
20
  ## Quick Start
15
21
 
16
22
  ```bash
@@ -24,6 +24,7 @@ const buildCopy = require('./utils/build-copy');
24
24
  const logger = require('./utils/logger');
25
25
  const { waitForHealthCheck } = require('./utils/health-check');
26
26
  const composeGenerator = require('./utils/compose-generator');
27
+ const dockerUtils = require('./utils/docker');
27
28
 
28
29
  const execAsync = promisify(exec);
29
30
 
@@ -308,6 +309,9 @@ async function prepareEnvironment(appName, appConfig, options) {
308
309
  async function startContainer(appName, composePath, port, appConfig = null, debug = false) {
309
310
  logger.log(chalk.blue(`Starting ${appName}...`));
310
311
 
312
+ // Ensure Docker + Compose available and determine correct compose command
313
+ const composeCmdBase = await dockerUtils.ensureDockerAndCompose().then(() => dockerUtils.getComposeCommand());
314
+
311
315
  const adminSecretsPath = await infra.ensureAdminSecrets();
312
316
  if (debug) {
313
317
  logger.log(chalk.gray(`[DEBUG] Admin secrets path: ${adminSecretsPath}`));
@@ -327,7 +331,7 @@ async function startContainer(appName, composePath, port, appConfig = null, debu
327
331
  logger.log(chalk.gray(`[DEBUG] Environment variables: ADMIN_SECRETS_PATH=${adminSecretsPath}, POSTGRES_PASSWORD=${postgresPassword ? '***' : '(not set)'}`));
328
332
  }
329
333
 
330
- const composeCmd = `docker-compose -f "${composePath}" up -d`;
334
+ const composeCmd = `${composeCmdBase} -f "${composePath}" up -d`;
331
335
  if (debug) {
332
336
  logger.log(chalk.gray(`[DEBUG] Executing: ${composeCmd}`));
333
337
  logger.log(chalk.gray(`[DEBUG] Compose file: ${composePath}`));
package/lib/infra.js CHANGED
@@ -20,6 +20,7 @@ const config = require('./config');
20
20
  const devConfig = require('./utils/dev-config');
21
21
  const logger = require('./utils/logger');
22
22
  const containerUtils = require('./utils/infra-containers');
23
+ const dockerUtils = require('./utils/docker');
23
24
 
24
25
  // Register Handlebars helper for equality check
25
26
  handlebars.registerHelper('eq', (a, b) => a === b);
@@ -76,8 +77,7 @@ function execAsyncWithCwd(command, options = {}) {
76
77
  */
77
78
  async function checkDockerAvailability() {
78
79
  try {
79
- await execAsync('docker --version');
80
- await execAsync('docker-compose --version');
80
+ await dockerUtils.ensureDockerAndCompose();
81
81
  } catch (error) {
82
82
  throw new Error('Docker or Docker Compose is not available. Please install and start Docker.');
83
83
  }
@@ -138,7 +138,8 @@ async function startInfra(developerId = null) {
138
138
  logger.log(`Using compose file: ${composePath}`);
139
139
  logger.log(`Starting infrastructure services for developer ${devId}...`);
140
140
  const projectName = getInfraProjectName(devId);
141
- await execAsyncWithCwd(`docker-compose -f "${composePath}" -p ${projectName} --env-file "${adminSecretsPath}" up -d`, { cwd: infraDir });
141
+ const composeCmd = await dockerUtils.getComposeCommand();
142
+ await execAsyncWithCwd(`${composeCmd} -f "${composePath}" -p ${projectName} --env-file "${adminSecretsPath}" up -d`, { cwd: infraDir });
142
143
  logger.log('Infrastructure services started successfully');
143
144
 
144
145
  await waitForServices(devId);
@@ -177,7 +178,8 @@ async function stopInfra() {
177
178
  try {
178
179
  logger.log('Stopping infrastructure services...');
179
180
  const projectName = getInfraProjectName(devId);
180
- await execAsyncWithCwd(`docker-compose -f "${composePath}" -p ${projectName} --env-file "${adminSecretsPath}" down`, { cwd: infraDir });
181
+ const composeCmd = await dockerUtils.getComposeCommand();
182
+ await execAsyncWithCwd(`${composeCmd} -f "${composePath}" -p ${projectName} --env-file "${adminSecretsPath}" down`, { cwd: infraDir });
181
183
  logger.log('Infrastructure services stopped');
182
184
  } finally {
183
185
  // Keep the compose file for future use
@@ -213,7 +215,8 @@ async function stopInfraWithVolumes() {
213
215
  try {
214
216
  logger.log('Stopping infrastructure services and removing all data...');
215
217
  const projectName = getInfraProjectName(devId);
216
- await execAsyncWithCwd(`docker-compose -f "${composePath}" -p ${projectName} --env-file "${adminSecretsPath}" down -v`, { cwd: infraDir });
218
+ const composeCmd = await dockerUtils.getComposeCommand();
219
+ await execAsyncWithCwd(`${composeCmd} -f "${composePath}" -p ${projectName} --env-file "${adminSecretsPath}" down -v`, { cwd: infraDir });
217
220
  logger.log('Infrastructure services stopped and all data removed');
218
221
  } finally {
219
222
  // Keep the compose file for future use
@@ -347,7 +350,8 @@ async function restartService(serviceName) {
347
350
  try {
348
351
  logger.log(`Restarting ${serviceName} service...`);
349
352
  const projectName = getInfraProjectName(devId);
350
- await execAsyncWithCwd(`docker-compose -f "${composePath}" -p ${projectName} --env-file "${adminSecretsPath}" restart ${serviceName}`, { cwd: infraDir });
353
+ const composeCmd = await dockerUtils.getComposeCommand();
354
+ await execAsyncWithCwd(`${composeCmd} -f "${composePath}" -p ${projectName} --env-file "${adminSecretsPath}" restart ${serviceName}`, { cwd: infraDir });
351
355
  logger.log(`${serviceName} service restarted successfully`);
352
356
  } finally {
353
357
  // Keep the compose file for future use
@@ -16,6 +16,9 @@ const handlebars = require('handlebars');
16
16
  const config = require('../config');
17
17
  const buildCopy = require('./build-copy');
18
18
 
19
+ // Register commonly used helpers
20
+ handlebars.registerHelper('eq', (a, b) => a === b);
21
+
19
22
  // Register Handlebars helper for quoting PostgreSQL identifiers
20
23
  // PostgreSQL requires identifiers with hyphens or special characters to be quoted
21
24
  handlebars.registerHelper('pgQuote', (identifier) => {
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Docker CLI Utilities
3
+ *
4
+ * Detects availability of Docker and determines the correct Docker Compose command
5
+ * across environments (Compose v2 plugin: "docker compose", Compose v1: "docker-compose").
6
+ *
7
+ * @fileoverview Docker/Compose detection helpers for AI Fabrix Builder
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const { exec } = require('child_process');
15
+ const { promisify } = require('util');
16
+
17
+ const execAsync = promisify(exec);
18
+
19
+ /**
20
+ * Checks that Docker CLI is available.
21
+ * @async
22
+ * @function checkDockerCli
23
+ * @returns {Promise<void>} Resolves if docker is available
24
+ * @throws {Error} If docker is unavailable
25
+ */
26
+ async function checkDockerCli() {
27
+ await execAsync('docker --version');
28
+ }
29
+
30
+ /**
31
+ * Determines the correct Docker Compose command on this system.
32
+ * Tries Docker Compose v2 plugin first: "docker compose", then falls back to v1: "docker-compose".
33
+ *
34
+ * @async
35
+ * @function getComposeCommand
36
+ * @returns {Promise<string>} The compose command to use ("docker compose" or "docker-compose")
37
+ * @throws {Error} If neither v2 nor v1 is available
38
+ */
39
+ async function getComposeCommand() {
40
+ // Prefer Compose v2 plugin if present
41
+ try {
42
+ await execAsync('docker compose version');
43
+ return 'docker compose';
44
+ } catch (_) {
45
+ // Fall back to legacy docker-compose
46
+ }
47
+
48
+ try {
49
+ await execAsync('docker-compose --version');
50
+ return 'docker-compose';
51
+ } catch (_) {
52
+ throw new Error('Docker Compose is not available (neither "docker compose" nor "docker-compose" found).');
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Ensures Docker and Docker Compose are available, returning the compose command to use.
58
+ * @async
59
+ * @function ensureDockerAndCompose
60
+ * @returns {Promise<string>} The compose command to use
61
+ * @throws {Error} If docker or compose is not available
62
+ */
63
+ async function ensureDockerAndCompose() {
64
+ await checkDockerCli();
65
+ return await getComposeCommand();
66
+ }
67
+
68
+ module.exports = {
69
+ checkDockerCli,
70
+ getComposeCommand,
71
+ ensureDockerAndCompose
72
+ };
73
+
@@ -12,6 +12,7 @@
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
14
  const os = require('os');
15
+ const dockerUtils = require('./docker');
15
16
 
16
17
  /**
17
18
  * Checks if Docker is installed and available
@@ -22,12 +23,8 @@ const os = require('os');
22
23
  */
23
24
  async function checkDocker() {
24
25
  try {
25
- const { exec } = require('child_process');
26
- const { promisify } = require('util');
27
- const execAsync = promisify(exec);
28
-
29
- await execAsync('docker --version');
30
- await execAsync('docker-compose --version');
26
+ await dockerUtils.checkDockerCli();
27
+ await dockerUtils.getComposeCommand();
31
28
  return 'ok';
32
29
  } catch (error) {
33
30
  return 'error';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -82,8 +82,10 @@ services:
82
82
 
83
83
  volumes:
84
84
  dev{{devId}}_postgres_data:
85
+ name: {{#if (eq devId 0)}}infra_postgres_data{{else}}infra_dev{{devId}}_postgres_data{{/if}}
85
86
  driver: local
86
87
  dev{{devId}}_redis_data:
88
+ name: {{#if (eq devId 0)}}infra_redis_data{{else}}infra_dev{{devId}}_redis_data{{/if}}
87
89
  driver: local
88
90
 
89
91
  networks:
@@ -14,7 +14,7 @@ services:
14
14
  - {{networkName}}
15
15
  {{#if requiresStorage}}
16
16
  volumes:
17
- - "{{mountVolume}}:/mnt/data"
17
+ - {{#if (eq devId 0)}}aifabrix_{{app.key}}_data{{else}}aifabrix_dev{{devId}}_{{app.key}}_data{{/if}}:/mnt/data
18
18
  {{/if}}
19
19
  healthcheck:
20
20
  test: ["CMD", "curl", "-f", "http://localhost:{{port}}{{healthCheck.path}}"]
@@ -98,6 +98,19 @@ services:
98
98
  restart: "no"
99
99
  {{/if}}
100
100
 
101
+ {{#if requiresStorage}}
102
+ volumes:
103
+ {{#if (eq devId 0)}}
104
+ aifabrix_{{app.key}}_data:
105
+ name: aifabrix_{{app.key}}_data
106
+ driver: local
107
+ {{else}}
108
+ aifabrix_dev{{devId}}_{{app.key}}_data:
109
+ name: aifabrix_dev{{devId}}_{{app.key}}_data
110
+ driver: local
111
+ {{/if}}
112
+ {{/if}}
113
+
101
114
  networks:
102
115
  {{networkName}}:
103
116
  external: true
@@ -14,7 +14,7 @@ services:
14
14
  - {{networkName}}
15
15
  {{#if requiresStorage}}
16
16
  volumes:
17
- - "{{mountVolume}}:/mnt/data"
17
+ - {{#if (eq devId 0)}}aifabrix_{{app.key}}_data{{else}}aifabrix_dev{{devId}}_{{app.key}}_data{{/if}}:/mnt/data
18
18
  {{/if}}
19
19
  healthcheck:
20
20
  test: ["CMD", "curl", "-f", "http://localhost:{{port}}{{healthCheck.path}}"]
@@ -98,6 +98,19 @@ services:
98
98
  restart: "no"
99
99
  {{/if}}
100
100
 
101
+ {{#if requiresStorage}}
102
+ volumes:
103
+ {{#if (eq devId 0)}}
104
+ aifabrix_{{app.key}}_data:
105
+ name: aifabrix_{{app.key}}_data
106
+ driver: local
107
+ {{else}}
108
+ aifabrix_dev{{devId}}_{{app.key}}_data:
109
+ name: aifabrix_dev{{devId}}_{{app.key}}_data
110
+ driver: local
111
+ {{/if}}
112
+ {{/if}}
113
+
101
114
  networks:
102
115
  {{networkName}}:
103
116
  external: true