@aifabrix/builder 2.21.0 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/app-list.js +60 -22
- package/lib/app-register.js +3 -2
- package/lib/app-rotate-secret.js +86 -48
- package/lib/cli.js +28 -28
- package/lib/commands/app.js +3 -0
- package/lib/config.js +40 -173
- package/lib/external-system-generator.js +3 -1
- package/lib/generator-external.js +229 -0
- package/lib/generator-helpers.js +205 -0
- package/lib/generator.js +2 -367
- package/lib/schema/external-system.schema.json +92 -1
- package/lib/utils/api-error-handler.js +9 -2
- package/lib/utils/app-register-api.js +39 -29
- package/lib/utils/app-register-auth.js +103 -39
- package/lib/utils/config-paths.js +112 -0
- package/lib/utils/config-tokens.js +233 -0
- package/lib/utils/device-code.js +28 -6
- package/lib/utils/error-formatters/http-status-errors.js +78 -5
- package/lib/utils/error-formatters/network-errors.js +24 -4
- package/lib/validate.js +67 -7
- package/lib/validator.js +3 -1
- package/package.json +1 -1
- package/templates/external-system/external-system.json.hbs +20 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External System Generator Functions
|
|
3
|
+
*
|
|
4
|
+
* Functions for generating deployment JSON for external system applications.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview External system generator functions for AI Fabrix Builder
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const Ajv = require('ajv');
|
|
14
|
+
const { detectAppType, getDeployJsonPath } = require('./utils/paths');
|
|
15
|
+
const { loadVariables, loadRbac } = require('./generator-helpers');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generates external system <app-name>-deploy.json by loading the system JSON file
|
|
19
|
+
* For external systems, the system JSON file is already created and we just need to reference it
|
|
20
|
+
* @async
|
|
21
|
+
* @function generateExternalSystemDeployJson
|
|
22
|
+
* @param {string} appName - Name of the application
|
|
23
|
+
* @param {string} appPath - Path to application directory (integration or builder)
|
|
24
|
+
* @returns {Promise<string>} Path to generated <app-name>-deploy.json file
|
|
25
|
+
* @throws {Error} If generation fails
|
|
26
|
+
*/
|
|
27
|
+
async function generateExternalSystemDeployJson(appName, appPath) {
|
|
28
|
+
if (!appName || typeof appName !== 'string') {
|
|
29
|
+
throw new Error('App name is required and must be a string');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
33
|
+
const { parsed: variables } = loadVariables(variablesPath);
|
|
34
|
+
|
|
35
|
+
if (!variables.externalIntegration) {
|
|
36
|
+
throw new Error('externalIntegration block not found in variables.yaml');
|
|
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);
|
|
42
|
+
const systemFileName = variables.externalIntegration.systems && variables.externalIntegration.systems.length > 0
|
|
43
|
+
? variables.externalIntegration.systems[0]
|
|
44
|
+
: `${appName}-deploy.json`;
|
|
45
|
+
|
|
46
|
+
// Resolve system file path (schemaBasePath is usually './' for same folder)
|
|
47
|
+
const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
|
|
48
|
+
const systemFilePath = path.isAbsolute(schemaBasePath)
|
|
49
|
+
? path.join(schemaBasePath, systemFileName)
|
|
50
|
+
: path.join(appPath, schemaBasePath, systemFileName);
|
|
51
|
+
|
|
52
|
+
// If system file doesn't exist, throw error (it should be created manually or via external-system-generator)
|
|
53
|
+
if (!fs.existsSync(systemFilePath)) {
|
|
54
|
+
throw new Error(`External system file not found: ${systemFilePath}. Please create it first.`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Read the system JSON file
|
|
58
|
+
const systemContent = await fs.promises.readFile(systemFilePath, 'utf8');
|
|
59
|
+
const systemJson = JSON.parse(systemContent);
|
|
60
|
+
|
|
61
|
+
// Load rbac.yaml from app directory (similar to regular apps)
|
|
62
|
+
const rbacPath = path.join(appPath, 'rbac.yaml');
|
|
63
|
+
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
|
+
}
|
|
75
|
+
|
|
76
|
+
// Write it as <app-name>-deploy.json (consistent naming)
|
|
77
|
+
const jsonContent = JSON.stringify(systemJson, null, 2);
|
|
78
|
+
await fs.promises.writeFile(deployJsonPath, jsonContent, { mode: 0o644, encoding: 'utf8' });
|
|
79
|
+
|
|
80
|
+
return deployJsonPath;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generates application-schema.json structure for external systems
|
|
85
|
+
* Combines system and datasource JSONs into application-level deployment format
|
|
86
|
+
* @async
|
|
87
|
+
* @function generateExternalSystemApplicationSchema
|
|
88
|
+
* @param {string} appName - Application name
|
|
89
|
+
* @returns {Promise<Object>} Application schema object
|
|
90
|
+
* @throws {Error} If generation fails
|
|
91
|
+
*/
|
|
92
|
+
async function generateExternalSystemApplicationSchema(appName) {
|
|
93
|
+
if (!appName || typeof appName !== 'string') {
|
|
94
|
+
throw new Error('App name is required and must be a string');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { appPath } = await detectAppType(appName);
|
|
98
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
99
|
+
|
|
100
|
+
// Load variables.yaml
|
|
101
|
+
const { parsed: variables } = loadVariables(variablesPath);
|
|
102
|
+
|
|
103
|
+
if (!variables.externalIntegration) {
|
|
104
|
+
throw new Error('externalIntegration block not found in variables.yaml');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Load system file
|
|
108
|
+
const schemaBasePath = variables.externalIntegration.schemaBasePath || './';
|
|
109
|
+
const systemFiles = variables.externalIntegration.systems || [];
|
|
110
|
+
|
|
111
|
+
if (systemFiles.length === 0) {
|
|
112
|
+
throw new Error('No system files specified in externalIntegration.systems');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const systemFileName = systemFiles[0];
|
|
116
|
+
const systemFilePath = path.isAbsolute(schemaBasePath)
|
|
117
|
+
? path.join(schemaBasePath, systemFileName)
|
|
118
|
+
: path.join(appPath, schemaBasePath, systemFileName);
|
|
119
|
+
|
|
120
|
+
if (!fs.existsSync(systemFilePath)) {
|
|
121
|
+
throw new Error(`System file not found: ${systemFilePath}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const systemContent = await fs.promises.readFile(systemFilePath, 'utf8');
|
|
125
|
+
const systemJson = JSON.parse(systemContent);
|
|
126
|
+
|
|
127
|
+
// Load rbac.yaml from app directory and merge if present
|
|
128
|
+
// Priority: roles/permissions in system JSON > rbac.yaml (if both exist, prefer JSON)
|
|
129
|
+
const rbacPath = path.join(appPath, 'rbac.yaml');
|
|
130
|
+
const rbac = loadRbac(rbacPath);
|
|
131
|
+
if (rbac) {
|
|
132
|
+
if (rbac.roles && (!systemJson.roles || systemJson.roles.length === 0)) {
|
|
133
|
+
systemJson.roles = rbac.roles;
|
|
134
|
+
}
|
|
135
|
+
if (rbac.permissions && (!systemJson.permissions || systemJson.permissions.length === 0)) {
|
|
136
|
+
systemJson.permissions = rbac.permissions;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Load datasource files
|
|
141
|
+
const datasourceFiles = variables.externalIntegration.dataSources || [];
|
|
142
|
+
const datasourceJsons = [];
|
|
143
|
+
|
|
144
|
+
for (const datasourceFile of datasourceFiles) {
|
|
145
|
+
const datasourcePath = path.isAbsolute(schemaBasePath)
|
|
146
|
+
? path.join(schemaBasePath, datasourceFile)
|
|
147
|
+
: path.join(appPath, schemaBasePath, datasourceFile);
|
|
148
|
+
|
|
149
|
+
if (!fs.existsSync(datasourcePath)) {
|
|
150
|
+
throw new Error(`Datasource file not found: ${datasourcePath}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const datasourceContent = await fs.promises.readFile(datasourcePath, 'utf8');
|
|
154
|
+
const datasourceJson = JSON.parse(datasourceContent);
|
|
155
|
+
datasourceJsons.push(datasourceJson);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Build application-schema.json structure
|
|
159
|
+
const applicationSchema = {
|
|
160
|
+
version: variables.externalIntegration.version || '1.0.0',
|
|
161
|
+
application: systemJson,
|
|
162
|
+
dataSources: datasourceJsons
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Validate individual components against their schemas
|
|
166
|
+
const externalSystemSchema = require('./schema/external-system.schema.json');
|
|
167
|
+
const externalDatasourceSchema = require('./schema/external-datasource.schema.json');
|
|
168
|
+
|
|
169
|
+
// For draft-2020-12 schemas, remove $schema to avoid AJV issues (similar to schema-loader.js)
|
|
170
|
+
const datasourceSchemaToAdd = { ...externalDatasourceSchema };
|
|
171
|
+
if (datasourceSchemaToAdd.$schema && datasourceSchemaToAdd.$schema.includes('2020-12')) {
|
|
172
|
+
delete datasourceSchemaToAdd.$schema;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const ajv = new Ajv({ allErrors: true, strict: false, removeAdditional: false });
|
|
176
|
+
|
|
177
|
+
// Validate application (system) against external-system schema
|
|
178
|
+
const externalSystemSchemaId = externalSystemSchema.$id || 'https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-system.schema.json';
|
|
179
|
+
ajv.addSchema(externalSystemSchema, externalSystemSchemaId);
|
|
180
|
+
const validateSystem = ajv.compile(externalSystemSchema);
|
|
181
|
+
const systemValid = validateSystem(systemJson);
|
|
182
|
+
|
|
183
|
+
if (!systemValid) {
|
|
184
|
+
// Filter out additionalProperties errors for required properties that aren't defined in schema
|
|
185
|
+
// This handles schema inconsistencies where authentication is required but not defined in properties
|
|
186
|
+
const filteredErrors = validateSystem.errors.filter(err => {
|
|
187
|
+
if (err.keyword === 'additionalProperties' && err.params?.additionalProperty === 'authentication') {
|
|
188
|
+
// Check if authentication is in required array
|
|
189
|
+
const required = externalSystemSchema.required || [];
|
|
190
|
+
if (required.includes('authentication')) {
|
|
191
|
+
return false; // Ignore this error since authentication is required but not defined
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (filteredErrors.length > 0) {
|
|
198
|
+
const errors = filteredErrors.map(err => {
|
|
199
|
+
const path = err.instancePath || err.schemaPath;
|
|
200
|
+
return `${path} ${err.message}`;
|
|
201
|
+
}).join(', ');
|
|
202
|
+
throw new Error(`System JSON does not match external-system schema: ${errors}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Validate each datasource against external-datasource schema
|
|
207
|
+
const externalDatasourceSchemaId = datasourceSchemaToAdd.$id || 'https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-datasource.schema.json';
|
|
208
|
+
ajv.addSchema(datasourceSchemaToAdd, externalDatasourceSchemaId);
|
|
209
|
+
const validateDatasource = ajv.compile(datasourceSchemaToAdd);
|
|
210
|
+
|
|
211
|
+
for (let i = 0; i < datasourceJsons.length; i++) {
|
|
212
|
+
const datasourceValid = validateDatasource(datasourceJsons[i]);
|
|
213
|
+
if (!datasourceValid) {
|
|
214
|
+
const errors = validateDatasource.errors.map(err => {
|
|
215
|
+
const path = err.instancePath || err.schemaPath;
|
|
216
|
+
return `${path} ${err.message}`;
|
|
217
|
+
}).join(', ');
|
|
218
|
+
throw new Error(`Datasource ${i + 1} (${datasourceJsons[i].key || 'unknown'}) does not match external-datasource schema: ${errors}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return applicationSchema;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
generateExternalSystemDeployJson,
|
|
227
|
+
generateExternalSystemApplicationSchema
|
|
228
|
+
};
|
|
229
|
+
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generator Helper Functions
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for loading and parsing configuration files used in deployment JSON generation.
|
|
5
|
+
*
|
|
6
|
+
* @fileoverview Generator helper functions for AI Fabrix Builder
|
|
7
|
+
* @author AI Fabrix Team
|
|
8
|
+
* @version 2.0.0
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const yaml = require('js-yaml');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Loads variables.yaml file
|
|
16
|
+
* @param {string} variablesPath - Path to variables.yaml
|
|
17
|
+
* @returns {Object} Parsed variables
|
|
18
|
+
* @throws {Error} If file not found or invalid YAML
|
|
19
|
+
*/
|
|
20
|
+
function loadVariables(variablesPath) {
|
|
21
|
+
if (!fs.existsSync(variablesPath)) {
|
|
22
|
+
throw new Error(`variables.yaml not found: ${variablesPath}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const variablesContent = fs.readFileSync(variablesPath, 'utf8');
|
|
26
|
+
try {
|
|
27
|
+
return { content: variablesContent, parsed: yaml.load(variablesContent) };
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Loads env.template file
|
|
35
|
+
* @param {string} templatePath - Path to env.template
|
|
36
|
+
* @returns {string} Template content
|
|
37
|
+
* @throws {Error} If file not found
|
|
38
|
+
*/
|
|
39
|
+
function loadEnvTemplate(templatePath) {
|
|
40
|
+
if (!fs.existsSync(templatePath)) {
|
|
41
|
+
throw new Error(`env.template not found: ${templatePath}`);
|
|
42
|
+
}
|
|
43
|
+
return fs.readFileSync(templatePath, 'utf8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Loads rbac.yaml file if it exists
|
|
48
|
+
* @param {string} rbacPath - Path to rbac.yaml
|
|
49
|
+
* @returns {Object|null} Parsed RBAC configuration or null
|
|
50
|
+
* @throws {Error} If file exists but has invalid YAML
|
|
51
|
+
*/
|
|
52
|
+
function loadRbac(rbacPath) {
|
|
53
|
+
if (!fs.existsSync(rbacPath)) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const rbacContent = fs.readFileSync(rbacPath, 'utf8');
|
|
58
|
+
try {
|
|
59
|
+
return yaml.load(rbacContent);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new Error(`Invalid YAML syntax in rbac.yaml: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Validates portalInput structure against schema requirements
|
|
67
|
+
* @param {Object} portalInput - Portal input configuration to validate
|
|
68
|
+
* @param {string} variableName - Variable name for error messages
|
|
69
|
+
* @throws {Error} If portalInput structure is invalid
|
|
70
|
+
*/
|
|
71
|
+
function validatePortalInput(portalInput, variableName) {
|
|
72
|
+
if (!portalInput || typeof portalInput !== 'object') {
|
|
73
|
+
throw new Error(`Invalid portalInput for variable '${variableName}': must be an object`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check required fields
|
|
77
|
+
if (!portalInput.field || typeof portalInput.field !== 'string') {
|
|
78
|
+
throw new Error(`Invalid portalInput for variable '${variableName}': field is required and must be a string`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!portalInput.label || typeof portalInput.label !== 'string') {
|
|
82
|
+
throw new Error(`Invalid portalInput for variable '${variableName}': label is required and must be a string`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Validate field type
|
|
86
|
+
const validFieldTypes = ['password', 'text', 'textarea', 'select'];
|
|
87
|
+
if (!validFieldTypes.includes(portalInput.field)) {
|
|
88
|
+
throw new Error(`Invalid portalInput for variable '${variableName}': field must be one of: ${validFieldTypes.join(', ')}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Validate select field requires options
|
|
92
|
+
if (portalInput.field === 'select') {
|
|
93
|
+
if (!portalInput.options || !Array.isArray(portalInput.options) || portalInput.options.length === 0) {
|
|
94
|
+
throw new Error(`Invalid portalInput for variable '${variableName}': select field requires a non-empty options array`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Validate optional fields
|
|
99
|
+
if (portalInput.placeholder !== undefined && typeof portalInput.placeholder !== 'string') {
|
|
100
|
+
throw new Error(`Invalid portalInput for variable '${variableName}': placeholder must be a string`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (portalInput.masked !== undefined && typeof portalInput.masked !== 'boolean') {
|
|
104
|
+
throw new Error(`Invalid portalInput for variable '${variableName}': masked must be a boolean`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (portalInput.validation !== undefined) {
|
|
108
|
+
if (typeof portalInput.validation !== 'object' || Array.isArray(portalInput.validation)) {
|
|
109
|
+
throw new Error(`Invalid portalInput for variable '${variableName}': validation must be an object`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (portalInput.options !== undefined && portalInput.field !== 'select') {
|
|
114
|
+
// Options should only be present for select fields
|
|
115
|
+
if (Array.isArray(portalInput.options) && portalInput.options.length > 0) {
|
|
116
|
+
throw new Error(`Invalid portalInput for variable '${variableName}': options can only be used with select field type`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Parses environment variables from env.template and merges portalInput from variables.yaml
|
|
123
|
+
* @param {string} envTemplate - Content of env.template file
|
|
124
|
+
* @param {Object|null} [variablesConfig=null] - Optional configuration from variables.yaml
|
|
125
|
+
* @returns {Array<Object>} Configuration array with merged portalInput
|
|
126
|
+
* @throws {Error} If portalInput structure is invalid
|
|
127
|
+
*/
|
|
128
|
+
function parseEnvironmentVariables(envTemplate, variablesConfig = null) {
|
|
129
|
+
const configuration = [];
|
|
130
|
+
const lines = envTemplate.split('\n');
|
|
131
|
+
|
|
132
|
+
// Create a map of portalInput configurations by variable name
|
|
133
|
+
const portalInputMap = new Map();
|
|
134
|
+
if (variablesConfig && variablesConfig.configuration && Array.isArray(variablesConfig.configuration)) {
|
|
135
|
+
for (const configItem of variablesConfig.configuration) {
|
|
136
|
+
if (configItem.name && configItem.portalInput) {
|
|
137
|
+
// Validate portalInput before adding to map
|
|
138
|
+
validatePortalInput(configItem.portalInput, configItem.name);
|
|
139
|
+
portalInputMap.set(configItem.name, configItem.portalInput);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const line of lines) {
|
|
145
|
+
const trimmed = line.trim();
|
|
146
|
+
|
|
147
|
+
// Skip empty lines and comments
|
|
148
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Parse KEY=VALUE format
|
|
153
|
+
const equalIndex = trimmed.indexOf('=');
|
|
154
|
+
if (equalIndex === -1) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const key = trimmed.substring(0, equalIndex).trim();
|
|
159
|
+
const value = trimmed.substring(equalIndex + 1).trim();
|
|
160
|
+
|
|
161
|
+
if (!key || !value) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Determine location and required status
|
|
166
|
+
let location = 'variable';
|
|
167
|
+
let required = false;
|
|
168
|
+
|
|
169
|
+
if (value.startsWith('kv://')) {
|
|
170
|
+
location = 'keyvault';
|
|
171
|
+
required = true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check if it's a sensitive variable
|
|
175
|
+
const sensitiveKeys = ['password', 'secret', 'key', 'token', 'auth'];
|
|
176
|
+
if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive))) {
|
|
177
|
+
required = true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const configItem = {
|
|
181
|
+
name: key,
|
|
182
|
+
value: value.replace('kv://', ''), // Remove kv:// prefix for KeyVault
|
|
183
|
+
location,
|
|
184
|
+
required
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Merge portalInput if it exists in variables.yaml
|
|
188
|
+
if (portalInputMap.has(key)) {
|
|
189
|
+
configItem.portalInput = portalInputMap.get(key);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
configuration.push(configItem);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return configuration;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = {
|
|
199
|
+
loadVariables,
|
|
200
|
+
loadEnvTemplate,
|
|
201
|
+
loadRbac,
|
|
202
|
+
validatePortalInput,
|
|
203
|
+
parseEnvironmentVariables
|
|
204
|
+
};
|
|
205
|
+
|