@aifabrix/builder 2.31.1 → 2.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/integration/hubspot/README.md +2 -2
- package/integration/hubspot/hubspot-deploy-company.json +17 -14
- package/integration/hubspot/hubspot-deploy-contact.json +19 -16
- package/integration/hubspot/hubspot-deploy-deal.json +21 -18
- package/lib/api/types/datasources.types.js +31 -5
- package/lib/api/types/wizard.types.js +142 -0
- package/lib/api/wizard.api.js +177 -0
- package/lib/{app-config.js → app/config.js} +4 -4
- package/lib/{app-deploy.js → app/deploy.js} +8 -8
- package/lib/app/display.js +90 -0
- package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
- package/lib/{app-down.js → app/down.js} +4 -4
- package/lib/app/helpers.js +218 -0
- package/lib/app/index.js +298 -0
- package/lib/{app-list.js → app/list.js} +6 -6
- package/lib/{app-push.js → app/push.js} +4 -4
- package/lib/{app-readme.js → app/readme.js} +34 -13
- package/lib/{app-register.js → app/register.js} +9 -9
- package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
- package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
- package/lib/{app-run.js → app/run.js} +6 -6
- package/lib/{build.js → build/index.js} +59 -32
- package/lib/build/package.json +7 -0
- package/lib/cli.js +245 -179
- package/lib/commands/app.js +3 -3
- package/lib/commands/datasource.js +4 -4
- package/lib/commands/login-credentials.js +209 -0
- package/lib/commands/login-device.js +254 -0
- package/lib/commands/login.js +67 -378
- package/lib/commands/logout.js +1 -1
- package/lib/commands/secrets-set.js +1 -1
- package/lib/commands/secure.js +2 -2
- package/lib/commands/wizard.js +498 -0
- package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
- package/lib/{config.js → core/config.js} +28 -26
- package/lib/{diff.js → core/diff.js} +157 -72
- package/lib/{secrets.js → core/secrets.js} +86 -49
- package/lib/{templates.js → core/templates-env.js} +14 -222
- package/lib/core/templates.js +279 -0
- package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
- package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
- package/lib/datasource/list.js +223 -0
- package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
- package/lib/{deployer.js → deployment/deployer.js} +48 -18
- package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
- package/lib/{push.js → deployment/push.js} +1 -1
- package/lib/external-system/deploy-helpers.js +145 -0
- package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
- package/lib/external-system/download-helpers.js +114 -0
- package/lib/{external-system-download.js → external-system/download.js} +92 -135
- package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
- package/lib/external-system/test-auth.js +40 -0
- package/lib/external-system/test-execution.js +84 -0
- package/lib/external-system/test-helpers.js +109 -0
- package/lib/{external-system-test.js → external-system/test.js} +174 -192
- package/lib/{generator-builders.js → generator/builders.js} +87 -10
- package/lib/{generator-external.js → generator/external.js} +115 -52
- package/lib/{github-generator.js → generator/github.js} +116 -15
- package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
- package/lib/{generator.js → generator/index.js} +49 -22
- package/lib/{generator-split.js → generator/split.js} +108 -55
- package/lib/generator/wizard-prompts.js +357 -0
- package/lib/generator/wizard.js +490 -0
- package/lib/{infra.js → infrastructure/index.js} +49 -22
- package/lib/schema/external-datasource.schema.json +145 -133
- package/lib/schema/external-system.schema.json +42 -0
- package/lib/utils/api.js +9 -5
- package/lib/utils/app-register-api.js +60 -32
- package/lib/utils/app-register-auth.js +172 -47
- package/lib/utils/app-register-config.js +130 -59
- package/lib/utils/app-run-containers.js +29 -8
- package/lib/utils/build-helpers.js +1 -1
- package/lib/utils/cli-utils.js +78 -30
- package/lib/utils/compose-generator.js +145 -65
- package/lib/utils/config-paths.js +2 -0
- package/lib/utils/deployment-errors.js +1 -1
- package/lib/utils/device-code.js +99 -41
- package/lib/utils/env-config-loader.js +1 -1
- package/lib/utils/env-copy.js +21 -18
- package/lib/utils/env-endpoints.js +115 -67
- package/lib/utils/env-map.js +13 -14
- package/lib/utils/env-ports.js +45 -25
- package/lib/utils/env-template.js +84 -42
- package/lib/utils/error-formatter.js +26 -9
- package/lib/utils/error-formatters/error-parser.js +90 -4
- package/lib/utils/error-formatters/http-status-errors.js +54 -17
- package/lib/utils/error-formatters/network-errors.js +103 -26
- package/lib/utils/external-system-display.js +184 -90
- package/lib/utils/external-system-validators.js +164 -42
- package/lib/utils/file-upload.js +109 -0
- package/lib/utils/health-check.js +199 -83
- package/lib/utils/infra-containers.js +1 -1
- package/lib/utils/infra-status.js +66 -15
- package/lib/utils/local-secrets.js +45 -25
- package/lib/utils/paths.js +45 -33
- package/lib/utils/schema-loader.js +42 -25
- package/lib/utils/schema-resolver.js +123 -74
- package/lib/utils/secrets-encryption.js +62 -25
- package/lib/utils/secrets-helpers.js +126 -63
- package/lib/utils/secrets-path.js +1 -1
- package/lib/utils/secrets-url.js +1 -1
- package/lib/utils/token-manager-refresh.js +181 -0
- package/lib/utils/token-manager.js +76 -123
- package/lib/utils/variable-transformer.js +154 -77
- package/lib/utils/yaml-preserve.js +41 -47
- package/lib/{template-validator.js → validation/template.js} +54 -23
- package/lib/{validate.js → validation/validate.js} +205 -125
- package/lib/{validator.js → validation/validator.js} +58 -39
- package/package.json +31 -2
- package/templates/external-system/deploy.ps1.hbs +34 -0
- package/templates/external-system/deploy.sh.hbs +34 -0
- package/templates/external-system/external-datasource.json.hbs +31 -12
- package/lib/app.js +0 -467
- package/lib/datasource-list.js +0 -141
- /package/lib/{app-prompts.js → app/prompts.js} +0 -0
- /package/lib/{env-reader.js → core/env-reader.js} +0 -0
- /package/lib/{key-generator.js → core/key-generator.js} +0 -0
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const Ajv = require('ajv');
|
|
14
|
-
const { detectAppType, getDeployJsonPath } = require('
|
|
15
|
-
const { loadVariables, loadRbac } = require('./
|
|
14
|
+
const { detectAppType, getDeployJsonPath } = require('../utils/paths');
|
|
15
|
+
const { loadVariables, loadRbac } = require('./helpers');
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Generates external system <app-name>-deploy.json by loading the system JSON file
|
|
@@ -24,56 +24,86 @@ const { loadVariables, loadRbac } = require('./generator-helpers');
|
|
|
24
24
|
* @returns {Promise<string>} Path to generated <app-name>-deploy.json file
|
|
25
25
|
* @throws {Error} If generation fails
|
|
26
26
|
*/
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// For external systems, the system JSON file should be in the same folder
|
|
40
|
-
// Check if it already exists (should be <app-name>-deploy.json)
|
|
41
|
-
const deployJsonPath = getDeployJsonPath(appName, 'external', true);
|
|
27
|
+
/**
|
|
28
|
+
* Resolves system file path from variables
|
|
29
|
+
* @function resolveSystemFilePath
|
|
30
|
+
* @param {Object} variables - Variables configuration
|
|
31
|
+
* @param {string} appPath - Application path
|
|
32
|
+
* @param {string} appName - Application name
|
|
33
|
+
* @returns {string} System file path
|
|
34
|
+
* @throws {Error} If file not found
|
|
35
|
+
*/
|
|
36
|
+
function resolveSystemFilePath(variables, appPath, appName) {
|
|
42
37
|
const systemFileName = variables.externalIntegration.systems && variables.externalIntegration.systems.length > 0
|
|
43
38
|
? variables.externalIntegration.systems[0]
|
|
44
39
|
: `${appName}-deploy.json`;
|
|
45
40
|
|
|
46
|
-
// Resolve system file path (schemaBasePath is usually './' for same folder)
|
|
47
41
|
const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
|
|
48
42
|
const systemFilePath = path.isAbsolute(schemaBasePath)
|
|
49
43
|
? path.join(schemaBasePath, systemFileName)
|
|
50
44
|
: path.join(appPath, schemaBasePath, systemFileName);
|
|
51
45
|
|
|
52
|
-
// If system file doesn't exist, throw error (it should be created manually or via external-system-generator)
|
|
53
46
|
if (!fs.existsSync(systemFilePath)) {
|
|
54
47
|
throw new Error(`External system file not found: ${systemFilePath}. Please create it first.`);
|
|
55
48
|
}
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
return systemFilePath;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Merges RBAC into system JSON
|
|
55
|
+
* @function mergeRbacIntoSystemJson
|
|
56
|
+
* @param {Object} systemJson - System JSON object
|
|
57
|
+
* @param {Object|null} rbac - RBAC configuration
|
|
58
|
+
*/
|
|
59
|
+
function mergeRbacIntoSystemJson(systemJson, rbac) {
|
|
60
|
+
if (!rbac) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Priority: roles/permissions in system JSON > rbac.yaml (if both exist, prefer JSON)
|
|
65
|
+
if (rbac.roles && (!systemJson.roles || systemJson.roles.length === 0)) {
|
|
66
|
+
systemJson.roles = rbac.roles;
|
|
67
|
+
}
|
|
68
|
+
if (rbac.permissions && (!systemJson.permissions || systemJson.permissions.length === 0)) {
|
|
69
|
+
systemJson.permissions = rbac.permissions;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Loads and parses system file
|
|
75
|
+
* @async
|
|
76
|
+
* @function loadSystemFileContent
|
|
77
|
+
* @param {string} systemFilePath - System file path
|
|
78
|
+
* @returns {Promise<Object>} Parsed system JSON
|
|
79
|
+
*/
|
|
80
|
+
async function loadSystemFileContent(systemFilePath) {
|
|
58
81
|
const systemContent = await fs.promises.readFile(systemFilePath, 'utf8');
|
|
59
|
-
|
|
82
|
+
return JSON.parse(systemContent);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function generateExternalSystemDeployJson(appName, appPath) {
|
|
86
|
+
if (!appName || typeof appName !== 'string') {
|
|
87
|
+
throw new Error('App name is required and must be a string');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
91
|
+
const { parsed: variables } = loadVariables(variablesPath);
|
|
92
|
+
|
|
93
|
+
if (!variables.externalIntegration) {
|
|
94
|
+
throw new Error('externalIntegration block not found in variables.yaml');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const systemFilePath = resolveSystemFilePath(variables, appPath, appName);
|
|
98
|
+
const systemJson = await loadSystemFileContent(systemFilePath);
|
|
60
99
|
|
|
61
100
|
// Load rbac.yaml from app directory (similar to regular apps)
|
|
62
101
|
const rbacPath = path.join(appPath, 'rbac.yaml');
|
|
63
102
|
const rbac = loadRbac(rbacPath);
|
|
64
|
-
|
|
65
|
-
// Merge rbac into systemJson if present
|
|
66
|
-
// Priority: roles/permissions in system JSON > rbac.yaml (if both exist, prefer JSON)
|
|
67
|
-
if (rbac) {
|
|
68
|
-
if (rbac.roles && (!systemJson.roles || systemJson.roles.length === 0)) {
|
|
69
|
-
systemJson.roles = rbac.roles;
|
|
70
|
-
}
|
|
71
|
-
if (rbac.permissions && (!systemJson.permissions || systemJson.permissions.length === 0)) {
|
|
72
|
-
systemJson.permissions = rbac.permissions;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
103
|
+
mergeRbacIntoSystemJson(systemJson, rbac);
|
|
75
104
|
|
|
76
105
|
// Write it as <app-name>-deploy.json (consistent naming)
|
|
106
|
+
const deployJsonPath = getDeployJsonPath(appName, 'external', true);
|
|
77
107
|
const jsonContent = JSON.stringify(systemJson, null, 2);
|
|
78
108
|
await fs.promises.writeFile(deployJsonPath, jsonContent, { mode: 0o644, encoding: 'utf8' });
|
|
79
109
|
|
|
@@ -242,22 +272,22 @@ function validateDatasourceSchemas(datasourceJsons, externalDatasourceSchema, aj
|
|
|
242
272
|
* @returns {Promise<Object>} Application schema object
|
|
243
273
|
* @throws {Error} If generation fails
|
|
244
274
|
*/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Loads and validates external integration configuration
|
|
277
|
+
* @async
|
|
278
|
+
* @function loadExternalIntegrationConfig
|
|
279
|
+
* @param {string} appPath - Application path
|
|
280
|
+
* @returns {Promise<Object>} Variables and schema base path
|
|
281
|
+
* @throws {Error} If configuration is invalid
|
|
282
|
+
*/
|
|
283
|
+
async function loadExternalIntegrationConfig(appPath) {
|
|
251
284
|
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
252
|
-
|
|
253
|
-
// Load variables.yaml
|
|
254
285
|
const { parsed: variables } = loadVariables(variablesPath);
|
|
255
286
|
|
|
256
287
|
if (!variables.externalIntegration) {
|
|
257
288
|
throw new Error('externalIntegration block not found in variables.yaml');
|
|
258
289
|
}
|
|
259
290
|
|
|
260
|
-
// Load system file and merge RBAC
|
|
261
291
|
const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
|
|
262
292
|
const systemFiles = variables.externalIntegration.systems || [];
|
|
263
293
|
|
|
@@ -265,18 +295,17 @@ async function generateExternalSystemApplicationSchema(appName) {
|
|
|
265
295
|
throw new Error('No system files specified in externalIntegration.systems');
|
|
266
296
|
}
|
|
267
297
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// Load datasource files
|
|
271
|
-
const datasourceFiles = variables.externalIntegration.dataSources || [];
|
|
272
|
-
const datasourceJsons = await loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles);
|
|
273
|
-
|
|
274
|
-
// Build application-schema.json structure
|
|
275
|
-
const applicationSchema = buildBaseSchema(systemJson, datasourceJsons, variables.externalIntegration.version);
|
|
298
|
+
return { variables, schemaBasePath, systemFiles };
|
|
299
|
+
}
|
|
276
300
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Prepares schemas for validation
|
|
303
|
+
* @function prepareSchemasForValidation
|
|
304
|
+
* @returns {Object} Prepared schemas
|
|
305
|
+
*/
|
|
306
|
+
function prepareSchemasForValidation() {
|
|
307
|
+
const externalSystemSchema = require('../schema/external-system.schema.json');
|
|
308
|
+
const externalDatasourceSchema = require('../schema/external-datasource.schema.json');
|
|
280
309
|
|
|
281
310
|
// For draft-2020-12 schemas, remove $schema to avoid AJV issues
|
|
282
311
|
const datasourceSchemaToAdd = { ...externalDatasourceSchema };
|
|
@@ -284,6 +313,18 @@ async function generateExternalSystemApplicationSchema(appName) {
|
|
|
284
313
|
delete datasourceSchemaToAdd.$schema;
|
|
285
314
|
}
|
|
286
315
|
|
|
316
|
+
return { externalSystemSchema, datasourceSchemaToAdd };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Validates application schema components
|
|
321
|
+
* @function validateApplicationSchemaComponents
|
|
322
|
+
* @param {Object} systemJson - System JSON
|
|
323
|
+
* @param {Object[]} datasourceJsons - Array of datasource JSONs
|
|
324
|
+
* @param {Object} externalSystemSchema - External system schema
|
|
325
|
+
* @param {Object} datasourceSchemaToAdd - Datasource schema
|
|
326
|
+
*/
|
|
327
|
+
function validateApplicationSchemaComponents(systemJson, datasourceJsons, externalSystemSchema, datasourceSchemaToAdd) {
|
|
287
328
|
const ajv = new Ajv({ allErrors: true, strict: false, removeAdditional: false });
|
|
288
329
|
|
|
289
330
|
// Validate system against external-system schema
|
|
@@ -291,6 +332,28 @@ async function generateExternalSystemApplicationSchema(appName) {
|
|
|
291
332
|
|
|
292
333
|
// Validate datasources against external-datasource schema
|
|
293
334
|
validateDatasourceSchemas(datasourceJsons, datasourceSchemaToAdd, ajv);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function generateExternalSystemApplicationSchema(appName) {
|
|
338
|
+
if (!appName || typeof appName !== 'string') {
|
|
339
|
+
throw new Error('App name is required and must be a string');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const { appPath } = await detectAppType(appName);
|
|
343
|
+
const { variables, schemaBasePath, systemFiles } = await loadExternalIntegrationConfig(appPath);
|
|
344
|
+
|
|
345
|
+
const systemJson = await loadSystemFile(appPath, schemaBasePath, systemFiles[0]);
|
|
346
|
+
|
|
347
|
+
// Load datasource files
|
|
348
|
+
const datasourceFiles = variables.externalIntegration.dataSources || [];
|
|
349
|
+
const datasourceJsons = await loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles);
|
|
350
|
+
|
|
351
|
+
// Build application-schema.json structure
|
|
352
|
+
const applicationSchema = buildBaseSchema(systemJson, datasourceJsons, variables.externalIntegration.version);
|
|
353
|
+
|
|
354
|
+
// Validate individual components against their schemas
|
|
355
|
+
const { externalSystemSchema, datasourceSchemaToAdd } = prepareSchemasForValidation();
|
|
356
|
+
validateApplicationSchemaComponents(systemJson, datasourceJsons, externalSystemSchema, datasourceSchemaToAdd);
|
|
294
357
|
|
|
295
358
|
return applicationSchema;
|
|
296
359
|
}
|
|
@@ -21,7 +21,7 @@ Handlebars.registerHelper('contains', (array, value) => {
|
|
|
21
21
|
* @returns {Promise<Object>} Object mapping step names to their compiled content
|
|
22
22
|
*/
|
|
23
23
|
async function loadStepTemplates(stepNames = []) {
|
|
24
|
-
const stepsDir = path.join(__dirname, '..', 'templates', 'github', 'steps');
|
|
24
|
+
const stepsDir = path.join(__dirname, '..', '..', 'templates', 'github', 'steps');
|
|
25
25
|
const stepTemplates = {};
|
|
26
26
|
|
|
27
27
|
for (const stepName of stepNames) {
|
|
@@ -64,7 +64,7 @@ async function generateGithubWorkflows(appPath, config, options = {}) {
|
|
|
64
64
|
const templateContext = await getTemplateContext(config, options, stepTemplates);
|
|
65
65
|
|
|
66
66
|
// Load and compile templates
|
|
67
|
-
const templatesDir = path.join(__dirname, '..', 'templates', 'github');
|
|
67
|
+
const templatesDir = path.join(__dirname, '..', '..', 'templates', 'github');
|
|
68
68
|
const templateFiles = await fs.readdir(templatesDir);
|
|
69
69
|
|
|
70
70
|
const generatedFiles = [];
|
|
@@ -103,17 +103,48 @@ async function generateGithubWorkflows(appPath, config, options = {}) {
|
|
|
103
103
|
* @param {Object} stepTemplates - Compiled step templates
|
|
104
104
|
* @returns {Promise<Object>} Template context
|
|
105
105
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Builds base context for template rendering
|
|
108
|
+
* @function buildBaseContext
|
|
109
|
+
* @param {Object} config - Application configuration
|
|
110
|
+
* @param {Object} options - Additional options
|
|
111
|
+
* @returns {Object} Base context object
|
|
112
|
+
*/
|
|
113
|
+
/**
|
|
114
|
+
* Determines file extension based on language
|
|
115
|
+
* @function getFileExtension
|
|
116
|
+
* @param {string} language - Programming language
|
|
117
|
+
* @returns {string} File extension
|
|
118
|
+
*/
|
|
119
|
+
function getFileExtension(language) {
|
|
120
|
+
return language === 'python' ? 'py' : 'js';
|
|
121
|
+
}
|
|
108
122
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Determines source directory based on language
|
|
125
|
+
* @function getSourceDir
|
|
126
|
+
* @param {string} language - Programming language
|
|
127
|
+
* @returns {string} Source directory
|
|
128
|
+
*/
|
|
129
|
+
function getSourceDir(language) {
|
|
130
|
+
return language === 'python' ? 'src' : 'lib';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Builds base configuration values
|
|
135
|
+
* @function buildBaseConfigValues
|
|
136
|
+
* @param {Object} config - Configuration object
|
|
137
|
+
* @param {Object} options - Options object
|
|
138
|
+
* @returns {Object} Base configuration values
|
|
139
|
+
*/
|
|
140
|
+
function buildBaseConfigValues(config, options) {
|
|
141
|
+
const language = config.language || 'typescript';
|
|
142
|
+
return {
|
|
112
143
|
appName: config.appName || 'myapp',
|
|
113
144
|
mainBranch: options.mainBranch || 'main',
|
|
114
|
-
language:
|
|
115
|
-
fileExtension:
|
|
116
|
-
sourceDir:
|
|
145
|
+
language: language,
|
|
146
|
+
fileExtension: getFileExtension(language),
|
|
147
|
+
sourceDir: getSourceDir(language),
|
|
117
148
|
buildCommand: options.buildCommand || 'npm run build',
|
|
118
149
|
port: config.port || 3000,
|
|
119
150
|
database: config.database || false,
|
|
@@ -121,11 +152,37 @@ async function getTemplateContext(config, options = {}, stepTemplates = {}) {
|
|
|
121
152
|
storage: config.storage || false,
|
|
122
153
|
authentication: config.authentication || false
|
|
123
154
|
};
|
|
155
|
+
}
|
|
124
156
|
|
|
157
|
+
function buildBaseContext(config, options) {
|
|
158
|
+
return buildBaseConfigValues(config, options);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Renders step templates
|
|
163
|
+
* @function renderStepTemplates
|
|
164
|
+
* @param {Object} stepTemplates - Step templates object
|
|
165
|
+
* @param {Object} baseContext - Base context for rendering
|
|
166
|
+
* @returns {Object} Rendered steps
|
|
167
|
+
*/
|
|
168
|
+
function renderStepTemplates(stepTemplates, baseContext) {
|
|
169
|
+
const renderedSteps = {};
|
|
125
170
|
for (const [stepName, template] of Object.entries(stepTemplates)) {
|
|
126
171
|
renderedSteps[stepName] = template(baseContext);
|
|
127
172
|
}
|
|
173
|
+
return renderedSteps;
|
|
174
|
+
}
|
|
128
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Builds final template context
|
|
178
|
+
* @function buildFinalTemplateContext
|
|
179
|
+
* @param {Object} baseContext - Base context
|
|
180
|
+
* @param {Object} options - Additional options
|
|
181
|
+
* @param {Object} renderedSteps - Rendered step templates
|
|
182
|
+
* @returns {Object} Final template context
|
|
183
|
+
*/
|
|
184
|
+
function buildFinalTemplateContext(baseContext, options, renderedSteps) {
|
|
185
|
+
const githubSteps = options.githubSteps || [];
|
|
129
186
|
return {
|
|
130
187
|
...baseContext,
|
|
131
188
|
uploadCoverage: options.uploadCoverage !== false,
|
|
@@ -136,6 +193,12 @@ async function getTemplateContext(config, options = {}, stepTemplates = {}) {
|
|
|
136
193
|
};
|
|
137
194
|
}
|
|
138
195
|
|
|
196
|
+
async function getTemplateContext(config, options = {}, stepTemplates = {}) {
|
|
197
|
+
const baseContext = buildBaseContext(config, options);
|
|
198
|
+
const renderedSteps = renderStepTemplates(stepTemplates, baseContext);
|
|
199
|
+
return buildFinalTemplateContext(baseContext, options, renderedSteps);
|
|
200
|
+
}
|
|
201
|
+
|
|
139
202
|
/**
|
|
140
203
|
* Generate a specific workflow file
|
|
141
204
|
* @param {string} appPath - Path to application directory
|
|
@@ -151,7 +214,7 @@ async function generateWorkflowFile(appPath, templateName, config, options = {})
|
|
|
151
214
|
await fs.mkdir(workflowsDir, { recursive: true });
|
|
152
215
|
|
|
153
216
|
// Load template
|
|
154
|
-
const templatePath = path.join(__dirname, '..', 'templates', 'github', `${templateName}.hbs`);
|
|
217
|
+
const templatePath = path.join(__dirname, '..', '..', 'templates', 'github', `${templateName}.hbs`);
|
|
155
218
|
const templateContent = await fs.readFile(templatePath, 'utf8');
|
|
156
219
|
|
|
157
220
|
// Compile template
|
|
@@ -185,11 +248,15 @@ async function generateWorkflowFile(appPath, templateName, config, options = {})
|
|
|
185
248
|
* @param {Object} options - Generation options
|
|
186
249
|
* @returns {Object} Validation result
|
|
187
250
|
*/
|
|
188
|
-
|
|
251
|
+
/**
|
|
252
|
+
* Validates required fields
|
|
253
|
+
* @function validateRequiredFields
|
|
254
|
+
* @param {Object} config - Application configuration
|
|
255
|
+
* @returns {string[]} Array of error messages
|
|
256
|
+
*/
|
|
257
|
+
function validateRequiredFields(config) {
|
|
189
258
|
const errors = [];
|
|
190
|
-
const warnings = [];
|
|
191
259
|
|
|
192
|
-
// Validate required fields
|
|
193
260
|
if (!config.appName) {
|
|
194
261
|
errors.push('Application name is required');
|
|
195
262
|
}
|
|
@@ -202,6 +269,19 @@ function validateWorkflowConfig(config, options = {}) {
|
|
|
202
269
|
errors.push('Language must be either typescript or python');
|
|
203
270
|
}
|
|
204
271
|
|
|
272
|
+
return errors;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Validates optional fields
|
|
277
|
+
* @function validateOptionalFields
|
|
278
|
+
* @param {Object} config - Application configuration
|
|
279
|
+
* @param {Object} options - Generation options
|
|
280
|
+
* @returns {string[]} Array of error messages
|
|
281
|
+
*/
|
|
282
|
+
function validateOptionalFields(config, options) {
|
|
283
|
+
const errors = [];
|
|
284
|
+
|
|
205
285
|
// Validate port
|
|
206
286
|
if (config.port && (config.port < 1 || config.port > 65535)) {
|
|
207
287
|
errors.push('Port must be between 1 and 65535');
|
|
@@ -212,7 +292,18 @@ function validateWorkflowConfig(config, options = {}) {
|
|
|
212
292
|
errors.push('Main branch name contains invalid characters');
|
|
213
293
|
}
|
|
214
294
|
|
|
215
|
-
|
|
295
|
+
return errors;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Collects validation warnings
|
|
300
|
+
* @function collectValidationWarnings
|
|
301
|
+
* @param {Object} config - Application configuration
|
|
302
|
+
* @returns {string[]} Array of warning messages
|
|
303
|
+
*/
|
|
304
|
+
function collectValidationWarnings(config) {
|
|
305
|
+
const warnings = [];
|
|
306
|
+
|
|
216
307
|
if (config.language === 'python' && !config.database) {
|
|
217
308
|
warnings.push('Python applications typically require a database');
|
|
218
309
|
}
|
|
@@ -221,6 +312,16 @@ function validateWorkflowConfig(config, options = {}) {
|
|
|
221
312
|
warnings.push('Authentication typically requires a database for user storage');
|
|
222
313
|
}
|
|
223
314
|
|
|
315
|
+
return warnings;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function validateWorkflowConfig(config, options = {}) {
|
|
319
|
+
const errors = [
|
|
320
|
+
...validateRequiredFields(config),
|
|
321
|
+
...validateOptionalFields(config, options)
|
|
322
|
+
];
|
|
323
|
+
const warnings = collectValidationWarnings(config);
|
|
324
|
+
|
|
224
325
|
return {
|
|
225
326
|
valid: errors.length === 0,
|
|
226
327
|
errors,
|
|
@@ -157,11 +157,13 @@ function validatePortalInput(portalInput, variableName) {
|
|
|
157
157
|
* @returns {Array<Object>} Configuration array with merged portalInput
|
|
158
158
|
* @throws {Error} If portalInput structure is invalid
|
|
159
159
|
*/
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Creates a map of portalInput configurations from variables config
|
|
162
|
+
* @function createPortalInputMap
|
|
163
|
+
* @param {Object|null} variablesConfig - Configuration from variables.yaml
|
|
164
|
+
* @returns {Map} Map of variable names to portalInput configurations
|
|
165
|
+
*/
|
|
166
|
+
function createPortalInputMap(variablesConfig) {
|
|
165
167
|
const portalInputMap = new Map();
|
|
166
168
|
if (variablesConfig && variablesConfig.configuration && Array.isArray(variablesConfig.configuration)) {
|
|
167
169
|
for (const configItem of variablesConfig.configuration) {
|
|
@@ -172,55 +174,103 @@ function parseEnvironmentVariables(envTemplate, variablesConfig = null) {
|
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
}
|
|
177
|
+
return portalInputMap;
|
|
178
|
+
}
|
|
175
179
|
|
|
176
|
-
|
|
177
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Parses a single environment variable line
|
|
182
|
+
* @function parseEnvironmentVariableLine
|
|
183
|
+
* @param {string} line - Line to parse
|
|
184
|
+
* @returns {Object|null} Parsed variable object or null if invalid
|
|
185
|
+
*/
|
|
186
|
+
function parseEnvironmentVariableLine(line) {
|
|
187
|
+
const trimmed = line.trim();
|
|
178
188
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
// Skip empty lines and comments
|
|
190
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
183
193
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
194
|
+
// Parse KEY=VALUE format
|
|
195
|
+
const equalIndex = trimmed.indexOf('=');
|
|
196
|
+
if (equalIndex === -1) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
189
199
|
|
|
190
|
-
|
|
191
|
-
|
|
200
|
+
const key = trimmed.substring(0, equalIndex).trim();
|
|
201
|
+
const value = trimmed.substring(equalIndex + 1).trim();
|
|
192
202
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
203
|
+
if (!key || !value) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
196
206
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
let required = false;
|
|
207
|
+
return { key, value };
|
|
208
|
+
}
|
|
200
209
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Determines location and required status for a variable
|
|
212
|
+
* @function determineVariableLocation
|
|
213
|
+
* @param {string} value - Variable value
|
|
214
|
+
* @param {string} key - Variable key
|
|
215
|
+
* @returns {Object} Object with location and required properties
|
|
216
|
+
*/
|
|
217
|
+
function determineVariableLocation(value, key) {
|
|
218
|
+
let location = 'variable';
|
|
219
|
+
let required = false;
|
|
205
220
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
221
|
+
if (value.startsWith('kv://')) {
|
|
222
|
+
location = 'keyvault';
|
|
223
|
+
required = true;
|
|
224
|
+
}
|
|
211
225
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
226
|
+
// Check if it's a sensitive variable
|
|
227
|
+
const sensitiveKeys = ['password', 'secret', 'key', 'token', 'auth'];
|
|
228
|
+
if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive))) {
|
|
229
|
+
required = true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { location, required };
|
|
233
|
+
}
|
|
218
234
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Creates a configuration item from parsed variable
|
|
237
|
+
* @function createConfigItem
|
|
238
|
+
* @param {string} key - Variable key
|
|
239
|
+
* @param {string} value - Variable value
|
|
240
|
+
* @param {string} location - Variable location
|
|
241
|
+
* @param {boolean} required - Whether variable is required
|
|
242
|
+
* @param {Map} portalInputMap - Map of portalInput configurations
|
|
243
|
+
* @returns {Object} Configuration item
|
|
244
|
+
*/
|
|
245
|
+
function createConfigItem(key, value, location, required, portalInputMap) {
|
|
246
|
+
const configItem = {
|
|
247
|
+
name: key,
|
|
248
|
+
value: value.replace('kv://', ''), // Remove kv:// prefix for KeyVault
|
|
249
|
+
location,
|
|
250
|
+
required
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// Merge portalInput if it exists in variables.yaml
|
|
254
|
+
if (portalInputMap.has(key)) {
|
|
255
|
+
configItem.portalInput = portalInputMap.get(key);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return configItem;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function parseEnvironmentVariables(envTemplate, variablesConfig = null) {
|
|
262
|
+
const configuration = [];
|
|
263
|
+
const lines = envTemplate.split('\n');
|
|
264
|
+
const portalInputMap = createPortalInputMap(variablesConfig);
|
|
265
|
+
|
|
266
|
+
for (const line of lines) {
|
|
267
|
+
const parsed = parseEnvironmentVariableLine(line);
|
|
268
|
+
if (!parsed) {
|
|
269
|
+
continue;
|
|
222
270
|
}
|
|
223
271
|
|
|
272
|
+
const { location, required } = determineVariableLocation(parsed.value, parsed.key);
|
|
273
|
+
const configItem = createConfigItem(parsed.key, parsed.value, location, required, portalInputMap);
|
|
224
274
|
configuration.push(configItem);
|
|
225
275
|
}
|
|
226
276
|
|