@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.
Files changed (61) hide show
  1. package/README.md +5 -3
  2. package/bin/aifabrix.js +9 -3
  3. package/jest.config.integration.js +30 -0
  4. package/lib/app-config.js +157 -0
  5. package/lib/app-deploy.js +233 -82
  6. package/lib/app-dockerfile.js +112 -0
  7. package/lib/app-prompts.js +244 -0
  8. package/lib/app-push.js +172 -0
  9. package/lib/app-run.js +235 -144
  10. package/lib/app.js +208 -274
  11. package/lib/audit-logger.js +2 -0
  12. package/lib/build.js +177 -125
  13. package/lib/cli.js +76 -86
  14. package/lib/commands/app.js +414 -0
  15. package/lib/commands/login.js +304 -0
  16. package/lib/config.js +78 -0
  17. package/lib/deployer.js +225 -81
  18. package/lib/env-reader.js +45 -30
  19. package/lib/generator.js +308 -191
  20. package/lib/github-generator.js +67 -7
  21. package/lib/infra.js +156 -61
  22. package/lib/push.js +105 -10
  23. package/lib/schema/application-schema.json +30 -2
  24. package/lib/schema/env-config.yaml +9 -1
  25. package/lib/schema/infrastructure-schema.json +589 -0
  26. package/lib/secrets.js +229 -24
  27. package/lib/template-validator.js +205 -0
  28. package/lib/templates.js +305 -170
  29. package/lib/utils/api.js +329 -0
  30. package/lib/utils/cli-utils.js +97 -0
  31. package/lib/utils/compose-generator.js +185 -0
  32. package/lib/utils/docker-build.js +173 -0
  33. package/lib/utils/dockerfile-utils.js +131 -0
  34. package/lib/utils/environment-checker.js +125 -0
  35. package/lib/utils/error-formatter.js +61 -0
  36. package/lib/utils/health-check.js +187 -0
  37. package/lib/utils/logger.js +53 -0
  38. package/lib/utils/template-helpers.js +223 -0
  39. package/lib/utils/variable-transformer.js +271 -0
  40. package/lib/validator.js +27 -112
  41. package/package.json +14 -10
  42. package/templates/README.md +75 -3
  43. package/templates/applications/keycloak/Dockerfile +36 -0
  44. package/templates/applications/keycloak/env.template +32 -0
  45. package/templates/applications/keycloak/rbac.yaml +37 -0
  46. package/templates/applications/keycloak/variables.yaml +56 -0
  47. package/templates/applications/miso-controller/Dockerfile +125 -0
  48. package/templates/applications/miso-controller/env.template +129 -0
  49. package/templates/applications/miso-controller/rbac.yaml +214 -0
  50. package/templates/applications/miso-controller/variables.yaml +56 -0
  51. package/templates/github/release.yaml.hbs +5 -26
  52. package/templates/github/steps/npm.hbs +24 -0
  53. package/templates/infra/compose.yaml +6 -6
  54. package/templates/python/docker-compose.hbs +19 -12
  55. package/templates/python/main.py +80 -0
  56. package/templates/python/requirements.txt +4 -0
  57. package/templates/typescript/Dockerfile.hbs +2 -2
  58. package/templates/typescript/docker-compose.hbs +19 -12
  59. package/templates/typescript/index.ts +116 -0
  60. package/templates/typescript/package.json +26 -0
  61. package/templates/typescript/tsconfig.json +24 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Logger Utility
3
+ *
4
+ * Centralized logging utility that wraps console methods
5
+ * Allows disabling eslint warnings in one place
6
+ *
7
+ * @fileoverview Logger utility for AI Fabrix Builder
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ /* eslint-disable no-console */
13
+
14
+ /**
15
+ * Logger utility that wraps console methods
16
+ * All console statements should use this logger to avoid eslint warnings
17
+ */
18
+ const logger = {
19
+ /**
20
+ * Log informational message
21
+ * @param {...any} args - Arguments to log
22
+ */
23
+ log: (...args) => {
24
+ console.log(...args);
25
+ },
26
+
27
+ /**
28
+ * Log error message
29
+ * @param {...any} args - Arguments to log
30
+ */
31
+ error: (...args) => {
32
+ console.error(...args);
33
+ },
34
+
35
+ /**
36
+ * Log warning message
37
+ * @param {...any} args - Arguments to log
38
+ */
39
+ warn: (...args) => {
40
+ console.warn(...args);
41
+ },
42
+
43
+ /**
44
+ * Log informational message (alias for log)
45
+ * @param {...any} args - Arguments to log
46
+ */
47
+ info: (...args) => {
48
+ console.log(...args);
49
+ }
50
+ };
51
+
52
+ module.exports = logger;
53
+
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Template Helper Utilities
3
+ *
4
+ * Handles template variable loading, updating, and merging
5
+ *
6
+ * @fileoverview Template helper utilities 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 logger = require('./logger');
15
+
16
+ /**
17
+ * Loads template variables from template's variables.yaml file
18
+ * @async
19
+ * @function loadTemplateVariables
20
+ * @param {string} templateName - Template name
21
+ * @returns {Promise<Object|null>} Template variables or null if not found
22
+ */
23
+ async function loadTemplateVariables(templateName) {
24
+ if (!templateName) {
25
+ return null;
26
+ }
27
+
28
+ const yaml = require('js-yaml');
29
+ const templatePath = path.join(__dirname, '..', '..', 'templates', 'applications', templateName);
30
+ const templateVariablesPath = path.join(templatePath, 'variables.yaml');
31
+
32
+ try {
33
+ const templateContent = await fs.readFile(templateVariablesPath, 'utf8');
34
+ return yaml.load(templateContent);
35
+ } catch (error) {
36
+ // Template variables.yaml not found or invalid, continue without it
37
+ if (error.code !== 'ENOENT') {
38
+ logger.warn(chalk.yellow(`⚠️ Warning: Could not load template variables.yaml: ${error.message}`));
39
+ }
40
+ return null;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Updates app key and display name in variables
46
+ * @function updateAppMetadata
47
+ * @param {Object} variables - Variables object
48
+ * @param {string} appName - Application name
49
+ */
50
+ function updateAppMetadata(variables, appName) {
51
+ if (variables.app) {
52
+ variables.app.key = appName;
53
+ }
54
+
55
+ if (variables.app?.displayName && variables.app.displayName.toLowerCase().includes('miso')) {
56
+ variables.app.displayName = appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Updates port in variables if provided
62
+ * @function updatePort
63
+ * @param {Object} variables - Variables object
64
+ * @param {Object} options - CLI options
65
+ * @param {Object} config - Final configuration
66
+ */
67
+ function updatePort(variables, options, config) {
68
+ if (options.port && config.port && variables.port !== config.port) {
69
+ variables.port = config.port;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Updates build configuration
75
+ * @function updateBuildConfig
76
+ * @param {Object} variables - Variables object
77
+ */
78
+ function updateBuildConfig(variables) {
79
+ if (variables.build && variables.build.envOutputPath) {
80
+ variables.build.envOutputPath = null;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Updates database configuration for --app flag
86
+ * @function updateDatabaseConfig
87
+ * @param {Object} variables - Variables object
88
+ * @param {Object} options - CLI options
89
+ * @param {string} appName - Application name
90
+ */
91
+ function updateDatabaseConfig(variables, options, appName) {
92
+ if (!options.app || !variables.requires) {
93
+ return;
94
+ }
95
+
96
+ if (variables.requires.databases) {
97
+ variables.requires.databases = [{ name: appName }];
98
+ } else if (variables.requires.database && !variables.requires.databases) {
99
+ variables.requires.databases = [{ name: appName }];
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Updates variables.yaml file after copying from template
105
+ * Updates app.key, displayName, and port with actual values
106
+ * @async
107
+ * @function updateTemplateVariables
108
+ * @param {string} appPath - Application directory path
109
+ * @param {string} appName - Application name
110
+ * @param {Object} options - CLI options
111
+ * @param {Object} config - Final configuration
112
+ */
113
+ async function updateTemplateVariables(appPath, appName, options, config) {
114
+ const variablesPath = path.join(appPath, 'variables.yaml');
115
+ try {
116
+ const yaml = require('js-yaml');
117
+ const variablesContent = await fs.readFile(variablesPath, 'utf8');
118
+ const variables = yaml.load(variablesContent);
119
+
120
+ updateAppMetadata(variables, appName);
121
+ updatePort(variables, options, config);
122
+ updateBuildConfig(variables);
123
+ updateDatabaseConfig(variables, options, appName);
124
+
125
+ await fs.writeFile(variablesPath, yaml.dump(variables, { indent: 2, lineWidth: 120, noRefs: true }));
126
+ } catch (error) {
127
+ if (error.code !== 'ENOENT') {
128
+ logger.warn(chalk.yellow(`⚠️ Warning: Could not update variables.yaml: ${error.message}`));
129
+ }
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Merges port from template variables if not in options
135
+ * @function mergePort
136
+ * @param {Object} merged - Merged options object
137
+ * @param {Object} templateVariables - Template variables
138
+ */
139
+ function mergePort(merged, templateVariables) {
140
+ if (!merged.port && templateVariables.port) {
141
+ merged.port = templateVariables.port;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Merges language from template variables if not in options
147
+ * @function mergeLanguage
148
+ * @param {Object} merged - Merged options object
149
+ * @param {Object} templateVariables - Template variables
150
+ */
151
+ function mergeLanguage(merged, templateVariables) {
152
+ if (!merged.language && templateVariables.build?.language) {
153
+ merged.language = templateVariables.build.language;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Merges service requirements from template variables if not in options
159
+ * @function mergeServices
160
+ * @param {Object} merged - Merged options object
161
+ * @param {Object} templateVariables - Template variables
162
+ */
163
+ function mergeServices(merged, templateVariables) {
164
+ // Database: use template requires.database if not specified in options
165
+ if (!Object.prototype.hasOwnProperty.call(merged, 'database') &&
166
+ templateVariables.requires?.database !== undefined) {
167
+ merged.database = templateVariables.requires.database;
168
+ }
169
+
170
+ // Redis: use template requires.redis if not specified in options
171
+ if (!Object.prototype.hasOwnProperty.call(merged, 'redis') &&
172
+ templateVariables.requires?.redis !== undefined) {
173
+ merged.redis = templateVariables.requires.redis;
174
+ }
175
+
176
+ // Storage: use template requires.storage if not specified in options
177
+ if (!Object.prototype.hasOwnProperty.call(merged, 'storage') &&
178
+ templateVariables.requires?.storage !== undefined) {
179
+ merged.storage = templateVariables.requires.storage;
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Merges authentication from template variables if not in options
185
+ * @function mergeAuthentication
186
+ * @param {Object} merged - Merged options object
187
+ * @param {Object} templateVariables - Template variables
188
+ */
189
+ function mergeAuthentication(merged, templateVariables) {
190
+ if (!Object.prototype.hasOwnProperty.call(merged, 'authentication') &&
191
+ templateVariables.authentication !== undefined) {
192
+ merged.authentication = !!templateVariables.authentication;
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Merges template variables into options
198
+ * @function mergeTemplateVariables
199
+ * @param {Object} options - User-provided options
200
+ * @param {Object} templateVariables - Template variables from variables.yaml
201
+ * @returns {Object} Merged options object
202
+ */
203
+ function mergeTemplateVariables(options, templateVariables) {
204
+ if (!templateVariables) {
205
+ return options;
206
+ }
207
+
208
+ const merged = { ...options };
209
+
210
+ mergePort(merged, templateVariables);
211
+ mergeLanguage(merged, templateVariables);
212
+ mergeServices(merged, templateVariables);
213
+ mergeAuthentication(merged, templateVariables);
214
+
215
+ return merged;
216
+ }
217
+
218
+ module.exports = {
219
+ loadTemplateVariables,
220
+ updateTemplateVariables,
221
+ mergeTemplateVariables
222
+ };
223
+
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Variable Transformation Utilities
3
+ *
4
+ * Transforms nested variables.yaml structure to flat schema format
5
+ * Converts app.*, image.*, requires.* to schema-compatible structure
6
+ *
7
+ * @fileoverview Variable transformation utilities for AI Fabrix Builder
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ /**
13
+ * Sanitizes authentication type - map keycloak to azure (schema allows: azure, local, none)
14
+ * @function sanitizeAuthType
15
+ * @param {string} authType - Authentication type
16
+ * @returns {string} Sanitized authentication type
17
+ */
18
+ function sanitizeAuthType(authType) {
19
+ if (authType === 'keycloak') {
20
+ return 'azure';
21
+ }
22
+ if (authType && !['azure', 'local', 'none'].includes(authType)) {
23
+ return 'azure'; // Default to azure if invalid type
24
+ }
25
+ return authType;
26
+ }
27
+
28
+ /**
29
+ * Transforms flat structure to schema-compatible format
30
+ * @function transformFlatStructure
31
+ * @param {Object} variables - Raw variables from YAML
32
+ * @param {string} appName - Application name (fallback)
33
+ * @returns {Object} Transformed variables matching schema
34
+ */
35
+ function transformFlatStructure(variables, appName) {
36
+ const result = {
37
+ key: variables.key || appName,
38
+ displayName: variables.displayName || appName,
39
+ description: variables.description || '',
40
+ type: variables.type || 'webapp',
41
+ image: variables.image,
42
+ registryMode: variables.registryMode || 'external',
43
+ port: variables.port || 3000,
44
+ requiresDatabase: variables.requiresDatabase || false,
45
+ requiresRedis: variables.requiresRedis || false,
46
+ requiresStorage: variables.requiresStorage || false,
47
+ databases: variables.databases || [],
48
+ ...variables
49
+ };
50
+
51
+ // Sanitize authentication if present
52
+ if (result.authentication && result.authentication.type) {
53
+ result.authentication = {
54
+ ...result.authentication,
55
+ type: sanitizeAuthType(result.authentication.type)
56
+ };
57
+ }
58
+
59
+ return result;
60
+ }
61
+
62
+ /**
63
+ * Builds image reference string from variables
64
+ * @function buildImageReference
65
+ * @param {Object} variables - Variables configuration
66
+ * @param {string} appName - Application name (fallback)
67
+ * @returns {string} Image reference string
68
+ */
69
+ function buildImageReference(variables, appName) {
70
+ const imageName = variables.image?.name || variables.app?.key || appName;
71
+ const registry = variables.image?.registry;
72
+ const tag = variables.image?.tag || 'latest';
73
+ return registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
74
+ }
75
+
76
+ /**
77
+ * Validates repository URL format
78
+ * @function validateRepositoryUrl
79
+ * @param {string} url - Repository URL
80
+ * @returns {boolean} True if valid
81
+ */
82
+ function validateRepositoryUrl(url) {
83
+ if (!url || url.trim() === '') {
84
+ return false;
85
+ }
86
+ const repoPattern = /^(https:\/\/github\.com\/[^/]+\/[^/]+|https:\/\/gitlab\.com\/[^/]+\/[^/]+|https:\/\/dev\.azure\.com\/[^/]+\/[^/]+\/[^/]+)$/;
87
+ return repoPattern.test(url);
88
+ }
89
+
90
+ /**
91
+ * Validates and transforms repository configuration
92
+ * @function validateRepositoryConfig
93
+ * @param {Object} repository - Repository configuration
94
+ * @returns {Object|null} Validated repository config or null
95
+ */
96
+ function validateRepositoryConfig(repository) {
97
+ if (!repository) {
98
+ return null;
99
+ }
100
+
101
+ const repo = { enabled: repository.enabled || false };
102
+ if (validateRepositoryUrl(repository.repositoryUrl)) {
103
+ repo.repositoryUrl = repository.repositoryUrl;
104
+ }
105
+
106
+ if (repo.enabled || repo.repositoryUrl) {
107
+ return repo;
108
+ }
109
+
110
+ return null;
111
+ }
112
+
113
+ /**
114
+ * Validates and transforms build configuration
115
+ * @function validateBuildConfig
116
+ * @param {Object} build - Build configuration
117
+ * @returns {Object|null} Validated build config or null
118
+ */
119
+ function validateBuildConfig(build) {
120
+ if (!build) {
121
+ return null;
122
+ }
123
+
124
+ const buildConfig = {};
125
+ if (build.envOutputPath) {
126
+ buildConfig.envOutputPath = build.envOutputPath;
127
+ }
128
+ if (build.secrets !== null && build.secrets !== undefined && build.secrets !== '') {
129
+ buildConfig.secrets = build.secrets;
130
+ }
131
+ if (build.localPort) {
132
+ buildConfig.localPort = build.localPort;
133
+ }
134
+ if (build.language) {
135
+ buildConfig.language = build.language;
136
+ }
137
+ if (build.context) {
138
+ buildConfig.context = build.context;
139
+ }
140
+ if (build.dockerfile && build.dockerfile.trim() !== '') {
141
+ buildConfig.dockerfile = build.dockerfile;
142
+ }
143
+
144
+ return Object.keys(buildConfig).length > 0 ? buildConfig : null;
145
+ }
146
+
147
+ /**
148
+ * Validates and transforms deployment configuration
149
+ * @function validateDeploymentConfig
150
+ * @param {Object} deployment - Deployment configuration
151
+ * @returns {Object|null} Validated deployment config or null
152
+ */
153
+ function validateDeploymentConfig(deployment) {
154
+ if (!deployment) {
155
+ return null;
156
+ }
157
+
158
+ const deploymentConfig = {};
159
+ if (deployment.controllerUrl && deployment.controllerUrl.trim() !== '' && /^https:\/\/.*$/.test(deployment.controllerUrl)) {
160
+ deploymentConfig.controllerUrl = deployment.controllerUrl;
161
+ }
162
+ if (deployment.clientId && deployment.clientId.trim() !== '' && /^[a-z0-9-]+$/.test(deployment.clientId)) {
163
+ deploymentConfig.clientId = deployment.clientId;
164
+ }
165
+ if (deployment.clientSecret && deployment.clientSecret.trim() !== '' && /^(kv:\/\/.*|.+)$/.test(deployment.clientSecret)) {
166
+ deploymentConfig.clientSecret = deployment.clientSecret;
167
+ }
168
+
169
+ return Object.keys(deploymentConfig).length > 0 ? deploymentConfig : null;
170
+ }
171
+
172
+ /**
173
+ * Transforms optional fields from variables
174
+ * @function transformOptionalFields
175
+ * @param {Object} variables - Raw variables from YAML
176
+ * @param {Object} transformed - Base transformed object
177
+ * @returns {Object} Transformed object with optional fields added
178
+ */
179
+ function transformOptionalFields(variables, transformed) {
180
+ if (variables.healthCheck) {
181
+ transformed.healthCheck = variables.healthCheck;
182
+ }
183
+
184
+ if (variables.authentication) {
185
+ transformed.authentication = {
186
+ ...variables.authentication,
187
+ type: sanitizeAuthType(variables.authentication.type)
188
+ };
189
+ }
190
+
191
+ const repository = validateRepositoryConfig(variables.repository);
192
+ if (repository) {
193
+ transformed.repository = repository;
194
+ }
195
+
196
+ const build = validateBuildConfig(variables.build);
197
+ if (build) {
198
+ transformed.build = build;
199
+ }
200
+
201
+ const deployment = validateDeploymentConfig(variables.deployment);
202
+ if (deployment) {
203
+ transformed.deployment = deployment;
204
+ }
205
+
206
+ if (variables.startupCommand) {
207
+ transformed.startupCommand = variables.startupCommand;
208
+ }
209
+ if (variables.runtimeVersion) {
210
+ transformed.runtimeVersion = variables.runtimeVersion;
211
+ }
212
+ if (variables.scaling) {
213
+ transformed.scaling = variables.scaling;
214
+ }
215
+ if (variables.frontDoorRouting) {
216
+ transformed.frontDoorRouting = variables.frontDoorRouting;
217
+ }
218
+ if (variables.roles) {
219
+ transformed.roles = variables.roles;
220
+ }
221
+ if (variables.permissions) {
222
+ transformed.permissions = variables.permissions;
223
+ }
224
+
225
+ return transformed;
226
+ }
227
+
228
+ /**
229
+ * Transforms nested variables.yaml structure to flat schema format
230
+ * Converts app.*, image.*, requires.* to schema-compatible structure
231
+ * Handles both flat and nested structures
232
+ *
233
+ * @function transformVariablesForValidation
234
+ * @param {Object} variables - Raw variables from YAML
235
+ * @param {string} appName - Application name (fallback)
236
+ * @returns {Object} Transformed variables matching schema
237
+ */
238
+ function transformVariablesForValidation(variables, appName) {
239
+ // Check if structure is already flat (has key, displayName, image as string)
240
+ const isFlat = variables.key && variables.image && typeof variables.image === 'string';
241
+
242
+ if (isFlat) {
243
+ return transformFlatStructure(variables, appName);
244
+ }
245
+
246
+ // Nested structure - transform it
247
+ const requires = variables.requires || {};
248
+ const imageRef = buildImageReference(variables, appName);
249
+
250
+ // Transform to flat schema structure
251
+ const transformed = {
252
+ key: variables.app?.key || appName,
253
+ displayName: variables.app?.displayName || appName,
254
+ description: variables.app?.description || '',
255
+ type: variables.app?.type || 'webapp',
256
+ image: imageRef,
257
+ registryMode: variables.image?.registryMode || 'external',
258
+ port: variables.port || 3000,
259
+ requiresDatabase: requires.database || false,
260
+ requiresRedis: requires.redis || false,
261
+ requiresStorage: requires.storage || false,
262
+ databases: requires.databases || (requires.database ? [{ name: variables.app?.key || appName }] : [])
263
+ };
264
+
265
+ return transformOptionalFields(variables, transformed);
266
+ }
267
+
268
+ module.exports = {
269
+ transformVariablesForValidation
270
+ };
271
+