@aifabrix/builder 2.0.0 ā 2.0.3
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 +5 -3
- package/bin/aifabrix.js +9 -3
- package/jest.config.integration.js +30 -0
- package/lib/app-config.js +157 -0
- package/lib/app-deploy.js +233 -82
- package/lib/app-dockerfile.js +112 -0
- package/lib/app-prompts.js +244 -0
- package/lib/app-push.js +172 -0
- package/lib/app-run.js +235 -144
- package/lib/app.js +208 -274
- package/lib/audit-logger.js +2 -0
- package/lib/build.js +177 -125
- package/lib/cli.js +76 -86
- package/lib/commands/app.js +414 -0
- package/lib/commands/login.js +304 -0
- package/lib/config.js +78 -0
- package/lib/deployer.js +225 -81
- package/lib/env-reader.js +45 -30
- package/lib/generator.js +308 -191
- package/lib/github-generator.js +67 -7
- package/lib/infra.js +156 -61
- package/lib/push.js +105 -10
- package/lib/schema/application-schema.json +30 -2
- package/lib/schema/env-config.yaml +9 -1
- package/lib/schema/infrastructure-schema.json +589 -0
- package/lib/secrets.js +229 -24
- package/lib/template-validator.js +205 -0
- package/lib/templates.js +305 -170
- package/lib/utils/api.js +329 -0
- package/lib/utils/cli-utils.js +97 -0
- package/lib/utils/compose-generator.js +185 -0
- package/lib/utils/docker-build.js +173 -0
- package/lib/utils/dockerfile-utils.js +131 -0
- package/lib/utils/environment-checker.js +125 -0
- package/lib/utils/error-formatter.js +61 -0
- package/lib/utils/health-check.js +187 -0
- package/lib/utils/logger.js +53 -0
- package/lib/utils/template-helpers.js +223 -0
- package/lib/utils/variable-transformer.js +271 -0
- package/lib/validator.js +27 -112
- package/package.json +14 -10
- package/templates/README.md +75 -3
- package/templates/applications/keycloak/Dockerfile +36 -0
- package/templates/applications/keycloak/env.template +32 -0
- package/templates/applications/keycloak/rbac.yaml +37 -0
- package/templates/applications/keycloak/variables.yaml +56 -0
- package/templates/applications/miso-controller/Dockerfile +125 -0
- package/templates/applications/miso-controller/env.template +129 -0
- package/templates/applications/miso-controller/rbac.yaml +214 -0
- package/templates/applications/miso-controller/variables.yaml +56 -0
- package/templates/github/release.yaml.hbs +5 -26
- package/templates/github/steps/npm.hbs +24 -0
- package/templates/infra/compose.yaml +6 -6
- package/templates/python/docker-compose.hbs +19 -12
- package/templates/python/main.py +80 -0
- package/templates/python/requirements.txt +4 -0
- package/templates/typescript/Dockerfile.hbs +2 -2
- package/templates/typescript/docker-compose.hbs +19 -12
- package/templates/typescript/index.ts +116 -0
- package/templates/typescript/package.json +26 -0
- package/templates/typescript/tsconfig.json +24 -0
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AI Fabrix - Builder SDK
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@aifabrix/builder)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
3
5
|
Local development infrastructure + Azure deployment tool.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
@@ -32,12 +34,12 @@ Want authentication or deployment controller?
|
|
|
32
34
|
|
|
33
35
|
```bash
|
|
34
36
|
# Keycloak for authentication
|
|
35
|
-
aifabrix create keycloak --port 8082 --database --template
|
|
37
|
+
aifabrix create keycloak --port 8082 --database --template keycloak
|
|
36
38
|
aifabrix build keycloak
|
|
37
39
|
aifabrix run keycloak
|
|
38
40
|
|
|
39
41
|
# Miso Controller for Azure deployments
|
|
40
|
-
aifabrix create miso-controller --port 3000 --database --redis --template
|
|
42
|
+
aifabrix create miso-controller --port 3000 --database --redis --template miso-controller
|
|
41
43
|
aifabrix build miso-controller
|
|
42
44
|
aifabrix run miso-controller
|
|
43
45
|
```
|
package/bin/aifabrix.js
CHANGED
|
@@ -10,11 +10,14 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @fileoverview CLI entry point for AI Fabrix Builder SDK
|
|
12
12
|
* @author AI Fabrix Team
|
|
13
|
-
* @version 2.0.
|
|
13
|
+
* @version 2.0.2
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const { Command } = require('commander');
|
|
17
17
|
const cli = require('../lib/cli');
|
|
18
|
+
const { setupAppCommands } = require('../lib/commands/app');
|
|
19
|
+
const logger = require('../lib/utils/logger');
|
|
20
|
+
const packageJson = require('../package.json');
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
23
|
* Initialize and configure the CLI
|
|
@@ -24,12 +27,15 @@ function initializeCLI() {
|
|
|
24
27
|
const program = new Command();
|
|
25
28
|
|
|
26
29
|
program.name('aifabrix')
|
|
27
|
-
.version(
|
|
30
|
+
.version(packageJson.version)
|
|
28
31
|
.description('AI Fabrix Local Fabric & Deployment SDK');
|
|
29
32
|
|
|
30
33
|
// Delegate command setup to lib/cli.js
|
|
31
34
|
cli.setupCommands(program);
|
|
32
35
|
|
|
36
|
+
// Add application management commands
|
|
37
|
+
setupAppCommands(program);
|
|
38
|
+
|
|
33
39
|
// Parse command line arguments
|
|
34
40
|
program.parse();
|
|
35
41
|
}
|
|
@@ -43,7 +49,7 @@ if (require.main === module) {
|
|
|
43
49
|
try {
|
|
44
50
|
initializeCLI();
|
|
45
51
|
} catch (error) {
|
|
46
|
-
|
|
52
|
+
logger.error('ā Failed to initialize CLI:', error.message);
|
|
47
53
|
process.exit(1);
|
|
48
54
|
}
|
|
49
55
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Configuration for Integration Tests
|
|
3
|
+
* Ensures tests run in correct order with proper error handling
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Jest config specifically for integration tests
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const baseConfig = require('./jest.config');
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
...baseConfig,
|
|
14
|
+
// Override testPathIgnorePatterns for integration tests
|
|
15
|
+
testPathIgnorePatterns: [
|
|
16
|
+
'/node_modules/',
|
|
17
|
+
'\\\\node_modules\\\\'
|
|
18
|
+
],
|
|
19
|
+
// Use custom test sequencer for correct order
|
|
20
|
+
testSequencer: '<rootDir>/tests/integration/test-sequencer.js',
|
|
21
|
+
// Longer timeout for integration tests
|
|
22
|
+
testTimeout: 300000, // 5 minutes
|
|
23
|
+
// Run tests sequentially
|
|
24
|
+
maxWorkers: 1,
|
|
25
|
+
// Don't ignore integration tests
|
|
26
|
+
testMatch: [
|
|
27
|
+
'**/tests/integration/**/*.test.js'
|
|
28
|
+
]
|
|
29
|
+
};
|
|
30
|
+
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Configuration File Generation
|
|
3
|
+
*
|
|
4
|
+
* Generates configuration files for applications (variables.yaml, env.template, rbac.yaml, etc.)
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Configuration file generation for AI Fabrix Builder
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs').promises;
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const chalk = require('chalk');
|
|
14
|
+
const { generateVariablesYaml, generateEnvTemplate, generateRbacYaml } = require('./templates');
|
|
15
|
+
const { generateEnvTemplate: generateEnvTemplateFromReader } = require('./env-reader');
|
|
16
|
+
const logger = require('./utils/logger');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Checks if a file exists
|
|
20
|
+
* @async
|
|
21
|
+
* @param {string} filePath - Path to file
|
|
22
|
+
* @returns {Promise<boolean>} True if file exists
|
|
23
|
+
*/
|
|
24
|
+
async function fileExists(filePath) {
|
|
25
|
+
try {
|
|
26
|
+
await fs.access(filePath);
|
|
27
|
+
return true;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generates variables.yaml file if it doesn't exist
|
|
35
|
+
* @async
|
|
36
|
+
* @param {string} appPath - Path to application directory
|
|
37
|
+
* @param {string} appName - Application name
|
|
38
|
+
* @param {Object} config - Application configuration
|
|
39
|
+
*/
|
|
40
|
+
async function generateVariablesYamlFile(appPath, appName, config) {
|
|
41
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
42
|
+
if (!(await fileExists(variablesPath))) {
|
|
43
|
+
const variablesYaml = generateVariablesYaml(appName, config);
|
|
44
|
+
await fs.writeFile(variablesPath, variablesYaml);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generates env.template file if it doesn't exist
|
|
50
|
+
* @async
|
|
51
|
+
* @param {string} appPath - Path to application directory
|
|
52
|
+
* @param {Object} config - Application configuration
|
|
53
|
+
* @param {Object} existingEnv - Existing environment variables
|
|
54
|
+
*/
|
|
55
|
+
async function generateEnvTemplateFile(appPath, config, existingEnv) {
|
|
56
|
+
const envTemplatePath = path.join(appPath, 'env.template');
|
|
57
|
+
if (!(await fileExists(envTemplatePath))) {
|
|
58
|
+
let envTemplate;
|
|
59
|
+
if (existingEnv) {
|
|
60
|
+
const envResult = await generateEnvTemplateFromReader(config, existingEnv);
|
|
61
|
+
envTemplate = envResult.template;
|
|
62
|
+
|
|
63
|
+
if (envResult.warnings.length > 0) {
|
|
64
|
+
logger.log(chalk.yellow('\nā ļø Environment conversion warnings:'));
|
|
65
|
+
envResult.warnings.forEach(warning => logger.log(chalk.yellow(` - ${warning}`)));
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
envTemplate = generateEnvTemplate(config);
|
|
69
|
+
}
|
|
70
|
+
await fs.writeFile(envTemplatePath, envTemplate);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Generates rbac.yaml file if authentication is enabled and file doesn't exist
|
|
76
|
+
* @async
|
|
77
|
+
* @param {string} appPath - Path to application directory
|
|
78
|
+
* @param {string} appName - Application name
|
|
79
|
+
* @param {Object} config - Application configuration
|
|
80
|
+
*/
|
|
81
|
+
async function generateRbacYamlFile(appPath, appName, config) {
|
|
82
|
+
if (!config.authentication) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const rbacPath = path.join(appPath, 'rbac.yaml');
|
|
87
|
+
if (!(await fileExists(rbacPath))) {
|
|
88
|
+
const rbacYaml = generateRbacYaml(appName, config);
|
|
89
|
+
if (rbacYaml) {
|
|
90
|
+
await fs.writeFile(rbacPath, rbacYaml);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generates aifabrix-deploy.json file
|
|
97
|
+
* @async
|
|
98
|
+
* @param {string} appPath - Path to application directory
|
|
99
|
+
* @param {string} appName - Application name
|
|
100
|
+
* @param {Object} config - Application configuration
|
|
101
|
+
*/
|
|
102
|
+
async function generateDeployJsonFile(appPath, appName, config) {
|
|
103
|
+
const deployJson = {
|
|
104
|
+
apiVersion: 'v1',
|
|
105
|
+
kind: 'ApplicationDeployment',
|
|
106
|
+
metadata: {
|
|
107
|
+
name: appName,
|
|
108
|
+
namespace: 'default'
|
|
109
|
+
},
|
|
110
|
+
spec: {
|
|
111
|
+
application: {
|
|
112
|
+
name: appName,
|
|
113
|
+
version: '1.0.0',
|
|
114
|
+
language: config.language,
|
|
115
|
+
port: config.port
|
|
116
|
+
},
|
|
117
|
+
services: {
|
|
118
|
+
database: config.database,
|
|
119
|
+
redis: config.redis,
|
|
120
|
+
storage: config.storage,
|
|
121
|
+
authentication: config.authentication
|
|
122
|
+
},
|
|
123
|
+
deployment: {
|
|
124
|
+
replicas: 1,
|
|
125
|
+
strategy: 'RollingUpdate'
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
await fs.writeFile(
|
|
131
|
+
path.join(appPath, 'aifabrix-deploy.json'),
|
|
132
|
+
JSON.stringify(deployJson, null, 2)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generate all configuration files for the application
|
|
138
|
+
* @param {string} appPath - Path to application directory
|
|
139
|
+
* @param {string} appName - Application name
|
|
140
|
+
* @param {Object} config - Application configuration
|
|
141
|
+
* @param {Object} existingEnv - Existing environment variables
|
|
142
|
+
*/
|
|
143
|
+
async function generateConfigFiles(appPath, appName, config, existingEnv) {
|
|
144
|
+
try {
|
|
145
|
+
await generateVariablesYamlFile(appPath, appName, config);
|
|
146
|
+
await generateEnvTemplateFile(appPath, config, existingEnv);
|
|
147
|
+
await generateRbacYamlFile(appPath, appName, config);
|
|
148
|
+
await generateDeployJsonFile(appPath, appName, config);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
throw new Error(`Failed to generate configuration files: ${error.message}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
generateConfigFiles
|
|
156
|
+
};
|
|
157
|
+
|
package/lib/app-deploy.js
CHANGED
|
@@ -14,6 +14,7 @@ const path = require('path');
|
|
|
14
14
|
const yaml = require('js-yaml');
|
|
15
15
|
const chalk = require('chalk');
|
|
16
16
|
const pushUtils = require('./push');
|
|
17
|
+
const logger = require('./utils/logger');
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Validate application name format
|
|
@@ -42,6 +43,63 @@ function validateAppName(appName) {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Validates push prerequisites
|
|
48
|
+
* @async
|
|
49
|
+
* @function validatePushPrerequisites
|
|
50
|
+
* @param {string} appName - Application name
|
|
51
|
+
* @param {string} registry - Registry URL
|
|
52
|
+
* @throws {Error} If prerequisites are not met
|
|
53
|
+
*/
|
|
54
|
+
async function validatePushPrerequisites(appName, registry) {
|
|
55
|
+
if (!pushUtils.validateRegistryURL(registry)) {
|
|
56
|
+
throw new Error(`Invalid registry URL format: ${registry}. Expected format: *.azurecr.io`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!await pushUtils.checkLocalImageExists(appName, 'latest')) {
|
|
60
|
+
throw new Error(`Docker image ${appName}:latest not found locally.\nRun 'aifabrix build ${appName}' first`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!await pushUtils.checkAzureCLIInstalled()) {
|
|
64
|
+
throw new Error('Azure CLI is not installed. Install from: https://docs.microsoft.com/cli/azure/install-azure-cli');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Executes push operations
|
|
70
|
+
* @async
|
|
71
|
+
* @function executePush
|
|
72
|
+
* @param {string} appName - Application name
|
|
73
|
+
* @param {string} registry - Registry URL
|
|
74
|
+
* @param {string[]} tags - Tags to push
|
|
75
|
+
* @throws {Error} If push fails
|
|
76
|
+
*/
|
|
77
|
+
async function executePush(appName, registry, tags) {
|
|
78
|
+
if (await pushUtils.checkACRAuthentication(registry)) {
|
|
79
|
+
logger.log(chalk.green(`ā Already authenticated with ${registry}`));
|
|
80
|
+
} else {
|
|
81
|
+
await pushUtils.authenticateACR(registry);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await Promise.all(tags.map(async(tag) => {
|
|
85
|
+
await pushUtils.tagImage(`${appName}:latest`, `${registry}/${appName}:${tag}`);
|
|
86
|
+
await pushUtils.pushImage(`${registry}/${appName}:${tag}`);
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Verifies push result
|
|
92
|
+
* @function verifyPushResult
|
|
93
|
+
* @param {string[]} tags - Tags that were pushed
|
|
94
|
+
* @param {string} registry - Registry URL
|
|
95
|
+
* @param {string} appName - Application name
|
|
96
|
+
*/
|
|
97
|
+
function verifyPushResult(tags, registry, appName) {
|
|
98
|
+
logger.log(chalk.green(`\nā Successfully pushed ${tags.length} tag(s) to ${registry}`));
|
|
99
|
+
logger.log(chalk.gray(`Image: ${registry}/${appName}:*`));
|
|
100
|
+
logger.log(chalk.gray(`Tags: ${tags.join(', ')}`));
|
|
101
|
+
}
|
|
102
|
+
|
|
45
103
|
/**
|
|
46
104
|
* Pushes application image to Azure Container Registry
|
|
47
105
|
* @async
|
|
@@ -52,7 +110,6 @@ function validateAppName(appName) {
|
|
|
52
110
|
*/
|
|
53
111
|
async function pushApp(appName, options = {}) {
|
|
54
112
|
try {
|
|
55
|
-
// Validate app name
|
|
56
113
|
validateAppName(appName);
|
|
57
114
|
|
|
58
115
|
const configPath = path.join(process.cwd(), 'builder', appName, 'variables.yaml');
|
|
@@ -68,37 +125,181 @@ async function pushApp(appName, options = {}) {
|
|
|
68
125
|
throw new Error('Registry URL is required. Provide via --registry flag or configure in variables.yaml under image.registry');
|
|
69
126
|
}
|
|
70
127
|
|
|
71
|
-
if (
|
|
72
|
-
throw new Error(`Invalid
|
|
128
|
+
if (!/^[^.]+\.azurecr\.io$/.test(registry)) {
|
|
129
|
+
throw new Error(`Invalid ACR URL format: ${registry}. Expected format: *.azurecr.io`);
|
|
73
130
|
}
|
|
74
131
|
|
|
75
132
|
const tags = options.tag ? options.tag.split(',').map(t => t.trim()) : ['latest'];
|
|
76
133
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
134
|
+
await validatePushPrerequisites(appName, registry);
|
|
135
|
+
await executePush(appName, registry, tags);
|
|
136
|
+
verifyPushResult(tags, registry, appName);
|
|
80
137
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
138
|
+
} catch (error) {
|
|
139
|
+
throw new Error(`Failed to push application: ${error.message}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
84
142
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Validates that the application directory exists
|
|
145
|
+
* @async
|
|
146
|
+
* @param {string} builderPath - Path to builder directory
|
|
147
|
+
* @param {string} appName - Application name
|
|
148
|
+
* @throws {Error} If directory doesn't exist
|
|
149
|
+
*/
|
|
150
|
+
async function validateAppDirectory(builderPath, appName) {
|
|
151
|
+
try {
|
|
152
|
+
await fs.access(builderPath);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error.code === 'ENOENT') {
|
|
155
|
+
throw new Error(`Application '${appName}' not found in builder/. Run 'aifabrix create ${appName}' first`);
|
|
89
156
|
}
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
90
160
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
161
|
+
/**
|
|
162
|
+
* Loads variables.yaml file
|
|
163
|
+
* @async
|
|
164
|
+
* @param {string} variablesPath - Path to variables.yaml
|
|
165
|
+
* @returns {Promise<Object>} Variables object
|
|
166
|
+
* @throws {Error} If file cannot be loaded
|
|
167
|
+
*/
|
|
168
|
+
async function loadVariablesFile(variablesPath) {
|
|
169
|
+
try {
|
|
170
|
+
const variablesContent = await fs.readFile(variablesPath, 'utf8');
|
|
171
|
+
return yaml.load(variablesContent);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
throw new Error(`Failed to load configuration from variables.yaml: ${error.message}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
95
176
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Extracts deployment configuration from options and variables
|
|
179
|
+
* @param {Object} options - CLI options
|
|
180
|
+
* @param {Object} variables - Variables from variables.yaml
|
|
181
|
+
* @returns {Object} Extracted configuration
|
|
182
|
+
*/
|
|
183
|
+
function extractDeploymentConfig(options, variables) {
|
|
184
|
+
return {
|
|
185
|
+
controllerUrl: options.controller || variables.deployment?.controllerUrl,
|
|
186
|
+
envKey: options.environment || variables.deployment?.environment || 'dev',
|
|
187
|
+
clientId: options.clientId || variables.deployment?.clientId,
|
|
188
|
+
clientSecret: options.clientSecret || variables.deployment?.clientSecret,
|
|
189
|
+
poll: options.poll !== false,
|
|
190
|
+
pollInterval: options.pollInterval || 5000,
|
|
191
|
+
pollMaxAttempts: options.pollMaxAttempts || 60
|
|
192
|
+
};
|
|
193
|
+
}
|
|
99
194
|
|
|
100
|
-
|
|
101
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Validates required deployment configuration
|
|
197
|
+
* @param {Object} config - Deployment configuration
|
|
198
|
+
* @throws {Error} If configuration is invalid
|
|
199
|
+
*/
|
|
200
|
+
function validateDeploymentConfig(config) {
|
|
201
|
+
if (!config.controllerUrl) {
|
|
202
|
+
throw new Error('Controller URL is required. Set it in variables.yaml or use --controller flag');
|
|
203
|
+
}
|
|
204
|
+
if (!config.clientId || !config.clientSecret) {
|
|
205
|
+
throw new Error('Client ID and Client Secret are required. Set them in variables.yaml or use --client-id and --client-secret flags');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Loads deployment configuration from variables.yaml
|
|
211
|
+
* @async
|
|
212
|
+
* @param {string} appName - Application name
|
|
213
|
+
* @param {Object} options - CLI options
|
|
214
|
+
* @returns {Promise<Object>} Deployment configuration
|
|
215
|
+
* @throws {Error} If configuration is invalid
|
|
216
|
+
*/
|
|
217
|
+
async function loadDeploymentConfig(appName, options) {
|
|
218
|
+
const builderPath = path.join(process.cwd(), 'builder', appName);
|
|
219
|
+
await validateAppDirectory(builderPath, appName);
|
|
220
|
+
|
|
221
|
+
const variablesPath = path.join(builderPath, 'variables.yaml');
|
|
222
|
+
const variables = await loadVariablesFile(variablesPath);
|
|
223
|
+
|
|
224
|
+
const config = extractDeploymentConfig(options, variables);
|
|
225
|
+
validateDeploymentConfig(config);
|
|
226
|
+
|
|
227
|
+
return config;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generates and validates deployment manifest
|
|
232
|
+
* @async
|
|
233
|
+
* @param {string} appName - Application name
|
|
234
|
+
* @returns {Promise<Object>} Deployment manifest
|
|
235
|
+
* @throws {Error} If generation or validation fails
|
|
236
|
+
*/
|
|
237
|
+
async function generateAndValidateManifest(appName) {
|
|
238
|
+
logger.log(chalk.blue(`\nš Generating deployment manifest for ${appName}...`));
|
|
239
|
+
const generator = require('./generator');
|
|
240
|
+
|
|
241
|
+
// generateDeployJson already validates against schema and throws on error
|
|
242
|
+
const manifestPath = await generator.generateDeployJson(appName);
|
|
243
|
+
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
244
|
+
|
|
245
|
+
// Additional validation for warnings (schema validation already passed)
|
|
246
|
+
// Note: Schema validation happens in generateDeployJson, this is just for additional checks if needed
|
|
247
|
+
return { manifest, manifestPath };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Displays deployment information
|
|
252
|
+
* @param {Object} manifest - Deployment manifest
|
|
253
|
+
* @param {string} manifestPath - Path to manifest file
|
|
254
|
+
*/
|
|
255
|
+
function displayDeploymentInfo(manifest, manifestPath) {
|
|
256
|
+
logger.log(chalk.green(`ā Manifest generated: ${manifestPath}`));
|
|
257
|
+
logger.log(chalk.blue(` Key: ${manifest.key}`));
|
|
258
|
+
logger.log(chalk.blue(` Display Name: ${manifest.displayName}`));
|
|
259
|
+
logger.log(chalk.blue(` Image: ${manifest.image}`));
|
|
260
|
+
logger.log(chalk.blue(` Port: ${manifest.port}`));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Executes deployment to controller
|
|
265
|
+
* @async
|
|
266
|
+
* @param {Object} manifest - Deployment manifest
|
|
267
|
+
* @param {Object} config - Deployment configuration
|
|
268
|
+
* @returns {Promise<Object>} Deployment result
|
|
269
|
+
*/
|
|
270
|
+
async function executeDeployment(manifest, config) {
|
|
271
|
+
logger.log(chalk.blue(`\nš Deploying to ${config.controllerUrl} (environment: ${config.envKey})...`));
|
|
272
|
+
const deployer = require('./deployer');
|
|
273
|
+
return await deployer.deployToController(
|
|
274
|
+
manifest,
|
|
275
|
+
config.controllerUrl,
|
|
276
|
+
config.envKey,
|
|
277
|
+
config.clientId,
|
|
278
|
+
config.clientSecret,
|
|
279
|
+
{
|
|
280
|
+
poll: config.poll,
|
|
281
|
+
pollInterval: config.pollInterval,
|
|
282
|
+
pollMaxAttempts: config.pollMaxAttempts
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Displays deployment results
|
|
289
|
+
* @param {Object} result - Deployment result
|
|
290
|
+
*/
|
|
291
|
+
function displayDeploymentResults(result) {
|
|
292
|
+
logger.log(chalk.green('\nā
Deployment initiated successfully'));
|
|
293
|
+
if (result.deploymentUrl) {
|
|
294
|
+
logger.log(chalk.blue(` URL: ${result.deploymentUrl}`));
|
|
295
|
+
}
|
|
296
|
+
if (result.deploymentId) {
|
|
297
|
+
logger.log(chalk.blue(` Deployment ID: ${result.deploymentId}`));
|
|
298
|
+
}
|
|
299
|
+
if (result.status) {
|
|
300
|
+
const statusIcon = result.status.status === 'completed' ? 'ā
' :
|
|
301
|
+
result.status.status === 'failed' ? 'ā' : 'ā³';
|
|
302
|
+
logger.log(chalk.blue(` Status: ${statusIcon} ${result.status.status}`));
|
|
102
303
|
}
|
|
103
304
|
}
|
|
104
305
|
|
|
@@ -129,70 +330,20 @@ async function deployApp(appName, options = {}) {
|
|
|
129
330
|
|
|
130
331
|
validateAppName(appName);
|
|
131
332
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
333
|
+
// 2. Load deployment configuration
|
|
334
|
+
const config = await loadDeploymentConfig(appName, options);
|
|
135
335
|
|
|
136
|
-
//
|
|
137
|
-
const
|
|
138
|
-
try {
|
|
139
|
-
await fs.access(builderPath);
|
|
140
|
-
} catch (error) {
|
|
141
|
-
if (error.code === 'ENOENT') {
|
|
142
|
-
throw new Error(`Application '${appName}' not found in builder/. Run 'aifabrix create ${appName}' first`);
|
|
143
|
-
}
|
|
144
|
-
throw error;
|
|
145
|
-
}
|
|
336
|
+
// 3. Generate and validate manifest
|
|
337
|
+
const { manifest, manifestPath } = await generateAndValidateManifest(appName);
|
|
146
338
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
const generator = require('./generator');
|
|
150
|
-
const manifestPath = await generator.generateDeployJson(appName);
|
|
151
|
-
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
152
|
-
|
|
153
|
-
// 4. Validate manifest
|
|
154
|
-
const validation = generator.validateDeploymentJson(manifest);
|
|
155
|
-
if (!validation.valid) {
|
|
156
|
-
console.log(chalk.red('\nā Validation failed:'));
|
|
157
|
-
validation.errors.forEach(error => console.log(chalk.red(` ⢠${error}`)));
|
|
158
|
-
throw new Error('Deployment manifest validation failed');
|
|
159
|
-
}
|
|
339
|
+
// 4. Display deployment info
|
|
340
|
+
displayDeploymentInfo(manifest, manifestPath);
|
|
160
341
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
validation.warnings.forEach(warning => console.log(chalk.yellow(` ⢠${warning}`)));
|
|
164
|
-
}
|
|
342
|
+
// 5. Execute deployment
|
|
343
|
+
const result = await executeDeployment(manifest, config);
|
|
165
344
|
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
console.log(chalk.blue(` Key: ${manifest.key}`));
|
|
169
|
-
console.log(chalk.blue(` Display Name: ${manifest.displayName}`));
|
|
170
|
-
console.log(chalk.blue(` Image: ${manifest.image}`));
|
|
171
|
-
console.log(chalk.blue(` Port: ${manifest.port}`));
|
|
172
|
-
|
|
173
|
-
// 6. Deploy to controller
|
|
174
|
-
console.log(chalk.blue(`\nš Deploying to ${options.controller}...`));
|
|
175
|
-
const deployer = require('./deployer');
|
|
176
|
-
const result = await deployer.deployToController(manifest, options.controller, {
|
|
177
|
-
environment: options.environment,
|
|
178
|
-
poll: options.poll !== false, // Poll by default
|
|
179
|
-
pollInterval: options.pollInterval || 5000,
|
|
180
|
-
pollMaxAttempts: options.pollMaxAttempts || 60
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// 7. Display results
|
|
184
|
-
console.log(chalk.green('\nā
Deployment initiated successfully'));
|
|
185
|
-
if (result.deploymentUrl) {
|
|
186
|
-
console.log(chalk.blue(` URL: ${result.deploymentUrl}`));
|
|
187
|
-
}
|
|
188
|
-
if (result.deploymentId) {
|
|
189
|
-
console.log(chalk.blue(` Deployment ID: ${result.deploymentId}`));
|
|
190
|
-
}
|
|
191
|
-
if (result.status) {
|
|
192
|
-
const statusIcon = result.status.status === 'completed' ? 'ā
' :
|
|
193
|
-
result.status.status === 'failed' ? 'ā' : 'ā³';
|
|
194
|
-
console.log(chalk.blue(` Status: ${statusIcon} ${result.status.status}`));
|
|
195
|
-
}
|
|
345
|
+
// 6. Display results
|
|
346
|
+
displayDeploymentResults(result);
|
|
196
347
|
|
|
197
348
|
return result;
|
|
198
349
|
|