@aifabrix/builder 2.31.1 → 2.32.2
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 +9 -9
- package/integration/hubspot/README.md +2 -2
- package/integration/hubspot/hubspot-deploy-company.json +17 -14
- package/integration/hubspot/hubspot-deploy-contact.json +19 -16
- package/integration/hubspot/hubspot-deploy-deal.json +21 -18
- package/lib/api/types/datasources.types.js +31 -5
- package/lib/api/types/wizard.types.js +142 -0
- package/lib/api/wizard.api.js +177 -0
- package/lib/{app-config.js → app/config.js} +4 -4
- package/lib/{app-deploy.js → app/deploy.js} +8 -8
- package/lib/app/display.js +90 -0
- package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
- package/lib/{app-down.js → app/down.js} +4 -4
- package/lib/app/helpers.js +218 -0
- package/lib/app/index.js +298 -0
- package/lib/{app-list.js → app/list.js} +6 -6
- package/lib/{app-push.js → app/push.js} +4 -4
- package/lib/{app-readme.js → app/readme.js} +34 -13
- package/lib/{app-register.js → app/register.js} +9 -9
- package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
- package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
- package/lib/{app-run.js → app/run.js} +6 -6
- package/lib/{build.js → build/index.js} +59 -32
- package/lib/build/package.json +7 -0
- package/lib/cli.js +245 -179
- package/lib/commands/app.js +3 -3
- package/lib/commands/datasource.js +4 -4
- package/lib/commands/login-credentials.js +209 -0
- package/lib/commands/login-device.js +254 -0
- package/lib/commands/login.js +67 -378
- package/lib/commands/logout.js +1 -1
- package/lib/commands/secrets-set.js +1 -1
- package/lib/commands/secure.js +2 -2
- package/lib/commands/wizard.js +498 -0
- package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
- package/lib/{config.js → core/config.js} +28 -26
- package/lib/{diff.js → core/diff.js} +157 -72
- package/lib/{secrets.js → core/secrets.js} +86 -49
- package/lib/{templates.js → core/templates-env.js} +14 -222
- package/lib/core/templates.js +279 -0
- package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
- package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
- package/lib/datasource/list.js +223 -0
- package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
- package/lib/{deployer.js → deployment/deployer.js} +48 -18
- package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
- package/lib/{push.js → deployment/push.js} +1 -1
- package/lib/external-system/deploy-helpers.js +145 -0
- package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
- package/lib/external-system/download-helpers.js +114 -0
- package/lib/{external-system-download.js → external-system/download.js} +92 -135
- package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
- package/lib/external-system/test-auth.js +40 -0
- package/lib/external-system/test-execution.js +84 -0
- package/lib/external-system/test-helpers.js +109 -0
- package/lib/{external-system-test.js → external-system/test.js} +174 -192
- package/lib/{generator-builders.js → generator/builders.js} +87 -10
- package/lib/{generator-external.js → generator/external.js} +115 -52
- package/lib/{github-generator.js → generator/github.js} +116 -15
- package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
- package/lib/{generator.js → generator/index.js} +49 -22
- package/lib/{generator-split.js → generator/split.js} +108 -55
- package/lib/generator/wizard-prompts.js +357 -0
- package/lib/generator/wizard.js +490 -0
- package/lib/{infra.js → infrastructure/index.js} +49 -22
- package/lib/schema/external-datasource.schema.json +158 -136
- package/lib/schema/external-system.schema.json +43 -1
- package/lib/utils/api.js +9 -5
- package/lib/utils/app-register-api.js +60 -32
- package/lib/utils/app-register-auth.js +172 -47
- package/lib/utils/app-register-config.js +130 -59
- package/lib/utils/app-run-containers.js +29 -8
- package/lib/utils/build-helpers.js +1 -1
- package/lib/utils/cli-utils.js +78 -30
- package/lib/utils/compose-generator.js +145 -65
- package/lib/utils/config-paths.js +2 -0
- package/lib/utils/deployment-errors.js +1 -1
- package/lib/utils/device-code.js +99 -41
- package/lib/utils/env-config-loader.js +1 -1
- package/lib/utils/env-copy.js +21 -18
- package/lib/utils/env-endpoints.js +115 -67
- package/lib/utils/env-map.js +13 -14
- package/lib/utils/env-ports.js +45 -25
- package/lib/utils/env-template.js +84 -42
- package/lib/utils/error-formatter.js +26 -9
- package/lib/utils/error-formatters/error-parser.js +90 -4
- package/lib/utils/error-formatters/http-status-errors.js +54 -17
- package/lib/utils/error-formatters/network-errors.js +103 -26
- package/lib/utils/external-system-display.js +184 -90
- package/lib/utils/external-system-validators.js +164 -42
- package/lib/utils/file-upload.js +109 -0
- package/lib/utils/health-check.js +199 -83
- package/lib/utils/infra-containers.js +1 -1
- package/lib/utils/infra-status.js +66 -15
- package/lib/utils/local-secrets.js +45 -25
- package/lib/utils/paths.js +45 -33
- package/lib/utils/schema-loader.js +42 -25
- package/lib/utils/schema-resolver.js +123 -74
- package/lib/utils/secrets-encryption.js +62 -25
- package/lib/utils/secrets-helpers.js +126 -63
- package/lib/utils/secrets-path.js +1 -1
- package/lib/utils/secrets-url.js +1 -1
- package/lib/utils/token-manager-refresh.js +181 -0
- package/lib/utils/token-manager.js +76 -123
- package/lib/utils/variable-transformer.js +154 -77
- package/lib/utils/yaml-preserve.js +41 -47
- package/lib/{template-validator.js → validation/template.js} +54 -23
- package/lib/{validate.js → validation/validate.js} +205 -125
- package/lib/{validator.js → validation/validator.js} +58 -39
- package/package.json +31 -2
- package/templates/external-system/deploy.ps1.hbs +34 -0
- package/templates/external-system/deploy.sh.hbs +34 -0
- package/templates/external-system/external-datasource.json.hbs +31 -12
- package/lib/app.js +0 -467
- package/lib/datasource-list.js +0 -141
- /package/lib/{app-prompts.js → app/prompts.js} +0 -0
- /package/lib/{env-reader.js → core/env-reader.js} +0 -0
- /package/lib/{key-generator.js → core/key-generator.js} +0 -0
|
@@ -24,40 +24,91 @@ const execAsync = promisify(exec);
|
|
|
24
24
|
* @param {string} appName - Application name
|
|
25
25
|
* @throws {Error} If db-init fails
|
|
26
26
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Checks if db-init container exists
|
|
29
|
+
* @async
|
|
30
|
+
* @function checkDbInitContainerExists
|
|
31
|
+
* @param {string} dbInitContainer - Container name
|
|
32
|
+
* @returns {Promise<boolean>} True if container exists
|
|
33
|
+
*/
|
|
34
|
+
async function checkDbInitContainerExists(dbInitContainer) {
|
|
29
35
|
try {
|
|
30
36
|
const { stdout } = await execAsync(`docker ps -a --filter "name=${dbInitContainer}" --format "{{.Names}}"`);
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
return stdout.trim() === dbInitContainer;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Gets container exit code
|
|
45
|
+
* @async
|
|
46
|
+
* @function getContainerExitCode
|
|
47
|
+
* @param {string} dbInitContainer - Container name
|
|
48
|
+
* @returns {Promise<string>} Exit code
|
|
49
|
+
*/
|
|
50
|
+
async function getContainerExitCode(dbInitContainer) {
|
|
51
|
+
const { stdout: exitCode } = await execAsync(`docker inspect --format='{{.State.ExitCode}}' ${dbInitContainer}`);
|
|
52
|
+
return exitCode.trim();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Handles exited container status
|
|
57
|
+
* @async
|
|
58
|
+
* @function handleExitedContainer
|
|
59
|
+
* @param {string} dbInitContainer - Container name
|
|
60
|
+
* @returns {Promise<boolean>} True if handled (container already exited)
|
|
61
|
+
*/
|
|
62
|
+
async function handleExitedContainer(dbInitContainer) {
|
|
63
|
+
const { stdout: status } = await execAsync(`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`);
|
|
64
|
+
if (status.trim() === 'exited') {
|
|
65
|
+
const exitCode = await getContainerExitCode(dbInitContainer);
|
|
66
|
+
if (exitCode === '0') {
|
|
67
|
+
logger.log(chalk.green('✓ Database initialization already completed'));
|
|
68
|
+
} else {
|
|
69
|
+
logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
|
|
33
70
|
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
34
75
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Waits for container to exit
|
|
78
|
+
* @async
|
|
79
|
+
* @function waitForContainerExit
|
|
80
|
+
* @param {string} dbInitContainer - Container name
|
|
81
|
+
* @param {number} maxAttempts - Maximum attempts
|
|
82
|
+
*/
|
|
83
|
+
async function waitForContainerExit(dbInitContainer, maxAttempts) {
|
|
84
|
+
for (let attempts = 0; attempts < maxAttempts; attempts++) {
|
|
85
|
+
const { stdout: currentStatus } = await execAsync(`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`);
|
|
86
|
+
if (currentStatus.trim() === 'exited') {
|
|
87
|
+
const exitCode = await getContainerExitCode(dbInitContainer);
|
|
88
|
+
if (exitCode === '0') {
|
|
89
|
+
logger.log(chalk.green('✓ Database initialization completed'));
|
|
40
90
|
} else {
|
|
41
|
-
logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode
|
|
91
|
+
logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
|
|
42
92
|
}
|
|
43
93
|
return;
|
|
44
94
|
}
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
45
98
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode.trim()}`));
|
|
56
|
-
}
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
99
|
+
async function waitForDbInit(appName) {
|
|
100
|
+
const dbInitContainer = `aifabrix-${appName}-db-init`;
|
|
101
|
+
try {
|
|
102
|
+
if (!(await checkDbInitContainerExists(dbInitContainer))) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (await handleExitedContainer(dbInitContainer)) {
|
|
107
|
+
return;
|
|
60
108
|
}
|
|
109
|
+
|
|
110
|
+
logger.log(chalk.blue('Waiting for database initialization to complete...'));
|
|
111
|
+
await waitForContainerExit(dbInitContainer, 30);
|
|
61
112
|
} catch (error) {
|
|
62
113
|
// db-init container might not exist, which is fine
|
|
63
114
|
}
|
|
@@ -71,56 +122,80 @@ async function waitForDbInit(appName) {
|
|
|
71
122
|
* @param {boolean} [debug=false] - Enable debug logging
|
|
72
123
|
* @returns {Promise<number>} Container port
|
|
73
124
|
*/
|
|
125
|
+
/**
|
|
126
|
+
* Gets port from docker inspect
|
|
127
|
+
* @async
|
|
128
|
+
* @function getPortFromDockerInspect
|
|
129
|
+
* @param {string} appName - Application name
|
|
130
|
+
* @param {boolean} debug - Debug flag
|
|
131
|
+
* @returns {Promise<number|null>} Port number or null
|
|
132
|
+
*/
|
|
133
|
+
async function getPortFromDockerInspect(appName, debug) {
|
|
134
|
+
const inspectCmd = `docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{if $conf}}{{range $conf}}{{.HostPort}}{{end}}{{end}}{{end}}' aifabrix-${appName}`;
|
|
135
|
+
if (debug) {
|
|
136
|
+
logger.log(chalk.gray(`[DEBUG] Executing: ${inspectCmd}`));
|
|
137
|
+
}
|
|
138
|
+
const { stdout: portMapping } = await execAsync(inspectCmd);
|
|
139
|
+
const ports = portMapping.trim().split('\n').filter(p => p && p !== '');
|
|
140
|
+
if (ports.length > 0) {
|
|
141
|
+
const port = parseInt(ports[0], 10);
|
|
142
|
+
if (!isNaN(port) && port > 0) {
|
|
143
|
+
if (debug) {
|
|
144
|
+
logger.log(chalk.gray(`[DEBUG] Detected port ${port} from docker inspect`));
|
|
145
|
+
}
|
|
146
|
+
return port;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Gets port from docker ps (fallback)
|
|
154
|
+
* @async
|
|
155
|
+
* @function getPortFromDockerPs
|
|
156
|
+
* @param {string} appName - Application name
|
|
157
|
+
* @param {boolean} debug - Debug flag
|
|
158
|
+
* @returns {Promise<number|null>} Port number or null
|
|
159
|
+
*/
|
|
160
|
+
async function getPortFromDockerPs(appName, debug) {
|
|
161
|
+
const psCmd = `docker ps --filter "name=aifabrix-${appName}" --format "{{.Ports}}"`;
|
|
162
|
+
if (debug) {
|
|
163
|
+
logger.log(chalk.gray(`[DEBUG] Fallback: Executing: ${psCmd}`));
|
|
164
|
+
}
|
|
165
|
+
const { stdout: psOutput } = await execAsync(psCmd);
|
|
166
|
+
const portMatch = psOutput.match(/:(\d+)->/);
|
|
167
|
+
if (!portMatch) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const port = parseInt(portMatch[1], 10);
|
|
171
|
+
if (isNaN(port) || port <= 0) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
if (debug) {
|
|
175
|
+
logger.log(chalk.gray(`[DEBUG] Detected port ${port} from docker ps`));
|
|
176
|
+
}
|
|
177
|
+
return port;
|
|
178
|
+
}
|
|
179
|
+
|
|
74
180
|
async function getContainerPort(appName, debug = false) {
|
|
75
181
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (debug) {
|
|
80
|
-
logger.log(chalk.gray(`[DEBUG] Executing: ${inspectCmd}`));
|
|
81
|
-
}
|
|
82
|
-
const { stdout: portMapping } = await execAsync(inspectCmd);
|
|
83
|
-
const ports = portMapping.trim().split('\n').filter(p => p && p !== '');
|
|
84
|
-
if (ports.length > 0) {
|
|
85
|
-
const port = parseInt(ports[0], 10);
|
|
86
|
-
if (!isNaN(port) && port > 0) {
|
|
87
|
-
if (debug) {
|
|
88
|
-
logger.log(chalk.gray(`[DEBUG] Detected port ${port} from docker inspect`));
|
|
89
|
-
}
|
|
90
|
-
return port;
|
|
91
|
-
}
|
|
182
|
+
const port = await getPortFromDockerInspect(appName, debug);
|
|
183
|
+
if (port !== null) {
|
|
184
|
+
return port;
|
|
92
185
|
}
|
|
93
186
|
|
|
94
|
-
// Fallback: try docker ps
|
|
187
|
+
// Fallback: try docker ps
|
|
95
188
|
try {
|
|
96
|
-
|
|
97
|
-
if (debug) {
|
|
98
|
-
logger.log(chalk.gray(`[DEBUG] Fallback: Executing: ${psCmd}`));
|
|
99
|
-
}
|
|
100
|
-
const { stdout: psOutput } = await execAsync(psCmd);
|
|
101
|
-
const portMatch = psOutput.match(/:(\d+)->/);
|
|
102
|
-
if (!portMatch) {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
const port = parseInt(portMatch[1], 10);
|
|
106
|
-
if (isNaN(port) || port <= 0) {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
if (debug) {
|
|
110
|
-
logger.log(chalk.gray(`[DEBUG] Detected port ${port} from docker ps`));
|
|
111
|
-
}
|
|
112
|
-
return port;
|
|
189
|
+
return await getPortFromDockerPs(appName, debug);
|
|
113
190
|
} catch (error) {
|
|
114
191
|
if (debug) {
|
|
115
192
|
logger.log(chalk.gray(`[DEBUG] Fallback port detection failed: ${error.message}`));
|
|
116
193
|
}
|
|
117
|
-
// Fall through
|
|
118
194
|
}
|
|
119
195
|
} catch (error) {
|
|
120
196
|
if (debug) {
|
|
121
197
|
logger.log(chalk.gray(`[DEBUG] Port detection failed: ${error.message}`));
|
|
122
198
|
}
|
|
123
|
-
// Fall through to default
|
|
124
199
|
}
|
|
125
200
|
if (debug) {
|
|
126
201
|
logger.log(chalk.gray('[DEBUG] Using default port 3000'));
|
|
@@ -250,17 +325,33 @@ async function checkHealthEndpoint(healthCheckUrl, debug = false) {
|
|
|
250
325
|
* @returns {Promise<void>} Resolves when health check passes
|
|
251
326
|
* @throws {Error} If health check times out
|
|
252
327
|
*/
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
328
|
+
/**
|
|
329
|
+
* Determines health check port
|
|
330
|
+
* @async
|
|
331
|
+
* @function determineHealthCheckPort
|
|
332
|
+
* @param {number|null} port - Provided port
|
|
333
|
+
* @param {string} appName - Application name
|
|
334
|
+
* @param {boolean} debug - Debug flag
|
|
335
|
+
* @returns {Promise<number>} Health check port
|
|
336
|
+
*/
|
|
337
|
+
async function determineHealthCheckPort(port, appName, debug) {
|
|
258
338
|
const healthCheckPort = port !== null && port !== undefined ? port : await getContainerPort(appName, debug);
|
|
259
|
-
|
|
260
339
|
if (debug) {
|
|
261
340
|
logger.log(chalk.gray(`[DEBUG] Health check port: ${healthCheckPort} (${port !== null && port !== undefined ? 'provided' : 'auto-detected'})`));
|
|
262
341
|
}
|
|
342
|
+
return healthCheckPort;
|
|
343
|
+
}
|
|
263
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Builds health check configuration
|
|
347
|
+
* @function buildHealthCheckConfig
|
|
348
|
+
* @param {number} healthCheckPort - Health check port
|
|
349
|
+
* @param {Object|null} config - Configuration object
|
|
350
|
+
* @param {number} timeout - Timeout in seconds
|
|
351
|
+
* @param {boolean} debug - Debug flag
|
|
352
|
+
* @returns {Object} Health check configuration
|
|
353
|
+
*/
|
|
354
|
+
function buildHealthCheckConfig(healthCheckPort, config, timeout, debug) {
|
|
264
355
|
const healthCheckPath = config?.healthCheck?.path || '/health';
|
|
265
356
|
const healthCheckUrl = `http://localhost:${healthCheckPort}${healthCheckPath}`;
|
|
266
357
|
const maxAttempts = timeout / 2;
|
|
@@ -270,25 +361,50 @@ async function waitForHealthCheck(appName, timeout = 90, port = null, config = n
|
|
|
270
361
|
logger.log(chalk.gray(`[DEBUG] Timeout: ${timeout} seconds, Max attempts: ${maxAttempts}`));
|
|
271
362
|
}
|
|
272
363
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
364
|
+
return { healthCheckUrl, maxAttempts };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Performs a single health check attempt
|
|
369
|
+
* @async
|
|
370
|
+
* @function performHealthCheckAttempt
|
|
371
|
+
* @param {string} healthCheckUrl - Health check URL
|
|
372
|
+
* @param {number} attempt - Attempt number
|
|
373
|
+
* @param {number} maxAttempts - Maximum attempts
|
|
374
|
+
* @param {boolean} debug - Debug flag
|
|
375
|
+
* @returns {Promise<boolean>} True if health check passed
|
|
376
|
+
*/
|
|
377
|
+
async function performHealthCheckAttempt(healthCheckUrl, attempt, maxAttempts, debug) {
|
|
378
|
+
try {
|
|
379
|
+
if (debug) {
|
|
380
|
+
logger.log(chalk.gray(`[DEBUG] Health check attempt ${attempt + 1}/${maxAttempts}`));
|
|
381
|
+
}
|
|
382
|
+
const healthCheckPassed = await checkHealthEndpoint(healthCheckUrl, debug);
|
|
383
|
+
if (healthCheckPassed) {
|
|
384
|
+
logger.log(chalk.green('✓ Application is healthy'));
|
|
287
385
|
if (debug) {
|
|
288
|
-
logger.log(chalk.gray(`[DEBUG] Health check
|
|
386
|
+
logger.log(chalk.gray(`[DEBUG] Health check passed after ${attempt + 1} attempt(s)`));
|
|
289
387
|
}
|
|
290
|
-
|
|
291
|
-
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
if (debug) {
|
|
392
|
+
logger.log(chalk.gray(`[DEBUG] Health check exception on attempt ${attempt + 1}: ${error.message}`));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function waitForHealthCheck(appName, timeout = 90, port = null, config = null, debug = false) {
|
|
399
|
+
await waitForDbInit(appName);
|
|
400
|
+
|
|
401
|
+
const healthCheckPort = await determineHealthCheckPort(port, appName, debug);
|
|
402
|
+
const { healthCheckUrl, maxAttempts } = buildHealthCheckConfig(healthCheckPort, config, timeout, debug);
|
|
403
|
+
|
|
404
|
+
for (let attempts = 0; attempts < maxAttempts; attempts++) {
|
|
405
|
+
const passed = await performHealthCheckAttempt(healthCheckUrl, attempts, maxAttempts, debug);
|
|
406
|
+
if (passed) {
|
|
407
|
+
return;
|
|
292
408
|
}
|
|
293
409
|
|
|
294
410
|
if (attempts < maxAttempts - 1) {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const { exec } = require('child_process');
|
|
13
13
|
const { promisify } = require('util');
|
|
14
|
-
const config = require('../config');
|
|
14
|
+
const config = require('../core/config');
|
|
15
15
|
const devConfig = require('./dev-config');
|
|
16
16
|
const containerUtils = require('./infra-containers');
|
|
17
17
|
|
|
@@ -74,6 +74,64 @@ async function getInfraStatus() {
|
|
|
74
74
|
return status;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Gets infrastructure container names for a developer ID
|
|
79
|
+
* @param {number} devIdNum - Developer ID number
|
|
80
|
+
* @param {string} devId - Developer ID string
|
|
81
|
+
* @returns {Array<string>} Array of infrastructure container names
|
|
82
|
+
*/
|
|
83
|
+
function getInfraContainerNames(devIdNum, devId) {
|
|
84
|
+
if (devIdNum === 0) {
|
|
85
|
+
return ['aifabrix-postgres', 'aifabrix-redis', 'aifabrix-pgadmin', 'aifabrix-redis-commander'];
|
|
86
|
+
}
|
|
87
|
+
return [`aifabrix-dev${devId}-postgres`, `aifabrix-dev${devId}-redis`, `aifabrix-dev${devId}-pgadmin`, `aifabrix-dev${devId}-redis-commander`];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Extracts app name from container name
|
|
92
|
+
* @param {string} containerName - Container name
|
|
93
|
+
* @param {number} devIdNum - Developer ID number
|
|
94
|
+
* @param {string} devId - Developer ID string
|
|
95
|
+
* @returns {string|null} App name or null if not matched
|
|
96
|
+
*/
|
|
97
|
+
function extractAppName(containerName, devIdNum, devId) {
|
|
98
|
+
const pattern = devIdNum === 0 ? /^aifabrix-(.+)$/ : new RegExp(`^aifabrix-dev${devId}-(.+)$`);
|
|
99
|
+
const match = containerName.match(pattern);
|
|
100
|
+
return match ? match[1] : null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Extracts host port from docker ports string
|
|
105
|
+
* @param {string} ports - Docker ports string
|
|
106
|
+
* @returns {string} Host port or 'unknown'
|
|
107
|
+
*/
|
|
108
|
+
function extractHostPort(ports) {
|
|
109
|
+
const portMatch = ports.match(/:(\d+)->\d+\//);
|
|
110
|
+
return portMatch ? portMatch[1] : 'unknown';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Parses container line and creates app status object
|
|
115
|
+
* @param {string} line - Container line from docker ps
|
|
116
|
+
* @param {Array<string>} infraContainers - Infrastructure container names
|
|
117
|
+
* @param {number} devIdNum - Developer ID number
|
|
118
|
+
* @param {string} devId - Developer ID string
|
|
119
|
+
* @returns {Object|null} App status object or null if not an app container
|
|
120
|
+
*/
|
|
121
|
+
function parseContainerLine(line, infraContainers, devIdNum, devId) {
|
|
122
|
+
const [containerName, ports, status] = line.split('\t');
|
|
123
|
+
if (infraContainers.includes(containerName)) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const appName = extractAppName(containerName, devIdNum, devId);
|
|
127
|
+
if (!appName) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const hostPort = extractHostPort(ports);
|
|
131
|
+
const url = hostPort !== 'unknown' ? `http://localhost:${hostPort}` : 'unknown';
|
|
132
|
+
return { name: appName, container: containerName, port: ports, status: status.trim(), url: url };
|
|
133
|
+
}
|
|
134
|
+
|
|
77
135
|
/**
|
|
78
136
|
* Gets status of running application containers
|
|
79
137
|
* Finds all containers matching pattern aifabrix-dev{id}-* (excluding infrastructure)
|
|
@@ -91,23 +149,16 @@ async function getAppStatus() {
|
|
|
91
149
|
const apps = [];
|
|
92
150
|
|
|
93
151
|
try {
|
|
94
|
-
const
|
|
152
|
+
const devIdNum = parseInt(devId, 10);
|
|
153
|
+
const filterPattern = devIdNum === 0 ? 'aifabrix-' : `aifabrix-dev${devId}-`;
|
|
95
154
|
const { stdout } = await execAsync(`docker ps --filter "name=${filterPattern}" --format "{{.Names}}\t{{.Ports}}\t{{.Status}}"`);
|
|
96
155
|
const lines = stdout.trim().split('\n').filter(line => line.trim() !== '');
|
|
97
|
-
const infraContainers = devId
|
|
98
|
-
? ['aifabrix-postgres', 'aifabrix-redis', 'aifabrix-pgadmin', 'aifabrix-redis-commander']
|
|
99
|
-
: [`aifabrix-dev${devId}-postgres`, `aifabrix-dev${devId}-redis`, `aifabrix-dev${devId}-pgadmin`, `aifabrix-dev${devId}-redis-commander`];
|
|
156
|
+
const infraContainers = getInfraContainerNames(devIdNum, devId);
|
|
100
157
|
for (const line of lines) {
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (!appNameMatch) continue;
|
|
106
|
-
const appName = appNameMatch[1];
|
|
107
|
-
const portMatch = ports.match(/:(\d+)->\d+\//);
|
|
108
|
-
const hostPort = portMatch ? portMatch[1] : 'unknown';
|
|
109
|
-
const url = hostPort !== 'unknown' ? `http://localhost:${hostPort}` : 'unknown';
|
|
110
|
-
apps.push({ name: appName, container: containerName, port: ports, status: status.trim(), url: url });
|
|
158
|
+
const appStatus = parseContainerLine(line, infraContainers, devIdNum, devId);
|
|
159
|
+
if (appStatus) {
|
|
160
|
+
apps.push(appStatus);
|
|
161
|
+
}
|
|
111
162
|
}
|
|
112
163
|
} catch (error) {
|
|
113
164
|
return [];
|
|
@@ -93,53 +93,73 @@ async function saveLocalSecret(key, value) {
|
|
|
93
93
|
* @example
|
|
94
94
|
* await saveSecret('myapp-client-idKeyVault', 'client-id-value', '/path/to/secrets.yaml');
|
|
95
95
|
*/
|
|
96
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Validates save secret parameters
|
|
98
|
+
* @function validateSaveSecretParams
|
|
99
|
+
* @param {string} key - Secret key
|
|
100
|
+
* @param {*} value - Secret value
|
|
101
|
+
* @param {string} secretsPath - Secrets path
|
|
102
|
+
* @throws {Error} If validation fails
|
|
103
|
+
*/
|
|
104
|
+
function validateSaveSecretParams(key, value, secretsPath) {
|
|
97
105
|
if (!key || typeof key !== 'string') {
|
|
98
106
|
throw new Error('Secret key is required and must be a string');
|
|
99
107
|
}
|
|
100
|
-
|
|
101
108
|
if (value === undefined || value === null) {
|
|
102
109
|
throw new Error('Secret value is required');
|
|
103
110
|
}
|
|
104
|
-
|
|
105
111
|
if (!secretsPath || typeof secretsPath !== 'string') {
|
|
106
112
|
throw new Error('Secrets path is required and must be a string');
|
|
107
113
|
}
|
|
114
|
+
}
|
|
108
115
|
|
|
109
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Resolves and prepares secrets path
|
|
118
|
+
* @function resolveAndPrepareSecretsPath
|
|
119
|
+
* @param {string} secretsPath - Secrets path
|
|
120
|
+
* @returns {string} Resolved path
|
|
121
|
+
*/
|
|
122
|
+
function resolveAndPrepareSecretsPath(secretsPath) {
|
|
110
123
|
const resolvedPath = path.isAbsolute(secretsPath)
|
|
111
124
|
? secretsPath
|
|
112
125
|
: path.resolve(process.cwd(), secretsPath);
|
|
113
126
|
|
|
114
127
|
const secretsDir = path.dirname(resolvedPath);
|
|
115
|
-
|
|
116
|
-
// Create directory if needed
|
|
117
128
|
if (!fs.existsSync(secretsDir)) {
|
|
118
129
|
fs.mkdirSync(secretsDir, { recursive: true, mode: 0o700 });
|
|
119
130
|
}
|
|
120
131
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
132
|
+
return resolvedPath;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Loads existing secrets from file
|
|
137
|
+
* @function loadExistingSecrets
|
|
138
|
+
* @param {string} resolvedPath - Resolved secrets path
|
|
139
|
+
* @returns {Object} Existing secrets object
|
|
140
|
+
*/
|
|
141
|
+
function loadExistingSecrets(resolvedPath) {
|
|
142
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
143
|
+
return {};
|
|
134
144
|
}
|
|
135
145
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
146
|
+
try {
|
|
147
|
+
const content = fs.readFileSync(resolvedPath, 'utf8');
|
|
148
|
+
const existingSecrets = yaml.load(content) || {};
|
|
149
|
+
return typeof existingSecrets === 'object' ? existingSecrets : {};
|
|
150
|
+
} catch (error) {
|
|
151
|
+
logger.warn(`Warning: Could not read existing secrets file: ${error.message}`);
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
141
155
|
|
|
142
|
-
|
|
156
|
+
async function saveSecret(key, value, secretsPath) {
|
|
157
|
+
validateSaveSecretParams(key, value, secretsPath);
|
|
158
|
+
|
|
159
|
+
const resolvedPath = resolveAndPrepareSecretsPath(secretsPath);
|
|
160
|
+
const existingSecrets = loadExistingSecrets(resolvedPath);
|
|
161
|
+
|
|
162
|
+
const updatedSecrets = { ...existingSecrets, [key]: value };
|
|
143
163
|
const yamlContent = yaml.dump(updatedSecrets, {
|
|
144
164
|
indent: 2,
|
|
145
165
|
lineWidth: 120,
|
package/lib/utils/paths.js
CHANGED
|
@@ -144,66 +144,78 @@ function getFallbackProjectRoot() {
|
|
|
144
144
|
* Works reliably in all environments including Jest tests and CI
|
|
145
145
|
* @returns {string} Absolute path to project root
|
|
146
146
|
*/
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Checks if global PROJECT_ROOT is valid
|
|
149
|
+
* @function checkGlobalProjectRoot
|
|
150
|
+
* @returns {string|null} Valid global root or null
|
|
151
|
+
*/
|
|
152
|
+
function checkGlobalProjectRoot() {
|
|
153
|
+
if (typeof global === 'undefined' || !global.PROJECT_ROOT) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
151
156
|
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
return
|
|
157
|
+
const globalRoot = global.PROJECT_ROOT;
|
|
158
|
+
if (!hasPackageJson(globalRoot)) {
|
|
159
|
+
return null;
|
|
155
160
|
}
|
|
156
161
|
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
162
|
+
// Verify that __dirname is actually within globalRoot
|
|
163
|
+
const dirnameNormalized = path.resolve(__dirname);
|
|
164
|
+
const globalRootNormalized = path.resolve(globalRoot);
|
|
165
|
+
const isWithinGlobalRoot = dirnameNormalized.startsWith(globalRootNormalized + path.sep) ||
|
|
166
|
+
dirnameNormalized === globalRootNormalized;
|
|
167
|
+
|
|
168
|
+
return isWithinGlobalRoot ? globalRoot : null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Tries different strategies to find project root
|
|
173
|
+
* @function tryFindProjectRoot
|
|
174
|
+
* @returns {string} Found project root
|
|
175
|
+
*/
|
|
176
|
+
function tryFindProjectRoot() {
|
|
177
|
+
// Strategy 1: Check global.PROJECT_ROOT
|
|
178
|
+
const globalRoot = checkGlobalProjectRoot();
|
|
179
|
+
if (globalRoot) {
|
|
180
|
+
cachedProjectRoot = globalRoot;
|
|
181
|
+
return cachedProjectRoot;
|
|
177
182
|
}
|
|
178
183
|
|
|
179
|
-
// Strategy 2: Walk up from __dirname
|
|
180
|
-
// This always works because __dirname is always correct relative to the code
|
|
184
|
+
// Strategy 2: Walk up from __dirname
|
|
181
185
|
const foundRoot = findProjectRootByWalkingUp(__dirname);
|
|
182
186
|
if (foundRoot && hasPackageJson(foundRoot)) {
|
|
183
187
|
cachedProjectRoot = foundRoot;
|
|
184
188
|
return cachedProjectRoot;
|
|
185
189
|
}
|
|
186
190
|
|
|
187
|
-
// Strategy 3: Try process.cwd()
|
|
191
|
+
// Strategy 3: Try process.cwd()
|
|
188
192
|
const cwdRoot = findProjectRootFromCwd();
|
|
189
193
|
if (cwdRoot && hasPackageJson(cwdRoot)) {
|
|
190
194
|
cachedProjectRoot = cwdRoot;
|
|
191
195
|
return cachedProjectRoot;
|
|
192
196
|
}
|
|
193
197
|
|
|
194
|
-
// Strategy 4: Fallback
|
|
198
|
+
// Strategy 4: Fallback
|
|
195
199
|
const fallbackRoot = getFallbackProjectRoot();
|
|
196
200
|
if (hasPackageJson(fallbackRoot)) {
|
|
197
201
|
cachedProjectRoot = fallbackRoot;
|
|
198
202
|
return cachedProjectRoot;
|
|
199
203
|
}
|
|
200
204
|
|
|
201
|
-
// Last resort
|
|
202
|
-
// This prevents crashes but should rarely happen
|
|
205
|
+
// Last resort
|
|
203
206
|
cachedProjectRoot = fallbackRoot;
|
|
204
207
|
return cachedProjectRoot;
|
|
205
208
|
}
|
|
206
209
|
|
|
210
|
+
function getProjectRoot() {
|
|
211
|
+
// Return cached value if available and valid
|
|
212
|
+
if (cachedProjectRoot && hasPackageJson(cachedProjectRoot)) {
|
|
213
|
+
return cachedProjectRoot;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return tryFindProjectRoot();
|
|
217
|
+
}
|
|
218
|
+
|
|
207
219
|
/**
|
|
208
220
|
* Returns the applications base directory for a developer.
|
|
209
221
|
* Dev 0: <home>/applications
|