@aifabrix/builder 2.1.7 ā 2.3.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/lib/app-deploy.js +73 -29
- package/lib/app-list.js +132 -0
- package/lib/app-readme.js +11 -4
- package/lib/app-register.js +435 -0
- package/lib/app-rotate-secret.js +164 -0
- package/lib/app-run.js +98 -84
- package/lib/app.js +13 -0
- package/lib/audit-logger.js +195 -15
- package/lib/build.js +155 -42
- package/lib/cli.js +104 -8
- package/lib/commands/app.js +8 -391
- package/lib/commands/login.js +130 -36
- package/lib/commands/secure.js +260 -0
- package/lib/config.js +315 -4
- package/lib/deployer.js +221 -183
- package/lib/infra.js +177 -112
- package/lib/push.js +34 -7
- package/lib/secrets.js +89 -23
- package/lib/templates.js +1 -1
- package/lib/utils/api-error-handler.js +465 -0
- package/lib/utils/api.js +165 -16
- package/lib/utils/auth-headers.js +84 -0
- package/lib/utils/build-copy.js +162 -0
- package/lib/utils/cli-utils.js +49 -3
- package/lib/utils/compose-generator.js +57 -16
- package/lib/utils/deployment-errors.js +90 -0
- package/lib/utils/deployment-validation.js +60 -0
- package/lib/utils/dev-config.js +83 -0
- package/lib/utils/docker-build.js +24 -0
- package/lib/utils/env-template.js +30 -10
- package/lib/utils/health-check.js +18 -1
- package/lib/utils/infra-containers.js +101 -0
- package/lib/utils/local-secrets.js +0 -2
- package/lib/utils/secrets-encryption.js +203 -0
- package/lib/utils/secrets-path.js +22 -3
- package/lib/utils/token-manager.js +381 -0
- package/package.json +2 -2
- package/templates/applications/README.md.hbs +155 -23
- package/templates/applications/miso-controller/Dockerfile +7 -119
- package/templates/infra/compose.yaml.hbs +93 -0
- package/templates/python/docker-compose.hbs +25 -17
- package/templates/typescript/docker-compose.hbs +25 -17
- package/test-output.txt +0 -5431
package/lib/build.js
CHANGED
|
@@ -19,13 +19,61 @@ const { promisify } = require('util');
|
|
|
19
19
|
const chalk = require('chalk');
|
|
20
20
|
const yaml = require('js-yaml');
|
|
21
21
|
const secrets = require('./secrets');
|
|
22
|
+
const config = require('./config');
|
|
22
23
|
const logger = require('./utils/logger');
|
|
23
24
|
const validator = require('./validator');
|
|
24
25
|
const dockerfileUtils = require('./utils/dockerfile-utils');
|
|
25
26
|
const dockerBuild = require('./utils/docker-build');
|
|
27
|
+
const buildCopy = require('./utils/build-copy');
|
|
26
28
|
|
|
27
29
|
const execAsync = promisify(exec);
|
|
28
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Copies application template files to dev directory
|
|
33
|
+
* Used when apps directory doesn't exist to ensure build can proceed
|
|
34
|
+
* @async
|
|
35
|
+
* @param {string} templatePath - Path to template directory
|
|
36
|
+
* @param {string} devDir - Target dev directory
|
|
37
|
+
* @param {string} _language - Language (typescript/python) - currently unused but kept for future use
|
|
38
|
+
* @throws {Error} If copying fails
|
|
39
|
+
*/
|
|
40
|
+
async function copyTemplateFilesToDevDir(templatePath, devDir, _language) {
|
|
41
|
+
if (!fsSync.existsSync(templatePath)) {
|
|
42
|
+
throw new Error(`Template path not found: ${templatePath}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const entries = await fs.readdir(templatePath);
|
|
46
|
+
|
|
47
|
+
// Copy only application files, skip Dockerfile and docker-compose templates
|
|
48
|
+
const appFiles = entries.filter(entry => {
|
|
49
|
+
const lowerEntry = entry.toLowerCase();
|
|
50
|
+
// Include .gitignore, exclude .hbs files and docker-related files
|
|
51
|
+
if (entry === '.gitignore') {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (lowerEntry.endsWith('.hbs')) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (lowerEntry.startsWith('dockerfile') || lowerEntry.includes('docker-compose')) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (entry.startsWith('.') && entry !== '.gitignore') {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
for (const entry of appFiles) {
|
|
67
|
+
const sourcePath = path.join(templatePath, entry);
|
|
68
|
+
const targetPath = path.join(devDir, entry);
|
|
69
|
+
|
|
70
|
+
const entryStats = await fs.stat(sourcePath);
|
|
71
|
+
if (entryStats.isFile()) {
|
|
72
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
29
77
|
/**
|
|
30
78
|
* Loads variables.yaml configuration for an application
|
|
31
79
|
* @param {string} appName - Application name
|
|
@@ -89,7 +137,6 @@ function detectLanguage(appPath) {
|
|
|
89
137
|
const packageJsonPath = path.join(appPath, 'package.json');
|
|
90
138
|
const requirementsPath = path.join(appPath, 'requirements.txt');
|
|
91
139
|
const pyprojectPath = path.join(appPath, 'pyproject.toml');
|
|
92
|
-
const dockerfilePath = path.join(appPath, 'Dockerfile');
|
|
93
140
|
|
|
94
141
|
// Check for package.json (TypeScript/Node.js)
|
|
95
142
|
if (fsSync.existsSync(packageJsonPath)) {
|
|
@@ -101,11 +148,6 @@ function detectLanguage(appPath) {
|
|
|
101
148
|
return 'python';
|
|
102
149
|
}
|
|
103
150
|
|
|
104
|
-
// Check for custom Dockerfile
|
|
105
|
-
if (fsSync.existsSync(dockerfilePath)) {
|
|
106
|
-
throw new Error('Custom Dockerfile found. Use --force-template to regenerate from template.');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
151
|
// Default to typescript if no indicators found
|
|
110
152
|
return 'typescript';
|
|
111
153
|
}
|
|
@@ -127,7 +169,7 @@ function detectLanguage(appPath) {
|
|
|
127
169
|
* const dockerfilePath = await generateDockerfile('myapp', 'typescript', config);
|
|
128
170
|
* // Returns: '~/.aifabrix/myapp/Dockerfile.typescript'
|
|
129
171
|
*/
|
|
130
|
-
async function generateDockerfile(appNameOrPath, language, config, buildConfig = {}) {
|
|
172
|
+
async function generateDockerfile(appNameOrPath, language, config, buildConfig = {}, devDir = null) {
|
|
131
173
|
let appName;
|
|
132
174
|
if (appNameOrPath.includes(path.sep) || appNameOrPath.includes('/') || appNameOrPath.includes('\\')) {
|
|
133
175
|
appName = path.basename(appNameOrPath);
|
|
@@ -151,12 +193,19 @@ async function generateDockerfile(appNameOrPath, language, config, buildConfig =
|
|
|
151
193
|
|
|
152
194
|
const dockerfileContent = dockerfileUtils.renderDockerfile(template, templateVars, language, isAppFlag, appSourcePath);
|
|
153
195
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
196
|
+
// Use dev directory if provided, otherwise use default aifabrix directory
|
|
197
|
+
let targetDir;
|
|
198
|
+
if (devDir) {
|
|
199
|
+
targetDir = devDir;
|
|
200
|
+
} else {
|
|
201
|
+
targetDir = path.join(os.homedir(), '.aifabrix', appName);
|
|
157
202
|
}
|
|
158
203
|
|
|
159
|
-
|
|
204
|
+
if (!fsSync.existsSync(targetDir)) {
|
|
205
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const dockerfilePath = path.join(targetDir, 'Dockerfile');
|
|
160
209
|
await fs.writeFile(dockerfilePath, dockerfileContent, 'utf8');
|
|
161
210
|
|
|
162
211
|
return dockerfilePath;
|
|
@@ -175,41 +224,29 @@ async function generateDockerfile(appNameOrPath, language, config, buildConfig =
|
|
|
175
224
|
* @returns {Promise<string>} Path to Dockerfile
|
|
176
225
|
*/
|
|
177
226
|
async function determineDockerfile(appName, options) {
|
|
178
|
-
|
|
227
|
+
// Use dev directory if provided, otherwise fall back to builder directory
|
|
228
|
+
const searchPath = options.devDir || path.join(process.cwd(), 'builder', appName);
|
|
179
229
|
|
|
180
|
-
const templateDockerfile = dockerfileUtils.checkTemplateDockerfile(
|
|
230
|
+
const templateDockerfile = dockerfileUtils.checkTemplateDockerfile(searchPath, appName, options.forceTemplate);
|
|
181
231
|
if (templateDockerfile) {
|
|
182
|
-
|
|
232
|
+
const relativePath = path.relative(process.cwd(), templateDockerfile);
|
|
233
|
+
logger.log(chalk.green(`ā Using existing Dockerfile: ${relativePath}`));
|
|
183
234
|
return templateDockerfile;
|
|
184
235
|
}
|
|
185
236
|
|
|
186
|
-
const customDockerfile = dockerfileUtils.checkProjectDockerfile(
|
|
237
|
+
const customDockerfile = dockerfileUtils.checkProjectDockerfile(searchPath, appName, options.buildConfig, options.contextPath, options.forceTemplate);
|
|
187
238
|
if (customDockerfile) {
|
|
188
239
|
logger.log(chalk.green(`ā Using custom Dockerfile: ${options.buildConfig.dockerfile}`));
|
|
189
240
|
return customDockerfile;
|
|
190
241
|
}
|
|
191
242
|
|
|
192
|
-
|
|
243
|
+
// Generate Dockerfile in dev directory if provided
|
|
244
|
+
const dockerfilePath = await generateDockerfile(appName, options.language, options.config, options.buildConfig, options.devDir);
|
|
193
245
|
const relativePath = path.relative(process.cwd(), dockerfilePath);
|
|
194
246
|
logger.log(chalk.green(`ā Generated Dockerfile from template: ${relativePath}`));
|
|
195
247
|
return dockerfilePath;
|
|
196
248
|
}
|
|
197
249
|
|
|
198
|
-
/**
|
|
199
|
-
* Prepares build context path
|
|
200
|
-
* @param {string} appName - Application name
|
|
201
|
-
* @param {string} contextPath - Relative context path
|
|
202
|
-
* @returns {string} Absolute context path
|
|
203
|
-
*/
|
|
204
|
-
function prepareBuildContext(appName, contextPath) {
|
|
205
|
-
// Ensure contextPath is a string
|
|
206
|
-
const context = typeof contextPath === 'string' ? contextPath : (contextPath || '');
|
|
207
|
-
return resolveContextPath(
|
|
208
|
-
path.join(process.cwd(), 'builder', appName),
|
|
209
|
-
context
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
250
|
/**
|
|
214
251
|
* Loads and validates configuration for build
|
|
215
252
|
* @async
|
|
@@ -309,20 +346,89 @@ async function buildApp(appName, options = {}) {
|
|
|
309
346
|
logger.log(chalk.blue(`\nšØ Building application: ${appName}`));
|
|
310
347
|
|
|
311
348
|
// 1. Load and validate configuration
|
|
312
|
-
const { config, imageName, buildConfig } = await loadAndValidateConfig(appName);
|
|
349
|
+
const { config: appConfig, imageName, buildConfig } = await loadAndValidateConfig(appName);
|
|
350
|
+
|
|
351
|
+
// 2. Get developer ID and copy files to dev-specific directory
|
|
352
|
+
const developerId = await config.getDeveloperId();
|
|
353
|
+
const directoryName = developerId === 0 ? 'applications' : `dev-${developerId}`;
|
|
354
|
+
logger.log(chalk.blue(`Copying files to developer-specific directory (${directoryName})...`));
|
|
355
|
+
const devDir = await buildCopy.copyBuilderToDevDirectory(appName, developerId);
|
|
356
|
+
logger.log(chalk.green(`ā Files copied to: ${devDir}`));
|
|
357
|
+
|
|
358
|
+
// 2a. Check if application source files exist, if not copy from templates
|
|
359
|
+
const appsPath = path.join(process.cwd(), 'apps', appName);
|
|
360
|
+
if (fsSync.existsSync(appsPath)) {
|
|
361
|
+
// Copy app source files from apps directory
|
|
362
|
+
await buildCopy.copyAppSourceFiles(appsPath, devDir);
|
|
363
|
+
logger.log(chalk.green(`ā Copied application source files from apps/${appName}`));
|
|
364
|
+
} else {
|
|
365
|
+
// No apps directory - check if we need to copy template files
|
|
366
|
+
const language = options.language || buildConfig.language || detectLanguage(devDir);
|
|
367
|
+
const packageJsonPath = path.join(devDir, 'package.json');
|
|
368
|
+
const requirementsPath = path.join(devDir, 'requirements.txt');
|
|
369
|
+
|
|
370
|
+
if (language === 'typescript' && !fsSync.existsSync(packageJsonPath)) {
|
|
371
|
+
// Copy TypeScript template files
|
|
372
|
+
const templatePath = path.join(__dirname, '..', 'templates', 'typescript');
|
|
373
|
+
await copyTemplateFilesToDevDir(templatePath, devDir, language);
|
|
374
|
+
logger.log(chalk.green(`ā Generated application files from ${language} template`));
|
|
375
|
+
} else if (language === 'python' && !fsSync.existsSync(requirementsPath)) {
|
|
376
|
+
// Copy Python template files
|
|
377
|
+
const templatePath = path.join(__dirname, '..', 'templates', 'python');
|
|
378
|
+
await copyTemplateFilesToDevDir(templatePath, devDir, language);
|
|
379
|
+
logger.log(chalk.green(`ā Generated application files from ${language} template`));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// 3. Prepare build context (use dev-specific directory)
|
|
384
|
+
// If buildConfig.context is relative, resolve it relative to devDir
|
|
385
|
+
// If buildConfig.context is '../..' (apps flag), keep it as is (it's relative to devDir)
|
|
386
|
+
let contextPath;
|
|
387
|
+
|
|
388
|
+
// Check if context is using old format (../appName) - these are incompatible with dev directory structure
|
|
389
|
+
if (buildConfig.context && buildConfig.context.startsWith('../') && buildConfig.context !== '../..') {
|
|
390
|
+
// Old format detected - always use devDir instead
|
|
391
|
+
logger.log(chalk.yellow(`ā ļø Warning: Build context uses old format: ${buildConfig.context}`));
|
|
392
|
+
logger.log(chalk.yellow(` Using dev directory instead: ${devDir}`));
|
|
393
|
+
contextPath = devDir;
|
|
394
|
+
} else if (buildConfig.context && buildConfig.context !== '../..') {
|
|
395
|
+
// Resolve relative context path from dev directory
|
|
396
|
+
contextPath = path.resolve(devDir, buildConfig.context);
|
|
397
|
+
} else if (buildConfig.context === '../..') {
|
|
398
|
+
// For apps flag, context is relative to devDir (which is ~/.aifabrix/<app>-dev-<id>)
|
|
399
|
+
// So '../..' from devDir goes to ~/.aifabrix, then we need to go to project root
|
|
400
|
+
// Actually, we need to keep the relative path and let Docker resolve it
|
|
401
|
+
// But the build context should be the project root, not devDir
|
|
402
|
+
contextPath = process.cwd();
|
|
403
|
+
} else {
|
|
404
|
+
// No context specified, use dev directory
|
|
405
|
+
contextPath = devDir;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Ensure context path is absolute and normalized
|
|
409
|
+
contextPath = path.resolve(contextPath);
|
|
313
410
|
|
|
314
|
-
//
|
|
315
|
-
const
|
|
411
|
+
// Validate that context path exists (skip in test environments)
|
|
412
|
+
const isTestEnv = process.env.NODE_ENV === 'test' ||
|
|
413
|
+
process.env.JEST_WORKER_ID !== undefined ||
|
|
414
|
+
typeof jest !== 'undefined';
|
|
316
415
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
416
|
+
if (!isTestEnv && !fsSync.existsSync(contextPath)) {
|
|
417
|
+
throw new Error(
|
|
418
|
+
`Build context path does not exist: ${contextPath}\n` +
|
|
419
|
+
`Expected dev directory: ${devDir}\n` +
|
|
420
|
+
'Please ensure files were copied correctly or update the context in variables.yaml.'
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// 4. Check if Dockerfile exists in dev directory
|
|
425
|
+
const appDockerfilePath = path.join(devDir, 'Dockerfile');
|
|
320
426
|
const hasExistingDockerfile = fsSync.existsSync(appDockerfilePath) && !options.forceTemplate;
|
|
321
427
|
|
|
322
|
-
//
|
|
428
|
+
// 5. Determine language (skip if existing Dockerfile found)
|
|
323
429
|
let language = options.language || buildConfig.language;
|
|
324
430
|
if (!language && !hasExistingDockerfile) {
|
|
325
|
-
language = detectLanguage(
|
|
431
|
+
language = detectLanguage(devDir);
|
|
326
432
|
} else if (!language) {
|
|
327
433
|
// Default language if existing Dockerfile is found (won't be used, but needed for API)
|
|
328
434
|
language = 'typescript';
|
|
@@ -331,17 +437,24 @@ async function buildApp(appName, options = {}) {
|
|
|
331
437
|
logger.log(chalk.green(`ā Detected language: ${language}`));
|
|
332
438
|
}
|
|
333
439
|
|
|
334
|
-
//
|
|
440
|
+
// 6. Determine Dockerfile (needs context path to generate in correct location)
|
|
441
|
+
// Use dev directory for Dockerfile generation
|
|
335
442
|
const dockerfilePath = await determineDockerfile(appName, {
|
|
336
443
|
language,
|
|
337
|
-
config,
|
|
444
|
+
config: appConfig,
|
|
338
445
|
buildConfig,
|
|
339
446
|
contextPath,
|
|
340
|
-
forceTemplate: options.forceTemplate
|
|
447
|
+
forceTemplate: options.forceTemplate,
|
|
448
|
+
devDir: devDir
|
|
341
449
|
});
|
|
342
450
|
|
|
343
451
|
// 6. Build Docker image
|
|
344
452
|
const tag = options.tag || 'latest';
|
|
453
|
+
|
|
454
|
+
// Log paths for debugging
|
|
455
|
+
logger.log(chalk.blue(`Using Dockerfile: ${dockerfilePath}`));
|
|
456
|
+
logger.log(chalk.blue(`Using build context: ${contextPath}`));
|
|
457
|
+
|
|
345
458
|
await executeBuild(imageName, dockerfilePath, contextPath, tag, options);
|
|
346
459
|
|
|
347
460
|
// 7. Post-build tasks
|
package/lib/cli.js
CHANGED
|
@@ -15,10 +15,13 @@ const secrets = require('./secrets');
|
|
|
15
15
|
const generator = require('./generator');
|
|
16
16
|
const validator = require('./validator');
|
|
17
17
|
const keyGenerator = require('./key-generator');
|
|
18
|
+
const config = require('./config');
|
|
19
|
+
const devConfig = require('./utils/dev-config');
|
|
18
20
|
const chalk = require('chalk');
|
|
19
21
|
const logger = require('./utils/logger');
|
|
20
22
|
const { validateCommand, handleCommandError } = require('./utils/cli-utils');
|
|
21
23
|
const { handleLogin } = require('./commands/login');
|
|
24
|
+
const { handleSecure } = require('./commands/secure');
|
|
22
25
|
|
|
23
26
|
/**
|
|
24
27
|
* Sets up all CLI commands on the Commander program instance
|
|
@@ -28,11 +31,12 @@ function setupCommands(program) {
|
|
|
28
31
|
// Authentication command
|
|
29
32
|
program.command('login')
|
|
30
33
|
.description('Authenticate with Miso Controller')
|
|
31
|
-
.option('-
|
|
34
|
+
.option('-c, --controller <url>', 'Controller URL', 'http://localhost:3000')
|
|
32
35
|
.option('-m, --method <method>', 'Authentication method (device|credentials)')
|
|
33
|
-
.option('
|
|
34
|
-
.option('--client-
|
|
35
|
-
.option('-
|
|
36
|
+
.option('-a, --app <app>', 'Application name (required for credentials method, reads from secrets.local.yaml)')
|
|
37
|
+
.option('--client-id <id>', 'Client ID (for credentials method, overrides secrets.local.yaml)')
|
|
38
|
+
.option('--client-secret <secret>', 'Client Secret (for credentials method, overrides secrets.local.yaml)')
|
|
39
|
+
.option('-e, --environment <env>', 'Environment key (updates root-level environment in config.yaml, e.g., miso, dev, tst, pro)')
|
|
36
40
|
.action(async(options) => {
|
|
37
41
|
try {
|
|
38
42
|
await handleLogin(options);
|
|
@@ -45,9 +49,21 @@ function setupCommands(program) {
|
|
|
45
49
|
// Infrastructure commands
|
|
46
50
|
program.command('up')
|
|
47
51
|
.description('Start local infrastructure services (Postgres, Redis, pgAdmin, Redis Commander)')
|
|
48
|
-
.
|
|
52
|
+
.option('-d, --developer <id>', 'Set developer ID and start infrastructure')
|
|
53
|
+
.action(async(options) => {
|
|
49
54
|
try {
|
|
50
|
-
|
|
55
|
+
let developerId = null;
|
|
56
|
+
if (options.developer) {
|
|
57
|
+
const id = parseInt(options.developer, 10);
|
|
58
|
+
if (isNaN(id) || id < 0) {
|
|
59
|
+
throw new Error('Developer ID must be a non-negative number (0 = default infra, > 0 = developer-specific)');
|
|
60
|
+
}
|
|
61
|
+
await config.setDeveloperId(id);
|
|
62
|
+
process.env.AIFABRIX_DEVELOPERID = id.toString();
|
|
63
|
+
developerId = id;
|
|
64
|
+
logger.log(chalk.green(`ā Developer ID set to ${id}`));
|
|
65
|
+
}
|
|
66
|
+
await infra.startInfra(developerId);
|
|
51
67
|
} catch (error) {
|
|
52
68
|
handleCommandError(error, 'up');
|
|
53
69
|
process.exit(1);
|
|
@@ -138,7 +154,7 @@ function setupCommands(program) {
|
|
|
138
154
|
program.command('deploy <app>')
|
|
139
155
|
.description('Deploy to Azure via Miso Controller')
|
|
140
156
|
.option('-c, --controller <url>', 'Controller URL')
|
|
141
|
-
.option('-e, --environment <env>', 'Environment (dev, tst, pro)', 'dev')
|
|
157
|
+
.option('-e, --environment <env>', 'Environment (miso, dev, tst, pro)', 'dev')
|
|
142
158
|
.option('--client-id <id>', 'Client ID (overrides config)')
|
|
143
159
|
.option('--client-secret <secret>', 'Client Secret (overrides config)')
|
|
144
160
|
.option('--poll', 'Poll for deployment status', true)
|
|
@@ -191,7 +207,7 @@ function setupCommands(program) {
|
|
|
191
207
|
});
|
|
192
208
|
|
|
193
209
|
program.command('status')
|
|
194
|
-
.description('Show detailed infrastructure service status')
|
|
210
|
+
.description('Show detailed infrastructure service status and running applications')
|
|
195
211
|
.action(async() => {
|
|
196
212
|
try {
|
|
197
213
|
const status = await infra.getInfraStatus();
|
|
@@ -207,6 +223,22 @@ function setupCommands(program) {
|
|
|
207
223
|
logger.log(` URL: ${info.url}`);
|
|
208
224
|
logger.log('');
|
|
209
225
|
});
|
|
226
|
+
|
|
227
|
+
// Show running applications
|
|
228
|
+
const apps = await infra.getAppStatus();
|
|
229
|
+
if (apps.length > 0) {
|
|
230
|
+
logger.log('š± Running Applications\n');
|
|
231
|
+
apps.forEach((app) => {
|
|
232
|
+
const normalizedStatus = String(app.status).trim().toLowerCase();
|
|
233
|
+
const icon = normalizedStatus.includes('running') || normalizedStatus.includes('up') ? 'ā
' : 'ā';
|
|
234
|
+
logger.log(`${icon} ${app.name}:`);
|
|
235
|
+
logger.log(` Container: ${app.container}`);
|
|
236
|
+
logger.log(` Port: ${app.port}`);
|
|
237
|
+
logger.log(` Status: ${app.status}`);
|
|
238
|
+
logger.log(` URL: ${app.url}`);
|
|
239
|
+
logger.log('');
|
|
240
|
+
});
|
|
241
|
+
}
|
|
210
242
|
} catch (error) {
|
|
211
243
|
handleCommandError(error, 'status');
|
|
212
244
|
process.exit(1);
|
|
@@ -291,6 +323,70 @@ function setupCommands(program) {
|
|
|
291
323
|
process.exit(1);
|
|
292
324
|
}
|
|
293
325
|
});
|
|
326
|
+
|
|
327
|
+
// Developer configuration commands
|
|
328
|
+
program.command('dev config')
|
|
329
|
+
.description('Show or set developer configuration')
|
|
330
|
+
.option('--set-id <id>', 'Set developer ID')
|
|
331
|
+
.action(async(cmdName, opts) => {
|
|
332
|
+
try {
|
|
333
|
+
// For commands with spaces like 'dev config', Commander.js passes command name as first arg
|
|
334
|
+
// Options are passed as second arg, or if only one arg, it might be the command name
|
|
335
|
+
const options = typeof cmdName === 'object' && cmdName !== null ? cmdName : (opts || {});
|
|
336
|
+
// Commander.js converts --set-id to setId in options object
|
|
337
|
+
const setIdValue = options.setId || options['set-id'];
|
|
338
|
+
if (setIdValue) {
|
|
339
|
+
const id = parseInt(setIdValue, 10);
|
|
340
|
+
if (isNaN(id) || id < 0) {
|
|
341
|
+
throw new Error('Developer ID must be a non-negative number (0 = default infra, > 0 = developer-specific)');
|
|
342
|
+
}
|
|
343
|
+
await config.setDeveloperId(id);
|
|
344
|
+
process.env.AIFABRIX_DEVELOPERID = id.toString();
|
|
345
|
+
logger.log(chalk.green(`ā Developer ID set to ${id}`));
|
|
346
|
+
// Use the ID we just set instead of reading from file to avoid race conditions
|
|
347
|
+
const devId = id;
|
|
348
|
+
const ports = devConfig.getDevPorts(devId);
|
|
349
|
+
logger.log('\nš§ Developer Configuration\n');
|
|
350
|
+
logger.log(`Developer ID: ${devId}`);
|
|
351
|
+
logger.log('\nPorts:');
|
|
352
|
+
logger.log(` App: ${ports.app}`);
|
|
353
|
+
logger.log(` Postgres: ${ports.postgres}`);
|
|
354
|
+
logger.log(` Redis: ${ports.redis}`);
|
|
355
|
+
logger.log(` pgAdmin: ${ports.pgadmin}`);
|
|
356
|
+
logger.log(` Redis Commander: ${ports.redisCommander}`);
|
|
357
|
+
logger.log('');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const devId = await config.getDeveloperId();
|
|
362
|
+
const ports = devConfig.getDevPorts(devId);
|
|
363
|
+
logger.log('\nš§ Developer Configuration\n');
|
|
364
|
+
logger.log(`Developer ID: ${devId}`);
|
|
365
|
+
logger.log('\nPorts:');
|
|
366
|
+
logger.log(` App: ${ports.app}`);
|
|
367
|
+
logger.log(` Postgres: ${ports.postgres}`);
|
|
368
|
+
logger.log(` Redis: ${ports.redis}`);
|
|
369
|
+
logger.log(` pgAdmin: ${ports.pgadmin}`);
|
|
370
|
+
logger.log(` Redis Commander: ${ports.redisCommander}`);
|
|
371
|
+
logger.log('');
|
|
372
|
+
} catch (error) {
|
|
373
|
+
handleCommandError(error, 'dev config');
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// Security command
|
|
379
|
+
program.command('secure')
|
|
380
|
+
.description('Encrypt secrets in secrets.local.yaml files for ISO 27001 compliance')
|
|
381
|
+
.option('--secrets-encryption <key>', 'Encryption key (32 bytes, hex or base64)')
|
|
382
|
+
.action(async(options) => {
|
|
383
|
+
try {
|
|
384
|
+
await handleSecure(options);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
handleCommandError(error, 'secure');
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
294
390
|
}
|
|
295
391
|
|
|
296
392
|
module.exports = {
|