@aifabrix/builder 2.0.0 → 2.0.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 +6 -2
- 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 +334 -133
- package/lib/app.js +208 -274
- package/lib/audit-logger.js +2 -0
- package/lib/build.js +209 -98
- 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/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/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 +13 -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 +168 -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/lib/app.js
CHANGED
|
@@ -11,206 +11,223 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs').promises;
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const inquirer = require('inquirer');
|
|
15
14
|
const chalk = require('chalk');
|
|
16
|
-
const
|
|
17
|
-
const { generateVariablesYaml, generateEnvTemplate, generateRbacYaml } = require('./templates');
|
|
18
|
-
const { readExistingEnv, generateEnvTemplate: generateEnvTemplateFromReader } = require('./env-reader');
|
|
15
|
+
const { readExistingEnv } = require('./env-reader');
|
|
19
16
|
const build = require('./build');
|
|
20
17
|
const appRun = require('./app-run');
|
|
21
|
-
const
|
|
18
|
+
const { validateTemplate, copyTemplateFiles, copyAppFiles } = require('./template-validator');
|
|
19
|
+
const { promptForOptions } = require('./app-prompts');
|
|
20
|
+
const { generateConfigFiles } = require('./app-config');
|
|
21
|
+
const { validateAppName, pushApp } = require('./app-push');
|
|
22
|
+
const { generateDockerfileForApp } = require('./app-dockerfile');
|
|
23
|
+
const { loadTemplateVariables, updateTemplateVariables, mergeTemplateVariables } = require('./utils/template-helpers');
|
|
24
|
+
const logger = require('./utils/logger');
|
|
22
25
|
|
|
23
26
|
/**
|
|
24
|
-
*
|
|
25
|
-
* @param {string} appName - Application name
|
|
26
|
-
* @
|
|
27
|
+
* Displays success message after app creation
|
|
28
|
+
* @param {string} appName - Application name
|
|
29
|
+
* @param {Object} config - Final configuration
|
|
30
|
+
* @param {string} envConversionMessage - Environment conversion message
|
|
27
31
|
*/
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
function displaySuccessMessage(appName, config, envConversionMessage, hasAppFiles = false) {
|
|
33
|
+
logger.log(chalk.green('\n✓ Application created successfully!'));
|
|
34
|
+
logger.log(chalk.blue(`\nApplication: ${appName}`));
|
|
35
|
+
logger.log(chalk.blue(`Location: builder/${appName}/`));
|
|
36
|
+
if (hasAppFiles) {
|
|
37
|
+
logger.log(chalk.blue(`Application files: apps/${appName}/`));
|
|
31
38
|
}
|
|
39
|
+
logger.log(chalk.blue(`Language: ${config.language}`));
|
|
40
|
+
logger.log(chalk.blue(`Port: ${config.port}`));
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
}
|
|
42
|
+
if (config.database) logger.log(chalk.yellow(' - Database enabled'));
|
|
43
|
+
if (config.redis) logger.log(chalk.yellow(' - Redis enabled'));
|
|
44
|
+
if (config.storage) logger.log(chalk.yellow(' - Storage enabled'));
|
|
45
|
+
if (config.authentication) logger.log(chalk.yellow(' - Authentication enabled'));
|
|
38
46
|
|
|
39
|
-
|
|
40
|
-
if (appName.startsWith('-') || appName.endsWith('-')) {
|
|
41
|
-
throw new Error('Application name cannot start or end with a dash');
|
|
42
|
-
}
|
|
47
|
+
logger.log(chalk.gray(envConversionMessage));
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
logger.log(chalk.green('\nNext steps:'));
|
|
50
|
+
logger.log(chalk.white('1. Copy env.template to .env and fill in your values'));
|
|
51
|
+
logger.log(chalk.white('2. Run: aifabrix build ' + appName));
|
|
52
|
+
logger.log(chalk.white('3. Run: aifabrix run ' + appName));
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
/**
|
|
51
|
-
*
|
|
56
|
+
* Validates that app directory doesn't already exist
|
|
57
|
+
* @async
|
|
58
|
+
* @param {string} appPath - Application directory path
|
|
52
59
|
* @param {string} appName - Application name
|
|
53
|
-
* @
|
|
54
|
-
* @returns {Promise<Object>} Complete configuration
|
|
60
|
+
* @throws {Error} If directory already exists
|
|
55
61
|
*/
|
|
56
|
-
async function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
message: 'What port should the application run on?',
|
|
65
|
-
default: '3000',
|
|
66
|
-
validate: (input) => {
|
|
67
|
-
const port = parseInt(input, 10);
|
|
68
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
69
|
-
return 'Port must be a number between 1 and 65535';
|
|
70
|
-
}
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
});
|
|
62
|
+
async function validateAppDirectoryNotExists(appPath, appName, baseDir = 'builder') {
|
|
63
|
+
try {
|
|
64
|
+
await fs.access(appPath);
|
|
65
|
+
throw new Error(`Application '${appName}' already exists in ${baseDir}/${appName}/`);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (error.code !== 'ENOENT') {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
74
70
|
}
|
|
71
|
+
}
|
|
75
72
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
],
|
|
86
|
-
default: 'typescript'
|
|
87
|
-
});
|
|
73
|
+
/**
|
|
74
|
+
* Handles GitHub workflow generation if requested
|
|
75
|
+
* @async
|
|
76
|
+
* @param {Object} options - Creation options
|
|
77
|
+
* @param {Object} config - Final configuration
|
|
78
|
+
*/
|
|
79
|
+
async function handleGitHubWorkflows(options, config) {
|
|
80
|
+
if (!options.github) {
|
|
81
|
+
return;
|
|
88
82
|
}
|
|
89
83
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
84
|
+
const githubGen = require('./github-generator');
|
|
85
|
+
|
|
86
|
+
// Parse github-steps if provided
|
|
87
|
+
const githubSteps = options.githubSteps
|
|
88
|
+
? options.githubSteps.split(',').map(s => s.trim()).filter(s => s.length > 0)
|
|
89
|
+
: [];
|
|
90
|
+
|
|
91
|
+
const workflowFiles = await githubGen.generateGithubWorkflows(
|
|
92
|
+
process.cwd(),
|
|
93
|
+
config,
|
|
94
|
+
{
|
|
95
|
+
mainBranch: options.mainBranch || 'main',
|
|
96
|
+
uploadCoverage: true,
|
|
97
|
+
githubSteps: githubSteps
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
logger.log(chalk.green('✓ Generated GitHub Actions workflows:'));
|
|
102
|
+
workflowFiles.forEach(file => logger.log(chalk.gray(` - ${file}`)));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Validates app creation prerequisites
|
|
107
|
+
* @async
|
|
108
|
+
* @function validateAppCreation
|
|
109
|
+
* @param {string} appName - Application name
|
|
110
|
+
* @param {Object} options - Creation options
|
|
111
|
+
* @param {string} appPath - Application directory path
|
|
112
|
+
* @throws {Error} If validation fails
|
|
113
|
+
*/
|
|
114
|
+
async function validateAppCreation(appName, options, appPath) {
|
|
115
|
+
validateAppName(appName);
|
|
116
|
+
await validateAppDirectoryNotExists(appPath, appName, 'builder');
|
|
99
117
|
|
|
100
|
-
if (!
|
|
101
|
-
|
|
102
|
-
type: 'confirm',
|
|
103
|
-
name: 'redis',
|
|
104
|
-
message: 'Does your application need Redis?',
|
|
105
|
-
default: false
|
|
106
|
-
});
|
|
118
|
+
if (!options.app) {
|
|
119
|
+
return;
|
|
107
120
|
}
|
|
108
121
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
const appsPath = path.join(process.cwd(), 'apps', appName);
|
|
123
|
+
try {
|
|
124
|
+
await fs.access(appsPath);
|
|
125
|
+
throw new Error(`Application '${appName}' already exists in apps/${appName}/`);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error.code !== 'ENOENT') {
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
116
130
|
}
|
|
131
|
+
}
|
|
117
132
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Processes template files if template is specified
|
|
135
|
+
* @async
|
|
136
|
+
* @function processTemplateFiles
|
|
137
|
+
* @param {string} template - Template name
|
|
138
|
+
* @param {string} appPath - Application directory path
|
|
139
|
+
* @param {string} appName - Application name
|
|
140
|
+
* @param {Object} options - Creation options
|
|
141
|
+
* @param {Object} config - Final configuration
|
|
142
|
+
* @throws {Error} If template processing fails
|
|
143
|
+
*/
|
|
144
|
+
async function processTemplateFiles(template, appPath, appName, options, config) {
|
|
145
|
+
if (!template) {
|
|
146
|
+
return;
|
|
125
147
|
}
|
|
126
148
|
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
appName,
|
|
133
|
-
port: parseInt(options.port || answers.port || 3000, 10),
|
|
134
|
-
language: options.language || answers.language || 'typescript',
|
|
135
|
-
database: options.database || answers.database || false,
|
|
136
|
-
redis: options.redis || answers.redis || false,
|
|
137
|
-
storage: options.storage || answers.storage || false,
|
|
138
|
-
authentication: options.authentication || answers.authentication || false
|
|
139
|
-
};
|
|
149
|
+
await validateTemplate(template);
|
|
150
|
+
const copiedFiles = await copyTemplateFiles(template, appPath);
|
|
151
|
+
logger.log(chalk.green(`✓ Copied ${copiedFiles.length} file(s) from template '${template}'`));
|
|
152
|
+
await updateTemplateVariables(appPath, appName, options, config);
|
|
140
153
|
}
|
|
141
154
|
|
|
142
155
|
/**
|
|
143
|
-
*
|
|
144
|
-
* @
|
|
156
|
+
* Updates variables.yaml for --app flag
|
|
157
|
+
* @async
|
|
158
|
+
* @function updateVariablesForAppFlag
|
|
159
|
+
* @param {string} appPath - Application directory path
|
|
145
160
|
* @param {string} appName - Application name
|
|
146
|
-
* @
|
|
147
|
-
* @param {Object} existingEnv - Existing environment variables
|
|
161
|
+
* @throws {Error} If update fails
|
|
148
162
|
*/
|
|
149
|
-
async function
|
|
163
|
+
async function updateVariablesForAppFlag(appPath, appName) {
|
|
150
164
|
try {
|
|
151
|
-
|
|
152
|
-
const
|
|
153
|
-
await fs.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
envTemplate = envResult.template;
|
|
160
|
-
|
|
161
|
-
if (envResult.warnings.length > 0) {
|
|
162
|
-
console.log(chalk.yellow('\n⚠️ Environment conversion warnings:'));
|
|
163
|
-
envResult.warnings.forEach(warning => console.log(chalk.yellow(` - ${warning}`)));
|
|
164
|
-
}
|
|
165
|
+
const yaml = require('js-yaml');
|
|
166
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
167
|
+
const variablesContent = await fs.readFile(variablesPath, 'utf8');
|
|
168
|
+
const variables = yaml.load(variablesContent);
|
|
169
|
+
|
|
170
|
+
if (variables.build) {
|
|
171
|
+
variables.build.context = '../..';
|
|
172
|
+
variables.build.envOutputPath = `apps/${appName}/.env`;
|
|
165
173
|
} else {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
// Generate rbac.yaml if authentication is enabled
|
|
171
|
-
if (config.authentication) {
|
|
172
|
-
const rbacYaml = generateRbacYaml(appName, config);
|
|
173
|
-
if (rbacYaml) {
|
|
174
|
-
await fs.writeFile(path.join(appPath, 'rbac.yaml'), rbacYaml);
|
|
175
|
-
}
|
|
174
|
+
variables.build = {
|
|
175
|
+
context: '../..',
|
|
176
|
+
envOutputPath: `apps/${appName}/.env`
|
|
177
|
+
};
|
|
176
178
|
}
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
const deployJson = {
|
|
180
|
-
apiVersion: 'v1',
|
|
181
|
-
kind: 'ApplicationDeployment',
|
|
182
|
-
metadata: {
|
|
183
|
-
name: appName,
|
|
184
|
-
namespace: 'default'
|
|
185
|
-
},
|
|
186
|
-
spec: {
|
|
187
|
-
application: {
|
|
188
|
-
name: appName,
|
|
189
|
-
version: '1.0.0',
|
|
190
|
-
language: config.language,
|
|
191
|
-
port: config.port
|
|
192
|
-
},
|
|
193
|
-
services: {
|
|
194
|
-
database: config.database,
|
|
195
|
-
redis: config.redis,
|
|
196
|
-
storage: config.storage,
|
|
197
|
-
authentication: config.authentication
|
|
198
|
-
},
|
|
199
|
-
deployment: {
|
|
200
|
-
replicas: 1,
|
|
201
|
-
strategy: 'RollingUpdate'
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
await fs.writeFile(
|
|
207
|
-
path.join(appPath, 'aifabrix-deploy.json'),
|
|
208
|
-
JSON.stringify(deployJson, null, 2)
|
|
209
|
-
);
|
|
210
|
-
|
|
180
|
+
await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }));
|
|
211
181
|
} catch (error) {
|
|
212
|
-
|
|
182
|
+
logger.warn(chalk.yellow(`⚠️ Warning: Could not update variables.yaml: ${error.message}`));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Gets language from config or variables.yaml
|
|
188
|
+
* @async
|
|
189
|
+
* @function getLanguageForAppFiles
|
|
190
|
+
* @param {string} language - Language from config
|
|
191
|
+
* @param {string} appPath - Application directory path
|
|
192
|
+
* @returns {Promise<string>} Language to use
|
|
193
|
+
* @throws {Error} If language cannot be determined
|
|
194
|
+
*/
|
|
195
|
+
async function getLanguageForAppFiles(language, appPath) {
|
|
196
|
+
if (language) {
|
|
197
|
+
return language;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const yaml = require('js-yaml');
|
|
201
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
202
|
+
const variablesContent = await fs.readFile(variablesPath, 'utf8');
|
|
203
|
+
const variables = yaml.load(variablesContent);
|
|
204
|
+
const languageFromYaml = variables?.build?.language;
|
|
205
|
+
|
|
206
|
+
if (!languageFromYaml) {
|
|
207
|
+
throw new Error('Language not specified and could not be determined from variables.yaml. Use --language flag or ensure variables.yaml contains build.language');
|
|
213
208
|
}
|
|
209
|
+
|
|
210
|
+
return languageFromYaml;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Sets up apps directory and copies application files
|
|
215
|
+
* @async
|
|
216
|
+
* @function setupAppFiles
|
|
217
|
+
* @param {string} appName - Application name
|
|
218
|
+
* @param {string} appPath - Application directory path
|
|
219
|
+
* @param {Object} config - Final configuration
|
|
220
|
+
* @param {Object} options - Creation options
|
|
221
|
+
* @throws {Error} If setup fails
|
|
222
|
+
*/
|
|
223
|
+
async function setupAppFiles(appName, appPath, config, options) {
|
|
224
|
+
const appsPath = path.join(process.cwd(), 'apps', appName);
|
|
225
|
+
await fs.mkdir(appsPath, { recursive: true });
|
|
226
|
+
await updateVariablesForAppFlag(appPath, appName);
|
|
227
|
+
|
|
228
|
+
const language = await getLanguageForAppFiles(config.language || options.language, appPath);
|
|
229
|
+
const copiedFiles = await copyAppFiles(language, appsPath);
|
|
230
|
+
logger.log(chalk.green(`✓ Copied ${copiedFiles.length} application file(s) to apps/${appName}/`));
|
|
214
231
|
}
|
|
215
232
|
|
|
216
233
|
/**
|
|
@@ -227,7 +244,7 @@ async function generateConfigFiles(appPath, appName, config, existingEnv) {
|
|
|
227
244
|
* @param {boolean} [options.storage] - Requires file storage
|
|
228
245
|
* @param {boolean} [options.authentication] - Requires authentication/RBAC
|
|
229
246
|
* @param {string} [options.language] - Runtime language (typescript/python)
|
|
230
|
-
* @param {string} [options.template] - Template to use (
|
|
247
|
+
* @param {string} [options.template] - Template to use (e.g., controller, keycloak)
|
|
231
248
|
* @returns {Promise<void>} Resolves when app is created
|
|
232
249
|
* @throws {Error} If creation fails
|
|
233
250
|
*
|
|
@@ -237,75 +254,40 @@ async function generateConfigFiles(appPath, appName, config, existingEnv) {
|
|
|
237
254
|
*/
|
|
238
255
|
async function createApp(appName, options = {}) {
|
|
239
256
|
try {
|
|
240
|
-
// Validate
|
|
241
|
-
|
|
257
|
+
// Validate appName early
|
|
258
|
+
if (!appName || typeof appName !== 'string') {
|
|
259
|
+
throw new Error('Application name is required');
|
|
260
|
+
}
|
|
242
261
|
|
|
243
|
-
// Check if directory already exists
|
|
244
262
|
const builderPath = path.join(process.cwd(), 'builder');
|
|
245
263
|
const appPath = path.join(builderPath, appName);
|
|
246
264
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (error.code !== 'ENOENT') {
|
|
252
|
-
throw error;
|
|
253
|
-
}
|
|
265
|
+
await validateAppCreation(appName, options, appPath);
|
|
266
|
+
|
|
267
|
+
if (options.template) {
|
|
268
|
+
await validateTemplate(options.template);
|
|
254
269
|
}
|
|
255
270
|
|
|
256
|
-
|
|
257
|
-
const
|
|
271
|
+
const templateVariables = await loadTemplateVariables(options.template);
|
|
272
|
+
const mergedOptions = mergeTemplateVariables(options, templateVariables);
|
|
273
|
+
const config = await promptForOptions(appName, mergedOptions);
|
|
258
274
|
|
|
259
|
-
// Create directory structure
|
|
260
275
|
await fs.mkdir(appPath, { recursive: true });
|
|
276
|
+
await processTemplateFiles(options.template, appPath, appName, options, config);
|
|
261
277
|
|
|
262
|
-
// Check for existing .env file
|
|
263
278
|
const existingEnv = await readExistingEnv(process.cwd());
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
envConversionMessage = '\n✓ Found existing .env file - sensitive values will be converted to kv:// references';
|
|
268
|
-
}
|
|
279
|
+
const envConversionMessage = existingEnv
|
|
280
|
+
? '\n✓ Found existing .env file - sensitive values will be converted to kv:// references'
|
|
281
|
+
: '';
|
|
269
282
|
|
|
270
|
-
// Generate configuration files
|
|
271
283
|
await generateConfigFiles(appPath, appName, config, existingEnv);
|
|
272
284
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const githubGen = require('./github-generator');
|
|
276
|
-
const workflowFiles = await githubGen.generateGithubWorkflows(
|
|
277
|
-
process.cwd(),
|
|
278
|
-
config,
|
|
279
|
-
{
|
|
280
|
-
mainBranch: options.mainBranch || 'main',
|
|
281
|
-
uploadCoverage: true,
|
|
282
|
-
publishToNpm: options.template === 'platform'
|
|
283
|
-
}
|
|
284
|
-
);
|
|
285
|
-
|
|
286
|
-
console.log(chalk.green('✓ Generated GitHub Actions workflows:'));
|
|
287
|
-
workflowFiles.forEach(file => console.log(chalk.gray(` - ${file}`)));
|
|
285
|
+
if (options.app) {
|
|
286
|
+
await setupAppFiles(appName, appPath, config, options);
|
|
288
287
|
}
|
|
289
288
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
console.log(chalk.blue(`\nApplication: ${appName}`));
|
|
293
|
-
console.log(chalk.blue(`Location: builder/${appName}/`));
|
|
294
|
-
console.log(chalk.blue(`Language: ${config.language}`));
|
|
295
|
-
console.log(chalk.blue(`Port: ${config.port}`));
|
|
296
|
-
|
|
297
|
-
if (config.database) console.log(chalk.yellow(' - Database enabled'));
|
|
298
|
-
if (config.redis) console.log(chalk.yellow(' - Redis enabled'));
|
|
299
|
-
if (config.storage) console.log(chalk.yellow(' - Storage enabled'));
|
|
300
|
-
if (config.authentication) console.log(chalk.yellow(' - Authentication enabled'));
|
|
301
|
-
|
|
302
|
-
console.log(chalk.gray(envConversionMessage));
|
|
303
|
-
|
|
304
|
-
console.log(chalk.green('\nNext steps:'));
|
|
305
|
-
console.log(chalk.white('1. Copy env.template to .env and fill in your values'));
|
|
306
|
-
console.log(chalk.white('2. Run: aifabrix build ' + appName));
|
|
307
|
-
console.log(chalk.white('3. Run: aifabrix run ' + appName));
|
|
308
|
-
|
|
289
|
+
await handleGitHubWorkflows(options, config);
|
|
290
|
+
displaySuccessMessage(appName, config, envConversionMessage, options.app);
|
|
309
291
|
} catch (error) {
|
|
310
292
|
throw new Error(`Failed to create application: ${error.message}`);
|
|
311
293
|
}
|
|
@@ -391,65 +373,13 @@ async function runApp(appName, options = {}) {
|
|
|
391
373
|
}
|
|
392
374
|
|
|
393
375
|
/**
|
|
394
|
-
*
|
|
376
|
+
* Deploys application to controller
|
|
395
377
|
* @async
|
|
396
|
-
* @function
|
|
378
|
+
* @function deployApp
|
|
397
379
|
* @param {string} appName - Name of the application
|
|
398
|
-
* @param {Object} options -
|
|
399
|
-
* @returns {Promise<void>} Resolves when
|
|
380
|
+
* @param {Object} options - Deployment options
|
|
381
|
+
* @returns {Promise<void>} Resolves when deployment is complete
|
|
400
382
|
*/
|
|
401
|
-
async function pushApp(appName, options = {}) {
|
|
402
|
-
try {
|
|
403
|
-
// Validate app name
|
|
404
|
-
validateAppName(appName);
|
|
405
|
-
|
|
406
|
-
const configPath = path.join(process.cwd(), 'builder', appName, 'variables.yaml');
|
|
407
|
-
let config;
|
|
408
|
-
try {
|
|
409
|
-
config = yaml.load(await fs.readFile(configPath, 'utf8'));
|
|
410
|
-
} catch (error) {
|
|
411
|
-
throw new Error(`Failed to load configuration: ${configPath}\nRun 'aifabrix create ${appName}' first`);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
const registry = options.registry || config.image?.registry;
|
|
415
|
-
if (!registry) {
|
|
416
|
-
throw new Error('Registry URL is required. Provide via --registry flag or configure in variables.yaml under image.registry');
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (!pushUtils.validateRegistryURL(registry)) {
|
|
420
|
-
throw new Error(`Invalid registry URL format: ${registry}. Expected format: *.azurecr.io`);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const tags = options.tag ? options.tag.split(',').map(t => t.trim()) : ['latest'];
|
|
424
|
-
|
|
425
|
-
if (!await pushUtils.checkLocalImageExists(appName, 'latest')) {
|
|
426
|
-
throw new Error(`Docker image ${appName}:latest not found locally.\nRun 'aifabrix build ${appName}' first`);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (!await pushUtils.checkAzureCLIInstalled()) {
|
|
430
|
-
throw new Error('Azure CLI is not installed. Install from: https://docs.microsoft.com/cli/azure/install-azure-cli');
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
if (await pushUtils.checkACRAuthentication(registry)) {
|
|
434
|
-
console.log(chalk.green(`✓ Already authenticated with ${registry}`));
|
|
435
|
-
} else {
|
|
436
|
-
await pushUtils.authenticateACR(registry);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
await Promise.all(tags.map(async(tag) => {
|
|
440
|
-
await pushUtils.tagImage(`${appName}:latest`, `${registry}/${appName}:${tag}`);
|
|
441
|
-
await pushUtils.pushImage(`${registry}/${appName}:${tag}`);
|
|
442
|
-
}));
|
|
443
|
-
|
|
444
|
-
console.log(chalk.green(`\n✓ Successfully pushed ${tags.length} tag(s) to ${registry}`));
|
|
445
|
-
console.log(chalk.gray(`Image: ${registry}/${appName}:*`));
|
|
446
|
-
console.log(chalk.gray(`Tags: ${tags.join(', ')}`));
|
|
447
|
-
|
|
448
|
-
} catch (error) {
|
|
449
|
-
throw new Error(`Failed to push application: ${error.message}`);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
383
|
async function deployApp(appName, options = {}) {
|
|
454
384
|
const appDeploy = require('./app-deploy');
|
|
455
385
|
return appDeploy.deployApp(appName, options);
|
|
@@ -461,8 +391,12 @@ module.exports = {
|
|
|
461
391
|
runApp,
|
|
462
392
|
detectLanguage,
|
|
463
393
|
generateDockerfile,
|
|
394
|
+
generateDockerfileForApp,
|
|
464
395
|
pushApp,
|
|
465
396
|
deployApp,
|
|
397
|
+
loadTemplateVariables,
|
|
398
|
+
updateTemplateVariables,
|
|
399
|
+
mergeTemplateVariables,
|
|
466
400
|
checkImageExists: appRun.checkImageExists,
|
|
467
401
|
checkContainerRunning: appRun.checkContainerRunning,
|
|
468
402
|
stopAndRemoveContainer: appRun.stopAndRemoveContainer,
|